Repository: doctrine/orm
Branch: 3.6.x
Commit: 27c33cf88dde
Files: 1645
Total size: 6.3 MB
Directory structure:
gitextract_zhimkn3r/
├── .doctrine-project.json
├── .gitattributes
├── .github/
│ ├── PULL_REQUEST_TEMPLATE/
│ │ ├── Failing_Test.md
│ │ ├── Improvement.md
│ │ └── New_Feature.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── coding-standards.yml
│ ├── composer-lint.yml
│ ├── continuous-integration.yml
│ ├── documentation.yml
│ ├── phpbench.yml
│ ├── release-on-milestone-closed.yml
│ ├── stale.yml
│ ├── static-analysis.yml
│ └── website-schema.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── UPGRADE.md
├── ci/
│ └── github/
│ └── phpunit/
│ ├── mysqli.xml
│ ├── pdo_mysql.xml
│ ├── pdo_pgsql.xml
│ ├── pdo_sqlite.xml
│ ├── pgsql.xml
│ └── sqlite3.xml
├── composer.json
├── docs/
│ ├── .gitignore
│ ├── LICENSE.md
│ ├── README.md
│ ├── composer.json
│ └── en/
│ ├── cookbook/
│ │ ├── advanced-field-value-conversion-using-custom-mapping-types.rst
│ │ ├── aggregate-fields.rst
│ │ ├── custom-mapping-types.rst
│ │ ├── decorator-pattern.rst
│ │ ├── dql-custom-walkers/
│ │ │ └── InterpolateParametersSQLOutputWalker.php
│ │ ├── dql-custom-walkers.rst
│ │ ├── dql-user-defined-functions.rst
│ │ ├── entities-in-session.rst
│ │ ├── generated-columns/
│ │ │ ├── Article.php
│ │ │ └── Person.php
│ │ ├── generated-columns.rst
│ │ ├── implementing-arrayaccess-for-domain-objects.rst
│ │ ├── mysql-enums.rst
│ │ ├── resolve-target-entity-listener.rst
│ │ ├── sql-table-prefixes.rst
│ │ ├── strategy-cookbook-introduction.rst
│ │ ├── validation-of-entities.rst
│ │ └── working-with-datetime.rst
│ ├── index.rst
│ ├── reference/
│ │ ├── advanced-configuration.rst
│ │ ├── architecture.rst
│ │ ├── association-mapping.rst
│ │ ├── attributes-reference.rst
│ │ ├── basic-mapping/
│ │ │ ├── DefaultValues.php
│ │ │ └── default-values.xml
│ │ ├── basic-mapping.rst
│ │ ├── batch-processing.rst
│ │ ├── best-practices.rst
│ │ ├── caching.rst
│ │ ├── change-tracking-policies.rst
│ │ ├── configuration.rst
│ │ ├── dql-doctrine-query-language.rst
│ │ ├── events.rst
│ │ ├── faq.rst
│ │ ├── filters.rst
│ │ ├── improving-performance.rst
│ │ ├── inheritance-mapping.rst
│ │ ├── installation.rst
│ │ ├── limitations-and-known-issues.rst
│ │ ├── metadata-drivers.rst
│ │ ├── namingstrategy.rst
│ │ ├── native-sql.rst
│ │ ├── partial-hydration.rst
│ │ ├── partial-objects.rst
│ │ ├── php-mapping.rst
│ │ ├── query-builder.rst
│ │ ├── second-level-cache.rst
│ │ ├── security.rst
│ │ ├── tools.rst
│ │ ├── transactions-and-concurrency.rst
│ │ ├── typedfieldmapper.rst
│ │ ├── unitofwork-associations.rst
│ │ ├── unitofwork.rst
│ │ ├── working-with-associations.rst
│ │ ├── working-with-objects.rst
│ │ └── xml-mapping.rst
│ ├── sidebar.rst
│ └── tutorials/
│ ├── composite-primary-keys.rst
│ ├── embeddables.rst
│ ├── extra-lazy-associations.rst
│ ├── getting-started.rst
│ ├── ordered-associations.rst
│ ├── override-field-association-mappings-in-subclasses.rst
│ ├── pagination.rst
│ ├── working-with-indexed-associations/
│ │ ├── Market.php
│ │ └── market.xml
│ └── working-with-indexed-associations.rst
├── doctrine-mapping.xsd
├── phpbench.json
├── phpcs.xml.dist
├── phpstan-baseline.neon
├── phpstan-dbal3.neon
├── phpstan-params.neon
├── phpstan.neon
├── phpunit.xml.dist
├── src/
│ ├── AbstractQuery.php
│ ├── Cache/
│ │ ├── AssociationCacheEntry.php
│ │ ├── CacheConfiguration.php
│ │ ├── CacheEntry.php
│ │ ├── CacheException.php
│ │ ├── CacheFactory.php
│ │ ├── CacheKey.php
│ │ ├── CollectionCacheEntry.php
│ │ ├── CollectionCacheKey.php
│ │ ├── CollectionHydrator.php
│ │ ├── ConcurrentRegion.php
│ │ ├── DefaultCache.php
│ │ ├── DefaultCacheFactory.php
│ │ ├── DefaultCollectionHydrator.php
│ │ ├── DefaultEntityHydrator.php
│ │ ├── DefaultQueryCache.php
│ │ ├── EntityCacheEntry.php
│ │ ├── EntityCacheKey.php
│ │ ├── EntityHydrator.php
│ │ ├── Exception/
│ │ │ ├── CacheException.php
│ │ │ ├── CannotUpdateReadOnlyCollection.php
│ │ │ ├── CannotUpdateReadOnlyEntity.php
│ │ │ ├── FeatureNotImplemented.php
│ │ │ ├── NonCacheableEntity.php
│ │ │ └── NonCacheableEntityAssociation.php
│ │ ├── Lock.php
│ │ ├── LockException.php
│ │ ├── Logging/
│ │ │ ├── CacheLogger.php
│ │ │ ├── CacheLoggerChain.php
│ │ │ └── StatisticsCacheLogger.php
│ │ ├── Persister/
│ │ │ ├── CachedPersister.php
│ │ │ ├── Collection/
│ │ │ │ ├── AbstractCollectionPersister.php
│ │ │ │ ├── CachedCollectionPersister.php
│ │ │ │ ├── NonStrictReadWriteCachedCollectionPersister.php
│ │ │ │ ├── ReadOnlyCachedCollectionPersister.php
│ │ │ │ └── ReadWriteCachedCollectionPersister.php
│ │ │ └── Entity/
│ │ │ ├── AbstractEntityPersister.php
│ │ │ ├── CachedEntityPersister.php
│ │ │ ├── NonStrictReadWriteCachedEntityPersister.php
│ │ │ ├── ReadOnlyCachedEntityPersister.php
│ │ │ └── ReadWriteCachedEntityPersister.php
│ │ ├── QueryCache.php
│ │ ├── QueryCacheEntry.php
│ │ ├── QueryCacheKey.php
│ │ ├── QueryCacheValidator.php
│ │ ├── Region/
│ │ │ ├── DefaultRegion.php
│ │ │ ├── FileLockRegion.php
│ │ │ └── UpdateTimestampCache.php
│ │ ├── Region.php
│ │ ├── RegionsConfiguration.php
│ │ ├── TimestampCacheEntry.php
│ │ ├── TimestampCacheKey.php
│ │ ├── TimestampQueryCacheValidator.php
│ │ └── TimestampRegion.php
│ ├── Cache.php
│ ├── Configuration.php
│ ├── Decorator/
│ │ └── EntityManagerDecorator.php
│ ├── EntityManager.php
│ ├── EntityManagerInterface.php
│ ├── EntityNotFoundException.php
│ ├── EntityRepository.php
│ ├── Event/
│ │ ├── ListenersInvoker.php
│ │ ├── LoadClassMetadataEventArgs.php
│ │ ├── OnClassMetadataNotFoundEventArgs.php
│ │ ├── OnClearEventArgs.php
│ │ ├── OnFlushEventArgs.php
│ │ ├── PostFlushEventArgs.php
│ │ ├── PostLoadEventArgs.php
│ │ ├── PostPersistEventArgs.php
│ │ ├── PostRemoveEventArgs.php
│ │ ├── PostUpdateEventArgs.php
│ │ ├── PreFlushEventArgs.php
│ │ ├── PrePersistEventArgs.php
│ │ ├── PreRemoveEventArgs.php
│ │ └── PreUpdateEventArgs.php
│ ├── Events.php
│ ├── Exception/
│ │ ├── ConfigurationException.php
│ │ ├── DuplicateFieldException.php
│ │ ├── EntityIdentityCollisionException.php
│ │ ├── EntityManagerClosed.php
│ │ ├── EntityMissingAssignedId.php
│ │ ├── InvalidEntityRepository.php
│ │ ├── InvalidHydrationMode.php
│ │ ├── ManagerException.php
│ │ ├── MissingIdentifierField.php
│ │ ├── MissingMappingDriverImplementation.php
│ │ ├── MultipleSelectorsFoundException.php
│ │ ├── NoMatchingPropertyException.php
│ │ ├── NotSupported.php
│ │ ├── ORMException.php
│ │ ├── PersisterException.php
│ │ ├── RepositoryException.php
│ │ ├── SchemaToolException.php
│ │ ├── UnexpectedAssociationValue.php
│ │ └── UnrecognizedIdentifierFields.php
│ ├── Id/
│ │ ├── AbstractIdGenerator.php
│ │ ├── AssignedGenerator.php
│ │ ├── BigIntegerIdentityGenerator.php
│ │ ├── IdentityGenerator.php
│ │ └── SequenceGenerator.php
│ ├── Internal/
│ │ ├── Hydration/
│ │ │ ├── AbstractHydrator.php
│ │ │ ├── ArrayHydrator.php
│ │ │ ├── HydrationException.php
│ │ │ ├── ObjectHydrator.php
│ │ │ ├── ScalarColumnHydrator.php
│ │ │ ├── ScalarHydrator.php
│ │ │ ├── SimpleObjectHydrator.php
│ │ │ └── SingleScalarHydrator.php
│ │ ├── HydrationCompleteHandler.php
│ │ ├── NoUnknownNamedArguments.php
│ │ ├── SQLResultCasing.php
│ │ ├── StronglyConnectedComponents.php
│ │ ├── TopologicalSort/
│ │ │ └── CycleDetectedException.php
│ │ ├── TopologicalSort.php
│ │ └── UnitOfWork/
│ │ └── InsertBatch.php
│ ├── LazyCriteriaCollection.php
│ ├── Mapping/
│ │ ├── AnsiQuoteStrategy.php
│ │ ├── ArrayAccessImplementation.php
│ │ ├── AssociationMapping.php
│ │ ├── AssociationOverride.php
│ │ ├── AssociationOverrides.php
│ │ ├── AttributeOverride.php
│ │ ├── AttributeOverrides.php
│ │ ├── Builder/
│ │ │ ├── AssociationBuilder.php
│ │ │ ├── ClassMetadataBuilder.php
│ │ │ ├── EmbeddedBuilder.php
│ │ │ ├── EntityListenerBuilder.php
│ │ │ ├── FieldBuilder.php
│ │ │ ├── ManyToManyAssociationBuilder.php
│ │ │ └── OneToManyAssociationBuilder.php
│ │ ├── Cache.php
│ │ ├── ChainTypedFieldMapper.php
│ │ ├── ChangeTrackingPolicy.php
│ │ ├── ClassMetadata.php
│ │ ├── ClassMetadataFactory.php
│ │ ├── Column.php
│ │ ├── CustomIdGenerator.php
│ │ ├── DefaultEntityListenerResolver.php
│ │ ├── DefaultNamingStrategy.php
│ │ ├── DefaultQuoteStrategy.php
│ │ ├── DefaultTypedFieldMapper.php
│ │ ├── DiscriminatorColumn.php
│ │ ├── DiscriminatorColumnMapping.php
│ │ ├── DiscriminatorMap.php
│ │ ├── Driver/
│ │ │ ├── AttributeDriver.php
│ │ │ ├── AttributeReader.php
│ │ │ ├── DatabaseDriver.php
│ │ │ ├── LoadMappingFileImplementation.php
│ │ │ ├── ReflectionBasedDriver.php
│ │ │ ├── RepeatableAttributeCollection.php
│ │ │ ├── SimplifiedXmlDriver.php
│ │ │ └── XmlDriver.php
│ │ ├── Embeddable.php
│ │ ├── Embedded.php
│ │ ├── EmbeddedClassMapping.php
│ │ ├── Entity.php
│ │ ├── EntityListenerResolver.php
│ │ ├── EntityListeners.php
│ │ ├── Exception/
│ │ │ ├── InvalidCustomGenerator.php
│ │ │ └── UnknownGeneratorType.php
│ │ ├── FieldMapping.php
│ │ ├── GeneratedValue.php
│ │ ├── GetReflectionClassImplementation.php
│ │ ├── HasLifecycleCallbacks.php
│ │ ├── Id.php
│ │ ├── Index.php
│ │ ├── InheritanceType.php
│ │ ├── InverseJoinColumn.php
│ │ ├── InverseSideMapping.php
│ │ ├── JoinColumn.php
│ │ ├── JoinColumnMapping.php
│ │ ├── JoinColumnProperties.php
│ │ ├── JoinColumns.php
│ │ ├── JoinTable.php
│ │ ├── JoinTableMapping.php
│ │ ├── LegacyReflectionFields.php
│ │ ├── ManyToMany.php
│ │ ├── ManyToManyAssociationMapping.php
│ │ ├── ManyToManyInverseSideMapping.php
│ │ ├── ManyToManyOwningSideMapping.php
│ │ ├── ManyToOne.php
│ │ ├── ManyToOneAssociationMapping.php
│ │ ├── MappedSuperclass.php
│ │ ├── MappingAttribute.php
│ │ ├── MappingException.php
│ │ ├── NamingStrategy.php
│ │ ├── OneToMany.php
│ │ ├── OneToManyAssociationMapping.php
│ │ ├── OneToOne.php
│ │ ├── OneToOneAssociationMapping.php
│ │ ├── OneToOneInverseSideMapping.php
│ │ ├── OneToOneOwningSideMapping.php
│ │ ├── OrderBy.php
│ │ ├── OwningSideMapping.php
│ │ ├── PostLoad.php
│ │ ├── PostPersist.php
│ │ ├── PostRemove.php
│ │ ├── PostUpdate.php
│ │ ├── PreFlush.php
│ │ ├── PrePersist.php
│ │ ├── PreRemove.php
│ │ ├── PreUpdate.php
│ │ ├── PropertyAccessors/
│ │ │ ├── EmbeddablePropertyAccessor.php
│ │ │ ├── EnumPropertyAccessor.php
│ │ │ ├── ObjectCastPropertyAccessor.php
│ │ │ ├── PropertyAccessor.php
│ │ │ ├── PropertyAccessorFactory.php
│ │ │ ├── RawValuePropertyAccessor.php
│ │ │ ├── ReadonlyAccessor.php
│ │ │ └── TypedNoDefaultPropertyAccessor.php
│ │ ├── QuoteStrategy.php
│ │ ├── ReflectionEmbeddedProperty.php
│ │ ├── ReflectionEnumProperty.php
│ │ ├── ReflectionReadonlyProperty.php
│ │ ├── SequenceGenerator.php
│ │ ├── Table.php
│ │ ├── ToManyAssociationMapping.php
│ │ ├── ToManyAssociationMappingImplementation.php
│ │ ├── ToManyInverseSideMapping.php
│ │ ├── ToManyOwningSideMapping.php
│ │ ├── ToOneAssociationMapping.php
│ │ ├── ToOneInverseSideMapping.php
│ │ ├── ToOneOwningSideMapping.php
│ │ ├── TypedFieldMapper.php
│ │ ├── UnderscoreNamingStrategy.php
│ │ ├── UniqueConstraint.php
│ │ └── Version.php
│ ├── NativeQuery.php
│ ├── NoResultException.php
│ ├── NonUniqueResultException.php
│ ├── ORMInvalidArgumentException.php
│ ├── ORMSetup.php
│ ├── OptimisticLockException.php
│ ├── PersistentCollection.php
│ ├── Persisters/
│ │ ├── Collection/
│ │ │ ├── AbstractCollectionPersister.php
│ │ │ ├── CollectionPersister.php
│ │ │ ├── ManyToManyPersister.php
│ │ │ └── OneToManyPersister.php
│ │ ├── Entity/
│ │ │ ├── AbstractEntityInheritancePersister.php
│ │ │ ├── BasicEntityPersister.php
│ │ │ ├── CachedPersisterContext.php
│ │ │ ├── EntityPersister.php
│ │ │ ├── JoinedSubclassPersister.php
│ │ │ └── SingleTablePersister.php
│ │ ├── Exception/
│ │ │ ├── CantUseInOperatorOnCompositeKeys.php
│ │ │ ├── InvalidOrientation.php
│ │ │ └── UnrecognizedField.php
│ │ ├── MatchingAssociationFieldRequiresObject.php
│ │ ├── PersisterException.php
│ │ ├── SqlExpressionVisitor.php
│ │ └── SqlValueVisitor.php
│ ├── PessimisticLockException.php
│ ├── Proxy/
│ │ ├── Autoloader.php
│ │ ├── DefaultProxyClassNameResolver.php
│ │ ├── InternalProxy.php
│ │ ├── NotAProxyClass.php
│ │ └── ProxyFactory.php
│ ├── Query/
│ │ ├── AST/
│ │ │ ├── ASTException.php
│ │ │ ├── AggregateExpression.php
│ │ │ ├── ArithmeticExpression.php
│ │ │ ├── ArithmeticFactor.php
│ │ │ ├── ArithmeticTerm.php
│ │ │ ├── BetweenExpression.php
│ │ │ ├── CoalesceExpression.php
│ │ │ ├── CollectionMemberExpression.php
│ │ │ ├── ComparisonExpression.php
│ │ │ ├── ConditionalExpression.php
│ │ │ ├── ConditionalFactor.php
│ │ │ ├── ConditionalPrimary.php
│ │ │ ├── ConditionalTerm.php
│ │ │ ├── DeleteClause.php
│ │ │ ├── DeleteStatement.php
│ │ │ ├── EmptyCollectionComparisonExpression.php
│ │ │ ├── EntityAsDtoArgumentExpression.php
│ │ │ ├── ExistsExpression.php
│ │ │ ├── FromClause.php
│ │ │ ├── Functions/
│ │ │ │ ├── AbsFunction.php
│ │ │ │ ├── AvgFunction.php
│ │ │ │ ├── BitAndFunction.php
│ │ │ │ ├── BitOrFunction.php
│ │ │ │ ├── ConcatFunction.php
│ │ │ │ ├── CountFunction.php
│ │ │ │ ├── CurrentDateFunction.php
│ │ │ │ ├── CurrentTimeFunction.php
│ │ │ │ ├── CurrentTimestampFunction.php
│ │ │ │ ├── DateAddFunction.php
│ │ │ │ ├── DateDiffFunction.php
│ │ │ │ ├── DateSubFunction.php
│ │ │ │ ├── FunctionNode.php
│ │ │ │ ├── IdentityFunction.php
│ │ │ │ ├── LengthFunction.php
│ │ │ │ ├── LocateFunction.php
│ │ │ │ ├── LowerFunction.php
│ │ │ │ ├── MaxFunction.php
│ │ │ │ ├── MinFunction.php
│ │ │ │ ├── ModFunction.php
│ │ │ │ ├── SizeFunction.php
│ │ │ │ ├── SqrtFunction.php
│ │ │ │ ├── SubstringFunction.php
│ │ │ │ ├── SumFunction.php
│ │ │ │ ├── TrimFunction.php
│ │ │ │ └── UpperFunction.php
│ │ │ ├── GeneralCaseExpression.php
│ │ │ ├── GroupByClause.php
│ │ │ ├── HavingClause.php
│ │ │ ├── IdentificationVariableDeclaration.php
│ │ │ ├── InListExpression.php
│ │ │ ├── InSubselectExpression.php
│ │ │ ├── IndexBy.php
│ │ │ ├── InputParameter.php
│ │ │ ├── InstanceOfExpression.php
│ │ │ ├── Join.php
│ │ │ ├── JoinAssociationDeclaration.php
│ │ │ ├── JoinAssociationPathExpression.php
│ │ │ ├── JoinClassPathExpression.php
│ │ │ ├── JoinVariableDeclaration.php
│ │ │ ├── LikeExpression.php
│ │ │ ├── Literal.php
│ │ │ ├── NewObjectExpression.php
│ │ │ ├── Node.php
│ │ │ ├── NullComparisonExpression.php
│ │ │ ├── NullIfExpression.php
│ │ │ ├── OrderByClause.php
│ │ │ ├── OrderByItem.php
│ │ │ ├── ParenthesisExpression.php
│ │ │ ├── PartialObjectExpression.php
│ │ │ ├── PathExpression.php
│ │ │ ├── Phase2OptimizableConditional.php
│ │ │ ├── QuantifiedExpression.php
│ │ │ ├── RangeVariableDeclaration.php
│ │ │ ├── SelectClause.php
│ │ │ ├── SelectExpression.php
│ │ │ ├── SelectStatement.php
│ │ │ ├── SimpleArithmeticExpression.php
│ │ │ ├── SimpleCaseExpression.php
│ │ │ ├── SimpleSelectClause.php
│ │ │ ├── SimpleSelectExpression.php
│ │ │ ├── SimpleWhenClause.php
│ │ │ ├── Subselect.php
│ │ │ ├── SubselectFromClause.php
│ │ │ ├── SubselectIdentificationVariableDeclaration.php
│ │ │ ├── TypedExpression.php
│ │ │ ├── UpdateClause.php
│ │ │ ├── UpdateItem.php
│ │ │ ├── UpdateStatement.php
│ │ │ ├── WhenClause.php
│ │ │ └── WhereClause.php
│ │ ├── Exec/
│ │ │ ├── AbstractSqlExecutor.php
│ │ │ ├── FinalizedSelectExecutor.php
│ │ │ ├── MultiTableDeleteExecutor.php
│ │ │ ├── MultiTableUpdateExecutor.php
│ │ │ ├── PreparedExecutorFinalizer.php
│ │ │ ├── SingleSelectExecutor.php
│ │ │ ├── SingleSelectSqlFinalizer.php
│ │ │ ├── SingleTableDeleteUpdateExecutor.php
│ │ │ └── SqlFinalizer.php
│ │ ├── Expr/
│ │ │ ├── Andx.php
│ │ │ ├── Base.php
│ │ │ ├── Comparison.php
│ │ │ ├── Composite.php
│ │ │ ├── From.php
│ │ │ ├── Func.php
│ │ │ ├── GroupBy.php
│ │ │ ├── Join.php
│ │ │ ├── Literal.php
│ │ │ ├── Math.php
│ │ │ ├── OrderBy.php
│ │ │ ├── Orx.php
│ │ │ └── Select.php
│ │ ├── Expr.php
│ │ ├── Filter/
│ │ │ ├── FilterException.php
│ │ │ ├── Parameter.php
│ │ │ └── SQLFilter.php
│ │ ├── FilterCollection.php
│ │ ├── Lexer.php
│ │ ├── OutputWalker.php
│ │ ├── Parameter.php
│ │ ├── ParameterTypeInferer.php
│ │ ├── Parser.php
│ │ ├── ParserResult.php
│ │ ├── Printer.php
│ │ ├── QueryException.php
│ │ ├── QueryExpressionVisitor.php
│ │ ├── ResultSetMapping.php
│ │ ├── ResultSetMappingBuilder.php
│ │ ├── SqlOutputWalker.php
│ │ ├── SqlWalker.php
│ │ ├── TokenType.php
│ │ ├── TreeWalker.php
│ │ ├── TreeWalkerAdapter.php
│ │ └── TreeWalkerChain.php
│ ├── Query.php
│ ├── QueryBuilder.php
│ ├── QueryType.php
│ ├── Repository/
│ │ ├── DefaultRepositoryFactory.php
│ │ ├── Exception/
│ │ │ ├── InvalidFindByCall.php
│ │ │ └── InvalidMagicMethodCall.php
│ │ └── RepositoryFactory.php
│ ├── Tools/
│ │ ├── AttachEntityListenersListener.php
│ │ ├── Console/
│ │ │ ├── ApplicationCompatibility.php
│ │ │ ├── Command/
│ │ │ │ ├── AbstractEntityManagerCommand.php
│ │ │ │ ├── ClearCache/
│ │ │ │ │ ├── CollectionRegionCommand.php
│ │ │ │ │ ├── EntityRegionCommand.php
│ │ │ │ │ ├── MetadataCommand.php
│ │ │ │ │ ├── QueryCommand.php
│ │ │ │ │ ├── QueryRegionCommand.php
│ │ │ │ │ └── ResultCommand.php
│ │ │ │ ├── Debug/
│ │ │ │ │ ├── AbstractCommand.php
│ │ │ │ │ ├── DebugEntityListenersDoctrineCommand.php
│ │ │ │ │ └── DebugEventManagerDoctrineCommand.php
│ │ │ │ ├── GenerateProxiesCommand.php
│ │ │ │ ├── InfoCommand.php
│ │ │ │ ├── MappingDescribeCommand.php
│ │ │ │ ├── MappingDescribeCommandFormat.php
│ │ │ │ ├── RunDqlCommand.php
│ │ │ │ ├── SchemaTool/
│ │ │ │ │ ├── AbstractCommand.php
│ │ │ │ │ ├── CreateCommand.php
│ │ │ │ │ ├── DropCommand.php
│ │ │ │ │ └── UpdateCommand.php
│ │ │ │ └── ValidateSchemaCommand.php
│ │ │ ├── ConsoleRunner.php
│ │ │ ├── EntityManagerProvider/
│ │ │ │ ├── ConnectionFromManagerProvider.php
│ │ │ │ ├── SingleManagerProvider.php
│ │ │ │ └── UnknownManagerException.php
│ │ │ ├── EntityManagerProvider.php
│ │ │ └── MetadataFilter.php
│ │ ├── Debug.php
│ │ ├── DebugUnitOfWorkListener.php
│ │ ├── Event/
│ │ │ ├── GenerateSchemaEventArgs.php
│ │ │ └── GenerateSchemaTableEventArgs.php
│ │ ├── Exception/
│ │ │ ├── MissingColumnException.php
│ │ │ └── NotSupported.php
│ │ ├── Pagination/
│ │ │ ├── CountOutputWalker.php
│ │ │ ├── CountWalker.php
│ │ │ ├── Exception/
│ │ │ │ └── RowNumberOverFunctionNotEnabled.php
│ │ │ ├── LimitSubqueryOutputWalker.php
│ │ │ ├── LimitSubqueryWalker.php
│ │ │ ├── Paginator.php
│ │ │ ├── RootTypeWalker.php
│ │ │ ├── RowNumberOverFunction.php
│ │ │ └── WhereInWalker.php
│ │ ├── ResolveTargetEntityListener.php
│ │ ├── SchemaTool.php
│ │ ├── SchemaValidator.php
│ │ ├── ToolEvents.php
│ │ └── ToolsException.php
│ ├── TransactionRequiredException.php
│ ├── UnexpectedResultException.php
│ ├── UnitOfWork.php
│ └── Utility/
│ ├── HierarchyDiscriminatorResolver.php
│ ├── IdentifierFlattener.php
│ ├── LockSqlHelper.php
│ └── PersisterHelper.php
└── tests/
├── .gitignore
├── Doctrine/
│ └── Tests/
│ └── ORM/
│ └── Functional/
│ ├── GH8011Test.php
│ └── Ticket/
│ ├── GH12063Test.php
│ └── LazyEagerCollectionTest.php
├── Performance/
│ ├── ChangeSet/
│ │ └── UnitOfWorkComputeChangesBench.php
│ ├── EntityManagerFactory.php
│ ├── Hydration/
│ │ ├── MixedQueryFetchJoinArrayHydrationPerformanceBench.php
│ │ ├── MixedQueryFetchJoinFullObjectHydrationPerformanceBench.php
│ │ ├── MixedQueryFetchJoinPartialObjectHydrationPerformanceBench.php
│ │ ├── SimpleHydrationBench.php
│ │ ├── SimpleInsertPerformanceBench.php
│ │ ├── SimpleQueryArrayHydrationPerformanceBench.php
│ │ ├── SimpleQueryFullObjectHydrationPerformanceBench.php
│ │ ├── SimpleQueryPartialObjectHydrationPerformanceBench.php
│ │ ├── SimpleQueryScalarHydrationPerformanceBench.php
│ │ ├── SingleTableInheritanceHydrationPerformanceBench.php
│ │ └── SingleTableInheritanceInsertPerformanceBench.php
│ ├── LazyLoading/
│ │ ├── ProxyInitializationTimeBench.php
│ │ └── ProxyInstantiationTimeBench.php
│ ├── Mock/
│ │ ├── NonLoadingPersister.php
│ │ ├── NonProxyLoadingEntityManager.php
│ │ └── NonProxyLoadingUnitOfWork.php
│ └── Query/
│ └── QueryBoundParameterProcessingBench.php
├── README.markdown
├── StaticAnalysis/
│ ├── Mapping/
│ │ └── class-metadata-constructor.php
│ ├── Tools/
│ │ └── Pagination/
│ │ └── paginator-covariant.php
│ └── get-metadata.php
├── Tests/
│ ├── DbalExtensions/
│ │ ├── Connection.php
│ │ ├── QueryLog.php
│ │ └── SqlLogger.php
│ ├── DbalTypes/
│ │ ├── CustomIdObject.php
│ │ ├── CustomIdObjectType.php
│ │ ├── CustomIntType.php
│ │ ├── GH8565EmployeePayloadType.php
│ │ ├── GH8565ManagerPayloadType.php
│ │ ├── NegativeToPositiveType.php
│ │ ├── Rot13Type.php
│ │ └── UpperCaseStringType.php
│ ├── EventListener/
│ │ └── CacheMetadataListener.php
│ ├── IterableTester.php
│ ├── Mocks/
│ │ ├── ArrayResultFactory.php
│ │ ├── AttributeDriverFactory.php
│ │ ├── CacheEntryMock.php
│ │ ├── CacheKeyMock.php
│ │ ├── CacheRegionMock.php
│ │ ├── CompatibilityType.php
│ │ ├── ConcurrentRegionMock.php
│ │ ├── CustomTreeWalkerJoin.php
│ │ ├── EntityManagerMock.php
│ │ ├── EntityPersisterMock.php
│ │ ├── ExceptionConverterMock.php
│ │ ├── MetadataDriverMock.php
│ │ ├── NullSqlWalker.php
│ │ ├── SchemaManagerMock.php
│ │ ├── TimestampRegionMock.php
│ │ └── UnitOfWorkMock.php
│ ├── Models/
│ │ ├── AbstractFetchEager/
│ │ │ ├── AbstractRemoteControl.php
│ │ │ ├── MobileRemoteControl.php
│ │ │ └── User.php
│ │ ├── BigIntegers/
│ │ │ └── BigIntegers.php
│ │ ├── BinaryPrimaryKey/
│ │ │ ├── BinaryId.php
│ │ │ ├── BinaryIdType.php
│ │ │ └── Category.php
│ │ ├── CMS/
│ │ │ ├── CmsAddress.php
│ │ │ ├── CmsAddressDTO.php
│ │ │ ├── CmsAddressDTONamedArgs.php
│ │ │ ├── CmsAddressListener.php
│ │ │ ├── CmsArticle.php
│ │ │ ├── CmsComment.php
│ │ │ ├── CmsDumbDTO.php
│ │ │ ├── CmsEmail.php
│ │ │ ├── CmsEmployee.php
│ │ │ ├── CmsGroup.php
│ │ │ ├── CmsPhonenumber.php
│ │ │ ├── CmsTag.php
│ │ │ ├── CmsUser.php
│ │ │ ├── CmsUserDTO.php
│ │ │ ├── CmsUserDTONamedArgs.php
│ │ │ └── CmsUserDTOVariadicArg.php
│ │ ├── Cache/
│ │ │ ├── Action.php
│ │ │ ├── Address.php
│ │ │ ├── Attraction.php
│ │ │ ├── AttractionContactInfo.php
│ │ │ ├── AttractionInfo.php
│ │ │ ├── AttractionLocationInfo.php
│ │ │ ├── Bar.php
│ │ │ ├── Beach.php
│ │ │ ├── City.php
│ │ │ ├── Client.php
│ │ │ ├── ComplexAction.php
│ │ │ ├── Country.php
│ │ │ ├── Flight.php
│ │ │ ├── Login.php
│ │ │ ├── Person.php
│ │ │ ├── Restaurant.php
│ │ │ ├── State.php
│ │ │ ├── Token.php
│ │ │ ├── Travel.php
│ │ │ ├── Traveler.php
│ │ │ ├── TravelerProfile.php
│ │ │ └── TravelerProfileInfo.php
│ │ ├── Company/
│ │ │ ├── CompanyAuction.php
│ │ │ ├── CompanyCar.php
│ │ │ ├── CompanyContract.php
│ │ │ ├── CompanyContractListener.php
│ │ │ ├── CompanyEmployee.php
│ │ │ ├── CompanyEvent.php
│ │ │ ├── CompanyFixContract.php
│ │ │ ├── CompanyFlexContract.php
│ │ │ ├── CompanyFlexUltraContract.php
│ │ │ ├── CompanyFlexUltraContractListener.php
│ │ │ ├── CompanyManager.php
│ │ │ ├── CompanyOrganization.php
│ │ │ ├── CompanyPerson.php
│ │ │ └── CompanyRaffle.php
│ │ ├── CompositeKeyInheritance/
│ │ │ ├── JoinedChildClass.php
│ │ │ ├── JoinedDerivedChildClass.php
│ │ │ ├── JoinedDerivedIdentityClass.php
│ │ │ ├── JoinedDerivedRootClass.php
│ │ │ ├── JoinedRootClass.php
│ │ │ ├── SingleChildClass.php
│ │ │ └── SingleRootClass.php
│ │ ├── CompositeKeyRelations/
│ │ │ ├── CustomerClass.php
│ │ │ └── InvoiceClass.php
│ │ ├── CustomType/
│ │ │ ├── CustomIdObjectTypeChild.php
│ │ │ ├── CustomIdObjectTypeParent.php
│ │ │ ├── CustomTypeChild.php
│ │ │ ├── CustomTypeParent.php
│ │ │ └── CustomTypeUpperCase.php
│ │ ├── Customer/
│ │ │ ├── CustomerType.php
│ │ │ ├── ExternalCustomer.php
│ │ │ └── InternalCustomer.php
│ │ ├── DDC117/
│ │ │ ├── DDC117ApproveChanges.php
│ │ │ ├── DDC117Article.php
│ │ │ ├── DDC117ArticleDetails.php
│ │ │ ├── DDC117Editor.php
│ │ │ ├── DDC117Link.php
│ │ │ ├── DDC117Reference.php
│ │ │ └── DDC117Translation.php
│ │ ├── DDC1476/
│ │ │ └── DDC1476EntityWithDefaultFieldType.php
│ │ ├── DDC1590/
│ │ │ ├── DDC1590Entity.php
│ │ │ └── DDC1590User.php
│ │ ├── DDC1872/
│ │ │ ├── DDC1872Bar.php
│ │ │ ├── DDC1872ExampleEntityWithOverride.php
│ │ │ ├── DDC1872ExampleEntityWithoutOverride.php
│ │ │ └── DDC1872ExampleTrait.php
│ │ ├── DDC2372/
│ │ │ ├── DDC2372Address.php
│ │ │ ├── DDC2372Admin.php
│ │ │ ├── DDC2372User.php
│ │ │ └── Traits/
│ │ │ └── DDC2372AddressAndAccessors.php
│ │ ├── DDC2504/
│ │ │ ├── DDC2504ChildClass.php
│ │ │ ├── DDC2504OtherClass.php
│ │ │ └── DDC2504RootClass.php
│ │ ├── DDC2825/
│ │ │ ├── ExplicitSchemaAndTable.php
│ │ │ └── SchemaAndTableInTableName.php
│ │ ├── DDC3231/
│ │ │ ├── DDC3231EntityRepository.php
│ │ │ ├── DDC3231User1.php
│ │ │ ├── DDC3231User1NoNamespace.php
│ │ │ ├── DDC3231User2.php
│ │ │ └── DDC3231User2NoNamespace.php
│ │ ├── DDC3293/
│ │ │ ├── DDC3293Address.php
│ │ │ ├── DDC3293User.php
│ │ │ └── DDC3293UserPrefixed.php
│ │ ├── DDC3346/
│ │ │ ├── DDC3346Article.php
│ │ │ └── DDC3346Author.php
│ │ ├── DDC3579/
│ │ │ ├── DDC3579Admin.php
│ │ │ ├── DDC3579Group.php
│ │ │ └── DDC3579User.php
│ │ ├── DDC3597/
│ │ │ ├── DDC3597Image.php
│ │ │ ├── DDC3597Media.php
│ │ │ ├── DDC3597Root.php
│ │ │ └── Embeddable/
│ │ │ └── DDC3597Dimension.php
│ │ ├── DDC3699/
│ │ │ ├── DDC3699Child.php
│ │ │ ├── DDC3699Parent.php
│ │ │ ├── DDC3699RelationMany.php
│ │ │ └── DDC3699RelationOne.php
│ │ ├── DDC3711/
│ │ │ ├── DDC3711EntityA.php
│ │ │ └── DDC3711EntityB.php
│ │ ├── DDC3899/
│ │ │ ├── DDC3899Contract.php
│ │ │ ├── DDC3899FixContract.php
│ │ │ ├── DDC3899FlexContract.php
│ │ │ └── DDC3899User.php
│ │ ├── DDC4006/
│ │ │ ├── DDC4006User.php
│ │ │ └── DDC4006UserId.php
│ │ ├── DDC5934/
│ │ │ ├── DDC5934BaseContract.php
│ │ │ ├── DDC5934Contract.php
│ │ │ └── DDC5934Member.php
│ │ ├── DDC6412/
│ │ │ └── DDC6412File.php
│ │ ├── DDC6573/
│ │ │ ├── DDC6573Currency.php
│ │ │ ├── DDC6573Item.php
│ │ │ └── DDC6573Money.php
│ │ ├── DDC753/
│ │ │ ├── DDC753CustomRepository.php
│ │ │ ├── DDC753DefaultRepository.php
│ │ │ ├── DDC753EntityWithCustomRepository.php
│ │ │ ├── DDC753EntityWithDefaultCustomRepository.php
│ │ │ ├── DDC753EntityWithInvalidRepository.php
│ │ │ └── DDC753InvalidRepository.php
│ │ ├── DDC869/
│ │ │ ├── DDC869ChequePayment.php
│ │ │ ├── DDC869CreditCardPayment.php
│ │ │ ├── DDC869Payment.php
│ │ │ └── DDC869PaymentRepository.php
│ │ ├── DDC889/
│ │ │ ├── DDC889Class.php
│ │ │ ├── DDC889Entity.php
│ │ │ └── DDC889SuperClass.php
│ │ ├── DDC964/
│ │ │ ├── DDC964Address.php
│ │ │ ├── DDC964Admin.php
│ │ │ ├── DDC964Group.php
│ │ │ ├── DDC964Guest.php
│ │ │ └── DDC964User.php
│ │ ├── DataTransferObjects/
│ │ │ ├── DtoWithArrayOfEnums.php
│ │ │ └── DtoWithEnum.php
│ │ ├── DirectoryTree/
│ │ │ ├── AbstractContentItem.php
│ │ │ ├── Directory.php
│ │ │ └── File.php
│ │ ├── ECommerce/
│ │ │ ├── ECommerceCart.php
│ │ │ ├── ECommerceCategory.php
│ │ │ ├── ECommerceCustomer.php
│ │ │ ├── ECommerceFeature.php
│ │ │ ├── ECommerceProduct.php
│ │ │ ├── ECommerceProduct2.php
│ │ │ └── ECommerceShipping.php
│ │ ├── EagerFetchedCompositeOneToMany/
│ │ │ ├── RootEntity.php
│ │ │ ├── SecondLevel.php
│ │ │ └── SecondLevelWithoutCompositePrimaryKey.php
│ │ ├── Enums/
│ │ │ ├── AccessLevel.php
│ │ │ ├── BookCategory.php
│ │ │ ├── BookGenre.php
│ │ │ ├── BookWithGenre.php
│ │ │ ├── Card.php
│ │ │ ├── CardNativeEnum.php
│ │ │ ├── CardWithDefault.php
│ │ │ ├── CardWithNullable.php
│ │ │ ├── City.php
│ │ │ ├── FaultySwitch.php
│ │ │ ├── Library.php
│ │ │ ├── Product.php
│ │ │ ├── Quantity.php
│ │ │ ├── Scale.php
│ │ │ ├── Suit.php
│ │ │ ├── SwitchStatus.php
│ │ │ ├── TypedCard.php
│ │ │ ├── TypedCardEnumCompositeId.php
│ │ │ ├── TypedCardEnumId.php
│ │ │ ├── TypedCardNativeEnum.php
│ │ │ ├── Unit.php
│ │ │ └── UserStatus.php
│ │ ├── Forum/
│ │ │ ├── ForumAvatar.php
│ │ │ ├── ForumBoard.php
│ │ │ ├── ForumCategory.php
│ │ │ ├── ForumEntry.php
│ │ │ └── ForumUser.php
│ │ ├── GH10132/
│ │ │ ├── Complex.php
│ │ │ └── ComplexChild.php
│ │ ├── GH10288/
│ │ │ └── GH10288People.php
│ │ ├── GH10334/
│ │ │ ├── GH10334Foo.php
│ │ │ ├── GH10334FooCollection.php
│ │ │ ├── GH10334Product.php
│ │ │ ├── GH10334ProductType.php
│ │ │ └── GH10334ProductTypeId.php
│ │ ├── GH10336/
│ │ │ ├── GH10336Entity.php
│ │ │ └── GH10336Relation.php
│ │ ├── GH11524/
│ │ │ ├── GH11524Entity.php
│ │ │ ├── GH11524Listener.php
│ │ │ └── GH11524Relation.php
│ │ ├── GH7141/
│ │ │ └── GH7141Article.php
│ │ ├── GH7316/
│ │ │ └── GH7316Article.php
│ │ ├── GH7717/
│ │ │ ├── GH7717Child.php
│ │ │ └── GH7717Parent.php
│ │ ├── GH8565/
│ │ │ ├── GH8565Employee.php
│ │ │ ├── GH8565Manager.php
│ │ │ └── GH8565Person.php
│ │ ├── Generic/
│ │ │ ├── BooleanModel.php
│ │ │ ├── DateTimeModel.php
│ │ │ ├── DecimalModel.php
│ │ │ ├── NonAlphaColumnsEntity.php
│ │ │ └── SerializationModel.php
│ │ ├── GeoNames/
│ │ │ ├── Admin1.php
│ │ │ ├── Admin1AlternateName.php
│ │ │ ├── City.php
│ │ │ └── Country.php
│ │ ├── Global/
│ │ │ └── GlobalNamespaceModel.php
│ │ ├── Hydration/
│ │ │ ├── EntityWithArrayDefaultArrayValueM2M.php
│ │ │ └── SimpleEntity.php
│ │ ├── InvalidXml.php
│ │ ├── Issue5989/
│ │ │ ├── Issue5989Employee.php
│ │ │ ├── Issue5989Manager.php
│ │ │ └── Issue5989Person.php
│ │ ├── Issue9300/
│ │ │ ├── Issue9300Child.php
│ │ │ └── Issue9300Parent.php
│ │ ├── JoinedInheritanceType/
│ │ │ ├── AnotherChildClass.php
│ │ │ ├── ChildClass.php
│ │ │ └── RootClass.php
│ │ ├── Legacy/
│ │ │ ├── LegacyArticle.php
│ │ │ ├── LegacyCar.php
│ │ │ ├── LegacyUser.php
│ │ │ └── LegacyUserReference.php
│ │ ├── ManyToManyPersister/
│ │ │ ├── ChildClass.php
│ │ │ ├── OtherParentClass.php
│ │ │ └── ParentClass.php
│ │ ├── MixedToOneIdentity/
│ │ │ ├── CompositeToOneKeyState.php
│ │ │ └── Country.php
│ │ ├── Navigation/
│ │ │ ├── NavCountry.php
│ │ │ ├── NavPhotos.php
│ │ │ ├── NavPointOfInterest.php
│ │ │ ├── NavTour.php
│ │ │ └── NavUser.php
│ │ ├── NonPublicSchemaJoins/
│ │ │ └── User.php
│ │ ├── NullDefault/
│ │ │ └── NullDefaultColumn.php
│ │ ├── OneToOneInverseSideLoad/
│ │ │ ├── InverseSide.php
│ │ │ └── OwningSide.php
│ │ ├── OneToOneInverseSideWithAssociativeIdLoad/
│ │ │ ├── InverseSide.php
│ │ │ ├── InverseSideIdTarget.php
│ │ │ └── OwningSide.php
│ │ ├── OneToOneSingleTableInheritance/
│ │ │ ├── Cat.php
│ │ │ ├── LitterBox.php
│ │ │ └── Pet.php
│ │ ├── Pagination/
│ │ │ ├── Company.php
│ │ │ ├── Department.php
│ │ │ ├── Logo.php
│ │ │ ├── User.php
│ │ │ └── User1.php
│ │ ├── PersistentObject/
│ │ │ ├── PersistentCollectionContent.php
│ │ │ ├── PersistentCollectionHolder.php
│ │ │ └── PersistentEntity.php
│ │ ├── Project/
│ │ │ ├── Project.php
│ │ │ ├── ProjectId.php
│ │ │ ├── ProjectInvalidMapping.php
│ │ │ └── ProjectName.php
│ │ ├── PropertyHooks/
│ │ │ ├── MappingVirtualProperty.php
│ │ │ └── User.php
│ │ ├── Quote/
│ │ │ ├── Address.php
│ │ │ ├── City.php
│ │ │ ├── FullAddress.php
│ │ │ ├── Group.php
│ │ │ ├── NumericEntity.php
│ │ │ ├── Phone.php
│ │ │ └── User.php
│ │ ├── ReadonlyProperties/
│ │ │ ├── Author.php
│ │ │ ├── Book.php
│ │ │ └── SimpleBook.php
│ │ ├── Reflection/
│ │ │ ├── AbstractEmbeddable.php
│ │ │ ├── ArrayObjectExtendingClass.php
│ │ │ ├── ClassWithMixedProperties.php
│ │ │ ├── ConcreteEmbeddable.php
│ │ │ └── ParentClass.php
│ │ ├── Routing/
│ │ │ ├── RoutingLeg.php
│ │ │ ├── RoutingLocation.php
│ │ │ ├── RoutingRoute.php
│ │ │ └── RoutingRouteBooking.php
│ │ ├── StockExchange/
│ │ │ ├── Bond.php
│ │ │ ├── Market.php
│ │ │ └── Stock.php
│ │ ├── Taxi/
│ │ │ ├── Car.php
│ │ │ ├── Driver.php
│ │ │ ├── PaidRide.php
│ │ │ └── Ride.php
│ │ ├── Tweet/
│ │ │ ├── Tweet.php
│ │ │ ├── User.php
│ │ │ └── UserList.php
│ │ ├── TypedProperties/
│ │ │ ├── Contact.php
│ │ │ ├── UserTyped.php
│ │ │ └── UserTypedWithCustomTypedField.php
│ │ ├── Upsertable/
│ │ │ ├── Insertable.php
│ │ │ └── Updatable.php
│ │ ├── ValueConversionType/
│ │ │ ├── AuxiliaryEntity.php
│ │ │ ├── InversedManyToManyCompositeIdEntity.php
│ │ │ ├── InversedManyToManyCompositeIdForeignKeyEntity.php
│ │ │ ├── InversedManyToManyEntity.php
│ │ │ ├── InversedManyToManyExtraLazyEntity.php
│ │ │ ├── InversedOneToManyCompositeIdEntity.php
│ │ │ ├── InversedOneToManyCompositeIdForeignKeyEntity.php
│ │ │ ├── InversedOneToManyEntity.php
│ │ │ ├── InversedOneToManyExtraLazyEntity.php
│ │ │ ├── InversedOneToOneCompositeIdEntity.php
│ │ │ ├── InversedOneToOneCompositeIdForeignKeyEntity.php
│ │ │ ├── InversedOneToOneEntity.php
│ │ │ ├── OwningManyToManyCompositeIdEntity.php
│ │ │ ├── OwningManyToManyCompositeIdForeignKeyEntity.php
│ │ │ ├── OwningManyToManyEntity.php
│ │ │ ├── OwningManyToManyExtraLazyEntity.php
│ │ │ ├── OwningManyToOneCompositeIdEntity.php
│ │ │ ├── OwningManyToOneCompositeIdForeignKeyEntity.php
│ │ │ ├── OwningManyToOneEntity.php
│ │ │ ├── OwningManyToOneExtraLazyEntity.php
│ │ │ ├── OwningManyToOneIdForeignKeyEntity.php
│ │ │ ├── OwningOneToOneCompositeIdEntity.php
│ │ │ ├── OwningOneToOneCompositeIdForeignKeyEntity.php
│ │ │ └── OwningOneToOneEntity.php
│ │ ├── ValueObjects/
│ │ │ ├── Name.php
│ │ │ └── Person.php
│ │ ├── VersionedManyToOne/
│ │ │ ├── Article.php
│ │ │ └── Category.php
│ │ └── VersionedOneToOne/
│ │ ├── FirstRelatedEntity.php
│ │ └── SecondRelatedEntity.php
│ ├── ORM/
│ │ ├── AbstractQueryTest.php
│ │ ├── Cache/
│ │ │ ├── CacheConfigTest.php
│ │ │ ├── CacheKeyTest.php
│ │ │ ├── CacheLoggerChainTest.php
│ │ │ ├── DefaultCacheFactoryTest.php
│ │ │ ├── DefaultCacheTest.php
│ │ │ ├── DefaultCollectionHydratorTest.php
│ │ │ ├── DefaultEntityHydratorTest.php
│ │ │ ├── DefaultQueryCacheTest.php
│ │ │ ├── DefaultRegionTest.php
│ │ │ ├── FileLockRegionTest.php
│ │ │ ├── Persister/
│ │ │ │ ├── Collection/
│ │ │ │ │ ├── CollectionPersisterTestCase.php
│ │ │ │ │ ├── NonStrictReadWriteCachedCollectionPersisterTest.php
│ │ │ │ │ ├── ReadOnlyCachedCollectionPersisterTest.php
│ │ │ │ │ └── ReadWriteCachedCollectionPersisterTest.php
│ │ │ │ └── Entity/
│ │ │ │ ├── EntityPersisterTestCase.php
│ │ │ │ ├── NonStrictReadWriteCachedEntityPersisterTest.php
│ │ │ │ ├── ReadOnlyCachedEntityPersisterTest.php
│ │ │ │ └── ReadWriteCachedEntityPersisterTest.php
│ │ │ ├── RegionTestCase.php
│ │ │ └── StatisticsCacheLoggerTest.php
│ │ ├── ConfigurationTest.php
│ │ ├── Entity/
│ │ │ └── ConstructorTest.php
│ │ ├── EntityManagerTest.php
│ │ ├── EntityNotFoundExceptionTest.php
│ │ ├── Event/
│ │ │ └── OnClassMetadataNotFoundEventArgsTest.php
│ │ ├── Functional/
│ │ │ ├── AbstractFetchEagerTest.php
│ │ │ ├── AbstractManyToManyAssociationTestCase.php
│ │ │ ├── AdvancedAssociationTest.php
│ │ │ ├── AdvancedDqlQueryTest.php
│ │ │ ├── BasicFunctionalTest.php
│ │ │ ├── CascadeRemoveOrderTest.php
│ │ │ ├── ClassTableInheritanceSecondTest.php
│ │ │ ├── ClassTableInheritanceTest.php
│ │ │ ├── ClearEventTest.php
│ │ │ ├── CompositeKeyRelationsTest.php
│ │ │ ├── CompositePrimaryKeyTest.php
│ │ │ ├── CompositePrimaryKeyWithAssociationsTest.php
│ │ │ ├── CustomFunctionsTest.php
│ │ │ ├── CustomIdObjectTypeTest.php
│ │ │ ├── DatabaseDriverTest.php
│ │ │ ├── DatabaseDriverTestCase.php
│ │ │ ├── DefaultTimeExpressionTest.php
│ │ │ ├── DefaultTimeExpressionXmlTest.php
│ │ │ ├── DefaultValuesTest.php
│ │ │ ├── DetachedEntityTest.php
│ │ │ ├── EagerFetchCollectionTest.php
│ │ │ ├── EagerFetchOneToManyWithCompositeKeyTest.php
│ │ │ ├── EntityListenersTest.php
│ │ │ ├── EntityRepositoryCriteriaTest.php
│ │ │ ├── EntityRepositoryTest.php
│ │ │ ├── EnumTest.php
│ │ │ ├── ExtraLazyCollectionTest.php
│ │ │ ├── FlushEventTest.php
│ │ │ ├── GH7877Test.php
│ │ │ ├── HydrationCacheTest.php
│ │ │ ├── IdentityMapTest.php
│ │ │ ├── IndexByAssociationTest.php
│ │ │ ├── InsertableUpdatableTest.php
│ │ │ ├── InvalidMappingDefinitionTest.php
│ │ │ ├── JoinedTableCompositeKeyTest.php
│ │ │ ├── LifecycleCallbackTest.php
│ │ │ ├── Locking/
│ │ │ │ ├── GearmanLockTest.php
│ │ │ │ ├── LockAgentWorker.php
│ │ │ │ ├── LockTest.php
│ │ │ │ └── OptimisticTest.php
│ │ │ ├── ManyToManyBasicAssociationTest.php
│ │ │ ├── ManyToManyBidirectionalAssociationTest.php
│ │ │ ├── ManyToManyEventTest.php
│ │ │ ├── ManyToManySelfReferentialAssociationTest.php
│ │ │ ├── ManyToManyUnidirectionalAssociationTest.php
│ │ │ ├── MappedSuperclassTest.php
│ │ │ ├── NativeQueryTest.php
│ │ │ ├── NewOperatorTest.php
│ │ │ ├── OneToManyBidirectionalAssociationTest.php
│ │ │ ├── OneToManyOrphanRemovalTest.php
│ │ │ ├── OneToManySelfReferentialAssociationTest.php
│ │ │ ├── OneToManyUnidirectionalAssociationTest.php
│ │ │ ├── OneToOneBidirectionalAssociationTest.php
│ │ │ ├── OneToOneEagerLoadingTest.php
│ │ │ ├── OneToOneInverseSideLoadAfterDqlQueryTest.php
│ │ │ ├── OneToOneInverseSideWithAssociativeIdLoadAfterDqlQueryTest.php
│ │ │ ├── OneToOneOrphanRemovalTest.php
│ │ │ ├── OneToOneSelfReferentialAssociationTest.php
│ │ │ ├── OneToOneSingleTableInheritanceTest.php
│ │ │ ├── OneToOneUnidirectionalAssociationTest.php
│ │ │ ├── OrderedCollectionTest.php
│ │ │ ├── OrderedJoinedTableInheritanceCollectionTest.php
│ │ │ ├── PaginationTest.php
│ │ │ ├── ParserResultSerializationTest.php
│ │ │ ├── ParserResults/
│ │ │ │ └── single_select_2_17_0.txt
│ │ │ ├── PersistentCollectionCriteriaTest.php
│ │ │ ├── PersistentCollectionTest.php
│ │ │ ├── PostFlushEventTest.php
│ │ │ ├── PostLoadEventTest.php
│ │ │ ├── PrePersistEventTest.php
│ │ │ ├── PropertyHooksTest.php
│ │ │ ├── ProxiesLikeEntitiesTest.php
│ │ │ ├── QueryBuilderParenthesisTest.php
│ │ │ ├── QueryCacheTest.php
│ │ │ ├── QueryDqlFunctionTest.php
│ │ │ ├── QueryIterableTest.php
│ │ │ ├── QueryParameterTest.php
│ │ │ ├── QueryTest.php
│ │ │ ├── ReadOnlyTest.php
│ │ │ ├── ReadonlyPropertiesTest.php
│ │ │ ├── ReferenceProxyTest.php
│ │ │ ├── ResultCacheTest.php
│ │ │ ├── SQLFilterTest.php
│ │ │ ├── SchemaTool/
│ │ │ │ ├── CompanySchemaTest.php
│ │ │ │ ├── DBAL483Test.php
│ │ │ │ ├── DDC214Test.php
│ │ │ │ ├── MySqlSchemaToolTest.php
│ │ │ │ └── PostgreSqlSchemaToolTest.php
│ │ │ ├── SchemaValidatorTest.php
│ │ │ ├── SecondLevelCacheCompositePrimaryKeyTest.php
│ │ │ ├── SecondLevelCacheCompositePrimaryKeyWithAssociationsTest.php
│ │ │ ├── SecondLevelCacheConcurrentTest.php
│ │ │ ├── SecondLevelCacheCountQueriesTest.php
│ │ │ ├── SecondLevelCacheCriteriaTest.php
│ │ │ ├── SecondLevelCacheExtraLazyCollectionTest.php
│ │ │ ├── SecondLevelCacheFunctionalTestCase.php
│ │ │ ├── SecondLevelCacheJoinTableInheritanceTest.php
│ │ │ ├── SecondLevelCacheManyToManyTest.php
│ │ │ ├── SecondLevelCacheManyToOneTest.php
│ │ │ ├── SecondLevelCacheOneToManyTest.php
│ │ │ ├── SecondLevelCacheOneToOneTest.php
│ │ │ ├── SecondLevelCacheQueryCacheTest.php
│ │ │ ├── SecondLevelCacheRepositoryTest.php
│ │ │ ├── SecondLevelCacheSingleTableInheritanceTest.php
│ │ │ ├── SecondLevelCacheTest.php
│ │ │ ├── SequenceGeneratorTest.php
│ │ │ ├── SingleTableCompositeKeyTest.php
│ │ │ ├── SingleTableInheritanceTest.php
│ │ │ ├── StandardEntityPersisterTest.php
│ │ │ ├── Ticket/
│ │ │ │ ├── DDC1040Test.php
│ │ │ │ ├── DDC1041Test.php
│ │ │ │ ├── DDC1043Test.php
│ │ │ │ ├── DDC1080Test.php
│ │ │ │ ├── DDC1113Test.php
│ │ │ │ ├── DDC1129Test.php
│ │ │ │ ├── DDC1163Test.php
│ │ │ │ ├── DDC117Test.php
│ │ │ │ ├── DDC1181Test.php
│ │ │ │ ├── DDC1193Test.php
│ │ │ │ ├── DDC1209Test.php
│ │ │ │ ├── DDC1225Test.php
│ │ │ │ ├── DDC1228Test.php
│ │ │ │ ├── DDC1238Test.php
│ │ │ │ ├── DDC1250Test.php
│ │ │ │ ├── DDC1300Test.php
│ │ │ │ ├── DDC1301Test.php
│ │ │ │ ├── DDC1306Test.php
│ │ │ │ ├── DDC1335Test.php
│ │ │ │ ├── DDC1400Test.php
│ │ │ │ ├── DDC142Test.php
│ │ │ │ ├── DDC1430Test.php
│ │ │ │ ├── DDC1436Test.php
│ │ │ │ ├── DDC144Test.php
│ │ │ │ ├── DDC1452Test.php
│ │ │ │ ├── DDC1454Test.php
│ │ │ │ ├── DDC1458Test.php
│ │ │ │ ├── DDC1461Test.php
│ │ │ │ ├── DDC1514Test.php
│ │ │ │ ├── DDC1515Test.php
│ │ │ │ ├── DDC1526Test.php
│ │ │ │ ├── DDC1545Test.php
│ │ │ │ ├── DDC1548Test.php
│ │ │ │ ├── DDC1595Test.php
│ │ │ │ ├── DDC163Test.php
│ │ │ │ ├── DDC1643Test.php
│ │ │ │ ├── DDC1654Test.php
│ │ │ │ ├── DDC1655Test.php
│ │ │ │ ├── DDC1666Test.php
│ │ │ │ ├── DDC1685Test.php
│ │ │ │ ├── DDC168Test.php
│ │ │ │ ├── DDC1695Test.php
│ │ │ │ ├── DDC1707Test.php
│ │ │ │ ├── DDC1719Test.php
│ │ │ │ ├── DDC1757Test.php
│ │ │ │ ├── DDC1778Test.php
│ │ │ │ ├── DDC1787Test.php
│ │ │ │ ├── DDC1843Test.php
│ │ │ │ ├── DDC1884Test.php
│ │ │ │ ├── DDC1885Test.php
│ │ │ │ ├── DDC1918Test.php
│ │ │ │ ├── DDC1925Test.php
│ │ │ │ ├── DDC192Test.php
│ │ │ │ ├── DDC1995Test.php
│ │ │ │ ├── DDC1998Test.php
│ │ │ │ ├── DDC199Test.php
│ │ │ │ ├── DDC2012Test.php
│ │ │ │ ├── DDC2074Test.php
│ │ │ │ ├── DDC2084Test.php
│ │ │ │ ├── DDC2090Test.php
│ │ │ │ ├── DDC2106Test.php
│ │ │ │ ├── DDC211Test.php
│ │ │ │ ├── DDC2138Test.php
│ │ │ │ ├── DDC2175Test.php
│ │ │ │ ├── DDC2182Test.php
│ │ │ │ ├── DDC2214Test.php
│ │ │ │ ├── DDC2224Test.php
│ │ │ │ ├── DDC2252Test.php
│ │ │ │ ├── DDC2306Test.php
│ │ │ │ ├── DDC2346Test.php
│ │ │ │ ├── DDC2350Test.php
│ │ │ │ ├── DDC2359Test.php
│ │ │ │ ├── DDC237Test.php
│ │ │ │ ├── DDC2387Test.php
│ │ │ │ ├── DDC2415Test.php
│ │ │ │ ├── DDC2494Test.php
│ │ │ │ ├── DDC2519Test.php
│ │ │ │ ├── DDC2575Test.php
│ │ │ │ ├── DDC2579Test.php
│ │ │ │ ├── DDC258Test.php
│ │ │ │ ├── DDC2602Test.php
│ │ │ │ ├── DDC2655Test.php
│ │ │ │ ├── DDC2660Test.php
│ │ │ │ ├── DDC2692Test.php
│ │ │ │ ├── DDC2759Test.php
│ │ │ │ ├── DDC2775Test.php
│ │ │ │ ├── DDC2780Test.php
│ │ │ │ ├── DDC2790Test.php
│ │ │ │ ├── DDC279Test.php
│ │ │ │ ├── DDC2825Test.php
│ │ │ │ ├── DDC2862Test.php
│ │ │ │ ├── DDC2895Test.php
│ │ │ │ ├── DDC2931Test.php
│ │ │ │ ├── DDC2943Test.php
│ │ │ │ ├── DDC2984Test.php
│ │ │ │ ├── DDC2996Test.php
│ │ │ │ ├── DDC3033Test.php
│ │ │ │ ├── DDC3042Test.php
│ │ │ │ ├── DDC3068Test.php
│ │ │ │ ├── DDC309Test.php
│ │ │ │ ├── DDC3103Test.php
│ │ │ │ ├── DDC3123Test.php
│ │ │ │ ├── DDC3160Test.php
│ │ │ │ ├── DDC3170Test.php
│ │ │ │ ├── DDC3192Test.php
│ │ │ │ ├── DDC3223Test.php
│ │ │ │ ├── DDC3300Test.php
│ │ │ │ ├── DDC3303Test.php
│ │ │ │ ├── DDC331Test.php
│ │ │ │ ├── DDC3330Test.php
│ │ │ │ ├── DDC3346Test.php
│ │ │ │ ├── DDC345Test.php
│ │ │ │ ├── DDC353Test.php
│ │ │ │ ├── DDC3582Test.php
│ │ │ │ ├── DDC3597Test.php
│ │ │ │ ├── DDC3634Test.php
│ │ │ │ ├── DDC3644Test.php
│ │ │ │ ├── DDC3719Test.php
│ │ │ │ ├── DDC371Test.php
│ │ │ │ ├── DDC3785Test.php
│ │ │ │ ├── DDC381Test.php
│ │ │ │ ├── DDC3967Test.php
│ │ │ │ ├── DDC4003Test.php
│ │ │ │ ├── DDC4024Test.php
│ │ │ │ ├── DDC422Test.php
│ │ │ │ ├── DDC425Test.php
│ │ │ │ ├── DDC440Test.php
│ │ │ │ ├── DDC444Test.php
│ │ │ │ ├── DDC448Test.php
│ │ │ │ ├── DDC493Test.php
│ │ │ │ ├── DDC512Test.php
│ │ │ │ ├── DDC513Test.php
│ │ │ │ ├── DDC522Test.php
│ │ │ │ ├── DDC531Test.php
│ │ │ │ ├── DDC5684Test.php
│ │ │ │ ├── DDC588Test.php
│ │ │ │ ├── DDC599Test.php
│ │ │ │ ├── DDC618Test.php
│ │ │ │ ├── DDC6303Test.php
│ │ │ │ ├── DDC633Test.php
│ │ │ │ ├── DDC6460Test.php
│ │ │ │ ├── DDC6558Test.php
│ │ │ │ ├── DDC656Test.php
│ │ │ │ ├── DDC6573Test.php
│ │ │ │ ├── DDC657Test.php
│ │ │ │ ├── DDC698Test.php
│ │ │ │ ├── DDC69Test.php
│ │ │ │ ├── DDC719Test.php
│ │ │ │ ├── DDC735Test.php
│ │ │ │ ├── DDC736Test.php
│ │ │ │ ├── DDC748Test.php
│ │ │ │ ├── DDC767Test.php
│ │ │ │ ├── DDC7969Test.php
│ │ │ │ ├── DDC809Test.php
│ │ │ │ ├── DDC812Test.php
│ │ │ │ ├── DDC832Test.php
│ │ │ │ ├── DDC837Test.php
│ │ │ │ ├── DDC849Test.php
│ │ │ │ ├── DDC881Test.php
│ │ │ │ ├── DDC933Test.php
│ │ │ │ ├── DDC949Test.php
│ │ │ │ ├── DDC960Test.php
│ │ │ │ ├── DDC992Test.php
│ │ │ │ ├── GH10049/
│ │ │ │ │ ├── GH10049Test.php
│ │ │ │ │ ├── ReadOnlyPropertyInheritor.php
│ │ │ │ │ └── ReadOnlyPropertyOwner.php
│ │ │ │ ├── GH10132Test.php
│ │ │ │ ├── GH10288Test.php
│ │ │ │ ├── GH10334Test.php
│ │ │ │ ├── GH10336Test.php
│ │ │ │ ├── GH10348Test.php
│ │ │ │ ├── GH10387Test.php
│ │ │ │ ├── GH10450Test.php
│ │ │ │ ├── GH10454Test.php
│ │ │ │ ├── GH10462Test.php
│ │ │ │ ├── GH10473Test.php
│ │ │ │ ├── GH10531Test.php
│ │ │ │ ├── GH10532Test.php
│ │ │ │ ├── GH10566Test.php
│ │ │ │ ├── GH10625Test.php
│ │ │ │ ├── GH10661/
│ │ │ │ │ ├── GH10661Test.php
│ │ │ │ │ ├── InvalidChildEntity.php
│ │ │ │ │ └── InvalidEntity.php
│ │ │ │ ├── GH10747Test.php
│ │ │ │ ├── GH10752Test.php
│ │ │ │ ├── GH10808Test.php
│ │ │ │ ├── GH10869Test.php
│ │ │ │ ├── GH10880Test.php
│ │ │ │ ├── GH10889Test.php
│ │ │ │ ├── GH10912Test.php
│ │ │ │ ├── GH10913Test.php
│ │ │ │ ├── GH10927Test.php
│ │ │ │ ├── GH11017/
│ │ │ │ │ ├── GH11017Entity.php
│ │ │ │ │ ├── GH11017Enum.php
│ │ │ │ │ └── GH11017Test.php
│ │ │ │ ├── GH11037/
│ │ │ │ │ ├── EntityStatus.php
│ │ │ │ │ ├── GH11037Test.php
│ │ │ │ │ ├── IntEntityStatus.php
│ │ │ │ │ ├── InvalidEntityWithTypedEnum.php
│ │ │ │ │ ├── StringEntityStatus.php
│ │ │ │ │ └── ValidEntityWithTypedEnum.php
│ │ │ │ ├── GH11058Test.php
│ │ │ │ ├── GH11072/
│ │ │ │ │ ├── GH11072EntityAdvanced.php
│ │ │ │ │ ├── GH11072EntityBasic.php
│ │ │ │ │ └── GH11072Test.php
│ │ │ │ ├── GH11112Test.php
│ │ │ │ ├── GH11135Test.php
│ │ │ │ ├── GH11149/
│ │ │ │ │ ├── EagerProduct.php
│ │ │ │ │ ├── EagerProductTranslation.php
│ │ │ │ │ ├── GH11149Test.php
│ │ │ │ │ └── Locale.php
│ │ │ │ ├── GH11163Test.php
│ │ │ │ ├── GH11199Test.php
│ │ │ │ ├── GH11341Test.php
│ │ │ │ ├── GH11386/
│ │ │ │ │ ├── GH11386EntityCart.php
│ │ │ │ │ ├── GH11386EntityCustomer.php
│ │ │ │ │ ├── GH11386EnumType.php
│ │ │ │ │ └── GH11386Test.php
│ │ │ │ ├── GH11487Test.php
│ │ │ │ ├── GH11500Test.php
│ │ │ │ ├── GH11501Test.php
│ │ │ │ ├── GH11524Test.php
│ │ │ │ ├── GH11982Test.php
│ │ │ │ ├── GH12166/
│ │ │ │ │ ├── GH12166Test.php
│ │ │ │ │ └── LazyEntityWithReadonlyId.php
│ │ │ │ ├── GH12174Test.php
│ │ │ │ ├── GH12183Test.php
│ │ │ │ ├── GH12254Test.php
│ │ │ │ ├── GH2947Test.php
│ │ │ │ ├── GH5562Test.php
│ │ │ │ ├── GH5742Test.php
│ │ │ │ ├── GH5762Test.php
│ │ │ │ ├── GH5804Test.php
│ │ │ │ ├── GH5887Test.php
│ │ │ │ ├── GH5988Test.php
│ │ │ │ ├── GH5998Test.php
│ │ │ │ ├── GH6029Test.php
│ │ │ │ ├── GH6123Test.php
│ │ │ │ ├── GH6141Test.php
│ │ │ │ ├── GH6217Test.php
│ │ │ │ ├── GH6362Test.php
│ │ │ │ ├── GH6394Test.php
│ │ │ │ ├── GH6402Test.php
│ │ │ │ ├── GH6464Test.php
│ │ │ │ ├── GH6499OneToManyRelationshipTest.php
│ │ │ │ ├── GH6499OneToOneRelationshipTest.php
│ │ │ │ ├── GH6499Test.php
│ │ │ │ ├── GH6531Test.php
│ │ │ │ ├── GH6682Test.php
│ │ │ │ ├── GH6699Test.php
│ │ │ │ ├── GH6740Test.php
│ │ │ │ ├── GH6823Test.php
│ │ │ │ ├── GH6937Test.php
│ │ │ │ ├── GH7006Test.php
│ │ │ │ ├── GH7012Test.php
│ │ │ │ ├── GH7062Test.php
│ │ │ │ ├── GH7067Test.php
│ │ │ │ ├── GH7068Test.php
│ │ │ │ ├── GH7079Test.php
│ │ │ │ ├── GH7180Test.php
│ │ │ │ ├── GH7259Test.php
│ │ │ │ ├── GH7286Test.php
│ │ │ │ ├── GH7366Test.php
│ │ │ │ ├── GH7496WithToIterableTest.php
│ │ │ │ ├── GH7505Test.php
│ │ │ │ ├── GH7512Test.php
│ │ │ │ ├── GH7629Test.php
│ │ │ │ ├── GH7661Test.php
│ │ │ │ ├── GH7684Test.php
│ │ │ │ ├── GH7717Test.php
│ │ │ │ ├── GH7735Test.php
│ │ │ │ ├── GH7737Test.php
│ │ │ │ ├── GH7761Test.php
│ │ │ │ ├── GH7767Test.php
│ │ │ │ ├── GH7820Test.php
│ │ │ │ ├── GH7829Test.php
│ │ │ │ ├── GH7836Test.php
│ │ │ │ ├── GH7864Test.php
│ │ │ │ ├── GH7869Test.php
│ │ │ │ ├── GH7875Test.php
│ │ │ │ ├── GH7941Test.php
│ │ │ │ ├── GH8055Test.php
│ │ │ │ ├── GH8061Test.php
│ │ │ │ ├── GH8127Test.php
│ │ │ │ ├── GH8217Test.php
│ │ │ │ ├── GH8415Test.php
│ │ │ │ ├── GH8415ToManyAssociationTest.php
│ │ │ │ ├── GH8443Test.php
│ │ │ │ ├── GH8499Test.php
│ │ │ │ ├── GH8663Test.php
│ │ │ │ ├── GH8914Test.php
│ │ │ │ ├── GH9027Test.php
│ │ │ │ ├── GH9109Test.php
│ │ │ │ ├── GH9192Test.php
│ │ │ │ ├── GH9230Test.php
│ │ │ │ ├── GH9335Test.php
│ │ │ │ ├── GH9467/
│ │ │ │ │ ├── GH9467Test.php
│ │ │ │ │ ├── JoinedInheritanceChild.php
│ │ │ │ │ ├── JoinedInheritanceNonInsertableColumn.php
│ │ │ │ │ ├── JoinedInheritanceNonUpdatableColumn.php
│ │ │ │ │ ├── JoinedInheritanceNonWritableColumn.php
│ │ │ │ │ ├── JoinedInheritanceRoot.php
│ │ │ │ │ └── JoinedInheritanceWritableColumn.php
│ │ │ │ ├── GH9516Test.php
│ │ │ │ ├── GH9579Test.php
│ │ │ │ ├── GH9807Test.php
│ │ │ │ ├── Issue5989Test.php
│ │ │ │ ├── Issue9300Test.php
│ │ │ │ ├── SwitchContextWithFilter/
│ │ │ │ │ ├── AbstractTestCase.php
│ │ │ │ │ ├── ChangeFiltersTest.php
│ │ │ │ │ ├── Entity/
│ │ │ │ │ │ ├── Insurance.php
│ │ │ │ │ │ ├── Order.php
│ │ │ │ │ │ ├── Patient.php
│ │ │ │ │ │ ├── PatientInsurance.php
│ │ │ │ │ │ ├── Practice.php
│ │ │ │ │ │ ├── PrimaryPatInsurance.php
│ │ │ │ │ │ ├── SecondaryPatInsurance.php
│ │ │ │ │ │ └── User.php
│ │ │ │ │ ├── SQLFilter/
│ │ │ │ │ │ ├── CompanySQLFilter.php
│ │ │ │ │ │ └── PracticeContextSQLFilter.php
│ │ │ │ │ └── SwitchContextTest.php
│ │ │ │ ├── SwitchContextWithFilterAndIndexedRelation/
│ │ │ │ │ ├── Category.php
│ │ │ │ │ ├── CategoryTypeSQLFilter.php
│ │ │ │ │ ├── ChangeFiltersTest.php
│ │ │ │ │ └── Company.php
│ │ │ │ ├── Ticket2481Test.php
│ │ │ │ ├── Ticket4646InstanceOfAbstractTest.php
│ │ │ │ ├── Ticket4646InstanceOfMultiLevelTest.php
│ │ │ │ ├── Ticket4646InstanceOfParametricTest.php
│ │ │ │ ├── Ticket4646InstanceOfTest.php
│ │ │ │ └── Ticket4646InstanceOfWithMultipleParametersTest.php
│ │ │ ├── TypeTest.php
│ │ │ ├── TypeValueSqlTest.php
│ │ │ ├── UnitOfWorkLifecycleTest.php
│ │ │ ├── ValueConversionType/
│ │ │ │ ├── ManyToManyCompositeIdForeignKeyTest.php
│ │ │ │ ├── ManyToManyCompositeIdTest.php
│ │ │ │ ├── ManyToManyCriteriaMatchingTest.php
│ │ │ │ ├── ManyToManyExtraLazyTest.php
│ │ │ │ ├── ManyToManyTest.php
│ │ │ │ ├── OneToManyCompositeIdForeignKeyTest.php
│ │ │ │ ├── OneToManyCompositeIdTest.php
│ │ │ │ ├── OneToManyCriteriaMatchingTest.php
│ │ │ │ ├── OneToManyExtraLazyTest.php
│ │ │ │ ├── OneToManyTest.php
│ │ │ │ ├── OneToOneCompositeIdForeignKeyTest.php
│ │ │ │ ├── OneToOneCompositeIdTest.php
│ │ │ │ └── OneToOneTest.php
│ │ │ ├── ValueObjectsTest.php
│ │ │ └── VersionedOneToOneTest.php
│ │ ├── Hydration/
│ │ │ ├── AbstractHydratorTest.php
│ │ │ ├── ArrayHydratorTest.php
│ │ │ ├── CustomHydratorTest.php
│ │ │ ├── HydrationTestCase.php
│ │ │ ├── ObjectHydratorTest.php
│ │ │ ├── ResultSetMappingTest.php
│ │ │ ├── ScalarColumnHydratorTest.php
│ │ │ ├── ScalarHydratorTest.php
│ │ │ ├── SimpleObjectHydratorTest.php
│ │ │ └── SingleScalarHydratorTest.php
│ │ ├── Id/
│ │ │ ├── AssignedGeneratorTest.php
│ │ │ └── SequenceGeneratorTest.php
│ │ ├── Internal/
│ │ │ ├── HydrationCompleteHandlerTest.php
│ │ │ ├── Node.php
│ │ │ ├── StronglyConnectedComponentsTest.php
│ │ │ ├── TopologicalSortTest.php
│ │ │ └── UnitOfWork/
│ │ │ └── InsertBatchTest.php
│ │ ├── LazyCriteriaCollectionTest.php
│ │ ├── Mapping/
│ │ │ ├── AnsiQuoteStrategyTest.php
│ │ │ ├── AssociationMappingTest.php
│ │ │ ├── AttributeDriverTest.php
│ │ │ ├── AttributeReaderTest.php
│ │ │ ├── BasicInheritanceMappingTest.php
│ │ │ ├── ClassMetadataBuilderTest.php
│ │ │ ├── ClassMetadataFactoryTest.php
│ │ │ ├── ClassMetadataLoadEventTest.php
│ │ │ ├── ClassMetadataTest.php
│ │ │ ├── DefaultQuoteStrategyTest.php
│ │ │ ├── DiscriminatorColumnMappingTest.php
│ │ │ ├── EmbeddedClassMappingTest.php
│ │ │ ├── EntityListenerResolverTest.php
│ │ │ ├── FieldBuilderTest.php
│ │ │ ├── FieldMappingTest.php
│ │ │ ├── Fixtures/
│ │ │ │ └── AttributeEntityWithNestedJoinColumns.php
│ │ │ ├── InverseSideMappingTest.php
│ │ │ ├── JoinColumnMappingTest.php
│ │ │ ├── JoinTableMappingTest.php
│ │ │ ├── ManyToManyOwningSideMappingTest.php
│ │ │ ├── ManyToOneAssociationMappingTest.php
│ │ │ ├── MappingDriverTestCase.php
│ │ │ ├── NamingStrategy/
│ │ │ │ ├── CustomPascalNamingStrategy.php
│ │ │ │ └── JoinColumnClassNamingStrategy.php
│ │ │ ├── NamingStrategyTest.php
│ │ │ ├── OneToOneOwningSideMappingTest.php
│ │ │ ├── OwningSideMappingTest.php
│ │ │ ├── PropertyAccessors/
│ │ │ │ ├── EnumPropertyAccessorTest.php
│ │ │ │ ├── ObjectCastPropertyAccessorTest.php
│ │ │ │ ├── RawValuePropertyAccessorTest.php
│ │ │ │ ├── ReadOnlyAccessorTest.php
│ │ │ │ └── TypedNoDefaultPropertyAccessorTest.php
│ │ │ ├── QuoteStrategyTest.php
│ │ │ ├── ReflectionEmbeddedPropertyTest.php
│ │ │ ├── ReflectionReadonlyPropertyTest.php
│ │ │ ├── StaticPHPMappingDriverTest.php
│ │ │ ├── Symfony/
│ │ │ │ ├── DriverTestCase.php
│ │ │ │ └── XmlDriverTest.php
│ │ │ ├── TableMappingTest.php
│ │ │ ├── ToManyAssociationMappingTest.php
│ │ │ ├── TypedEnumFieldMapperTest.php
│ │ │ ├── TypedFieldMapper/
│ │ │ │ └── CustomIntAsStringTypedFieldMapper.php
│ │ │ ├── TypedFieldMapperTest.php
│ │ │ ├── XmlMappingDriverTest.php
│ │ │ ├── invalid_xml/
│ │ │ │ └── Doctrine.Tests.Models.InvalidXml.dcm.xml
│ │ │ ├── php/
│ │ │ │ ├── Doctrine.Tests.Models.Enums.Card.php
│ │ │ │ ├── Doctrine.Tests.Models.TypedProperties.UserTypedWithCustomTypedField.php
│ │ │ │ ├── Doctrine.Tests.Models.Upsertable.Insertable.php
│ │ │ │ ├── Doctrine.Tests.Models.Upsertable.Updatable.php
│ │ │ │ └── Doctrine.Tests.ORM.Mapping.GH10288EnumTypePerson.php
│ │ │ ├── xml/
│ │ │ │ ├── CatNoId.dcm.xml
│ │ │ │ ├── DDC2429Book.orm.xml
│ │ │ │ ├── DDC2429Novel.orm.xml
│ │ │ │ ├── Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.CMS.CmsUser.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Cache.City.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Company.CompanyContract.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Company.CompanyFixContract.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Company.CompanyFlexContract.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Company.CompanyFlexUltraContract.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Customer.CustomerType.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC117.DDC117Translation.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC1476.DDC1476EntityWithDefaultFieldType.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC2825.ExplicitSchemaAndTable.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC2825.SchemaAndTableInTableName.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC3293.DDC3293Address.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC3293.DDC3293User.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC3293.DDC3293UserPrefixed.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC3579.DDC3579Admin.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC3579.DDC3579User.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC5934.DDC5934BaseContract.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC5934.DDC5934Contract.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC869.DDC869ChequePayment.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC869.DDC869CreditCardPayment.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC869.DDC869Payment.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC889.DDC889Class.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC889.DDC889Entity.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC889.DDC889SuperClass.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC964.DDC964Admin.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC964.DDC964Guest.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.DDC964.DDC964User.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Enums.Card.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.GH7141.GH7141Article.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.GH7316.GH7316Article.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Generic.BooleanModel.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Project.Project.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Project.ProjectInvalidMapping.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.TypedProperties.UserTyped.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.TypedProperties.UserTypedWithCustomTypedField.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Upsertable.Insertable.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.Upsertable.Updatable.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.ValueObjects.Name.dcm.xml
│ │ │ │ ├── Doctrine.Tests.Models.ValueObjects.Person.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Functional.XmlLegacyTimeEntity.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Functional.XmlTimeEntity.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.Animal.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.BlogPost.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.BlogPostComment.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.CTI.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.Comment.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.DDC1170Entity.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.GH10288EnumTypePerson.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.ReservedWordInTableColumn.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.SingleTableEntityIncompleteDiscriminatorColumnMapping.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.SingleTableEntityNoDiscriminatorColumnMapping.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.User.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.UserIncorrectAttributes.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.UserIncorrectIndex.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.UserIncorrectUniqueConstraint.dcm.xml
│ │ │ │ ├── Doctrine.Tests.ORM.Mapping.UserMissingAttributes.dcm.xml
│ │ │ │ └── Doctrine.Tests.ORM.Mapping.XMLSLC.dcm.xml
│ │ │ └── yaml/
│ │ │ ├── Doctrine.Tests.Models.TypedProperties.UserTypedWithCustomTypedField.dcm.yml
│ │ │ └── Doctrine.Tests.ORM.Mapping.GH10288EnumTypePerson.dcm.yml
│ │ ├── ORMInvalidArgumentExceptionTest.php
│ │ ├── ORMSetupTest.php
│ │ ├── Performance/
│ │ │ └── SecondLevelCacheTest.php
│ │ ├── PersistentCollectionTest.php
│ │ ├── Persisters/
│ │ │ ├── BasicEntityPersisterCompositeTypeParametersTest.php
│ │ │ ├── BasicEntityPersisterCompositeTypeSqlTest.php
│ │ │ ├── BasicEntityPersisterTypeValueSqlTest.php
│ │ │ ├── BinaryIdPersisterTest.php
│ │ │ ├── Exception/
│ │ │ │ └── UnrecognizedFieldTest.php
│ │ │ └── ManyToManyPersisterTest.php
│ │ ├── Proxy/
│ │ │ └── ProxyFactoryTest.php
│ │ ├── Query/
│ │ │ ├── CustomTreeWalkersJoinTest.php
│ │ │ ├── CustomTreeWalkersTest.php
│ │ │ ├── DeleteSqlGenerationTest.php
│ │ │ ├── ExprTest.php
│ │ │ ├── FilterCollectionTest.php
│ │ │ ├── LanguageRecognitionTest.php
│ │ │ ├── LexerTest.php
│ │ │ ├── NativeQueryTest.php
│ │ │ ├── ParameterTypeInfererTest.php
│ │ │ ├── ParserResultTest.php
│ │ │ ├── ParserTest.php
│ │ │ ├── QueryExpressionVisitorTest.php
│ │ │ ├── QueryTest.php
│ │ │ ├── SelectSqlGenerationTest.php
│ │ │ ├── SqlExpressionVisitorTest.php
│ │ │ ├── SqlWalkerTest.php
│ │ │ └── UpdateSqlGenerationTest.php
│ │ ├── QueryBuilderTest.php
│ │ ├── Repository/
│ │ │ └── DefaultRepositoryFactoryTest.php
│ │ ├── Tools/
│ │ │ ├── AttachEntityListenersListenerTest.php
│ │ │ ├── Console/
│ │ │ │ ├── Command/
│ │ │ │ │ ├── ClearCacheCollectionRegionCommandTest.php
│ │ │ │ │ ├── ClearCacheEntityRegionCommandTest.php
│ │ │ │ │ ├── ClearCacheQueryRegionCommandTest.php
│ │ │ │ │ ├── Debug/
│ │ │ │ │ │ ├── DebugEntityListenersDoctrineCommandTest.php
│ │ │ │ │ │ ├── DebugEventManagerDoctrineCommandTest.php
│ │ │ │ │ │ └── Fixtures/
│ │ │ │ │ │ ├── BarListener.php
│ │ │ │ │ │ ├── BazListener.php
│ │ │ │ │ │ └── FooListener.php
│ │ │ │ │ ├── InfoCommandTest.php
│ │ │ │ │ ├── MappingDescribeCommandTest.php
│ │ │ │ │ ├── RunDqlCommandTest.php
│ │ │ │ │ ├── SchemaTool/
│ │ │ │ │ │ ├── CommandTestCase.php
│ │ │ │ │ │ ├── CreateCommandTest.php
│ │ │ │ │ │ ├── DropCommandTest.php
│ │ │ │ │ │ └── Models/
│ │ │ │ │ │ └── Keyboard.php
│ │ │ │ │ └── ValidateSchemaCommandTest.php
│ │ │ │ ├── ConsoleRunnerTest.php
│ │ │ │ └── MetadataFilterTest.php
│ │ │ ├── DebugTest.php
│ │ │ ├── Pagination/
│ │ │ │ ├── CountOutputWalkerTest.php
│ │ │ │ ├── CountWalkerTest.php
│ │ │ │ ├── LimitSubqueryOutputWalkerTest.php
│ │ │ │ ├── LimitSubqueryWalkerTest.php
│ │ │ │ ├── PaginationTestCase.php
│ │ │ │ ├── PaginatorTest.php
│ │ │ │ ├── RootTypeWalkerTest.php
│ │ │ │ └── WhereInWalkerTest.php
│ │ │ ├── ResolveTargetEntityListenerTest.php
│ │ │ ├── SchemaToolTest.php
│ │ │ ├── SchemaValidatorTest.php
│ │ │ └── TestAsset/
│ │ │ ├── ChildClass.php
│ │ │ ├── ChildWithSameAttributesClass.php
│ │ │ └── ParentClass.php
│ │ ├── UnitOfWorkTest.php
│ │ └── Utility/
│ │ ├── HierarchyDiscriminatorResolverTest.php
│ │ ├── IdentifierFlattenerEnumIdTest.php
│ │ └── IdentifierFlattenerTest.php
│ ├── OrmFunctionalTestCase.php
│ ├── OrmTestCase.php
│ ├── Proxy/
│ │ └── AutoloaderTest.php
│ ├── TestInit.php
│ └── TestUtil.php
└── dbproperties.xml.dev
================================================
FILE CONTENTS
================================================
================================================
FILE: .doctrine-project.json
================================================
{
"active": true,
"name": "Object Relational Mapper",
"shortName": "ORM",
"slug": "orm",
"docsSlug": "doctrine-orm",
"versions": [
{
"name": "4.0",
"branchName": "4.0.x",
"slug": "latest",
"upcoming": true
},
{
"name": "3.7",
"branchName": "3.7.x",
"slug": "3.7",
"upcoming": true
},
{
"name": "3.6",
"branchName": "3.6.x",
"slug": "3.6",
"current": true
},
{
"name": "2.21",
"branchName": "2.21.x",
"slug": "2.21",
"upcoming": true
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"maintained": true
},
{
"name": "2.19",
"slug": "2.19",
"maintained": false
},
{
"name": "2.18",
"slug": "2.18",
"maintained": false
},
{
"name": "2.17",
"slug": "2.17",
"maintained": false
},
{
"name": "2.16",
"slug": "2.16",
"maintained": false
},
{
"name": "2.15",
"slug": "2.15",
"maintained": false
},
{
"name": "2.14",
"slug": "2.14",
"maintained": false
}
]
}
================================================
FILE: .gitattributes
================================================
/.github export-ignore
/ci export-ignore
/docs export-ignore
/tests export-ignore
/tools export-ignore
.doctrine-project.json export-ignore
.gitattributes export-ignore
.gitignore export-ignore
build.properties export-ignore
build.properties.dev export-ignore
build.xml export-ignore
CONTRIBUTING.md export-ignore
phpunit.xml.dist export-ignore
phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpstan-dbal3.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/Failing_Test.md
================================================
---
name: 🐞 Failing Test
about: You found a bug and have a failing Unit or Functional test? 🔨
---
### Failing Test
| Q | A
|------------ | ------
| BC Break | yes/no
| Version | x.y.z
#### Summary
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/Improvement.md
================================================
---
name: ⚙ Improvement
about: You have some improvement to make Doctrine better? 🎁
---
### Improvement
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/New_Feature.md
================================================
---
name: 🎉 New Feature
about: You have implemented some neat idea that you want to make part of Doctrine? 🎩
---
### New Feature
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "CI"
target-branch: "2.20.x"
================================================
FILE: .github/workflows/coding-standards.yml
================================================
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- src/**
- phpcs.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- src/**
- phpcs.xml.dist
- tests/**
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@13.1.0"
================================================
FILE: .github/workflows/composer-lint.yml
================================================
name: "Composer Lint"
on:
pull_request:
branches:
- "*.x"
paths:
- ".github/workflows/composer-lint.yml"
- "composer.json"
push:
branches:
- "*.x"
paths:
- ".github/workflows/composer-lint.yml"
- "composer.json"
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.1.0"
================================================
FILE: .github/workflows/continuous-integration.yml
================================================
name: "CI: PHPUnit"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- src/**
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- src/**
- tests/**
env:
fail-fast: true
jobs:
phpunit-smoke-check:
name: >
SQLite -
${{ format('PHP {0} - DBAL {1} - ext. {2} - proxy {3}',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø',
matrix.proxy || 'Ø'
) }}
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
deps:
- "highest"
stability:
- "stable"
native_lazy:
- "0"
include:
- php-version: "8.2"
dbal-version: "4@dev"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.2"
dbal-version: "4@dev"
extension: "sqlite3"
stability: "stable"
native_lazy: "0"
- php-version: "8.1"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "1"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "sqlite3"
stability: "dev"
native_lazy: "1"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Allow dev dependencies"
run: |
composer config minimum-stability dev
composer remove --no-update --dev phpbench/phpbench phpdocumentor/guides-cli
composer require --no-update symfony/console:^8 symfony/var-exporter:^8 doctrine/dbal:^4.4
composer require --dev --no-update symfony/cache:^8
if: "${{ matrix.stability == 'dev' }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Downgrade VarExporter"
run: 'composer require --no-update "symfony/var-exporter:^6.4 || ^7.4"'
if: "${{ matrix.native_lazy == '0' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Run PHPUnit with Second Level Cache and PHPUnit 10"
run: |
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
--exclude-group=performance,non-cacheable,locking_functional \
--coverage-clover=coverage-cache.xml
if: "${{ matrix.php-version == '8.1' }}"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Run PHPUnit with Second Level Cache and PHPUnit 11+"
run: |
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
--exclude-group=performance \
--exclude-group=non-cacheable \
--exclude-group=locking_functional \
--coverage-clover=coverage-cache.xml
if: "${{ matrix.php-version != '8.1' }}"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Upload coverage file"
uses: "actions/upload-artifact@v7"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-${{ matrix.native_lazy }}-coverage"
path: "coverage*.xml"
phpunit-deprecations:
name: "PHPUnit (fail on deprecations)"
runs-on: "ubuntu-24.04"
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.5"
extensions: "apcu, pdo, sqlite3"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Allow dev dependencies"
run: composer config minimum-stability dev
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "highest"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite3.xml --fail-on-deprecation"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
ENABLE_NATIVE_LAZY_OBJECTS: 1
phpunit-postgres:
name: >
${{ format('PostgreSQL {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.postgres-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
postgres-version:
- "17"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.2"
dbal-version: "4@dev"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "3.7"
postgres-version: "9.6"
extension: pdo_pgsql
services:
postgres:
image: "postgres:${{ matrix.postgres-version }}"
env:
POSTGRES_PASSWORD: "postgres"
options: >-
--health-cmd "pg_isready"
ports:
- "5432:5432"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "pgsql pdo_pgsql"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
phpunit-mariadb:
name: >
${{ format('MariaDB {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.mariadb-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "4@dev"
mariadb-version:
- "11.4"
extension:
- "mysqli"
- "pdo_mysql"
services:
mariadb:
image: "mariadb:${{ matrix.mariadb-version }}"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
MARIADB_DATABASE: "doctrine_tests"
options: >-
--health-cmd "healthcheck.sh --connect --innodb_initialized"
ports:
- "3306:3306"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
phpunit-mysql:
name: >
${{ format('MySQL {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.mysql-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
mysql-version:
- "5.7"
- "8.0"
extension:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
services:
mysql:
image: "mysql:${{ matrix.mysql-version }}"
options: >-
--health-cmd "mysqladmin ping --silent"
-e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-e MYSQL_DATABASE=doctrine_tests
ports:
- "3306:3306"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
- name: "Run PHPUnit with Second Level Cache and PHPUnit 10"
run: |
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
--exclude-group=performance,non-cacheable,locking_functional \
--coverage-clover=coverage-no-cache.xml"
if: "${{ matrix.php-version == '8.1' }}"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Run PHPUnit with Second Level Cache and PHPUnit 11+"
run: |
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
--exclude-group=performance \
--exclude-group=non-cacheable \
--exclude-group=locking_functional \
--coverage-clover=coverage-no-cache.xml
if: "${{ matrix.php-version != '8.1' }}"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-22.04"
# Only run on PRs from forks
if: "github.event.pull_request.head.repo.full_name != github.repository"
needs:
- "phpunit-smoke-check"
- "phpunit-postgres"
- "phpunit-mariadb"
- "phpunit-mysql"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v8"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v5"
with:
directory: reports
env:
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
================================================
FILE: .github/workflows/documentation.yml
================================================
name: "Documentation"
on:
pull_request:
branches:
- "*.x"
paths:
- ".github/workflows/documentation.yml"
- "docs/**"
push:
branches:
- "*.x"
paths:
- ".github/workflows/documentation.yml"
- "docs/**"
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@13.1.0"
================================================
FILE: .github/workflows/phpbench.yml
================================================
name: "Performance benchmark"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- src/**
- phpbench.json
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- src/**
- phpbench.json
- tests/**
env:
fail-fast: true
jobs:
phpbench:
name: "PHPBench"
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"
================================================
FILE: .github/workflows/release-on-milestone-closed.yml
================================================
name: "Automatic Releases"
on:
milestone:
types:
- "closed"
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.1.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
================================================
FILE: .github/workflows/stale.yml
================================================
name: 'Close stale pull requests'
on:
schedule:
- cron: '0 3 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-pr-message: >
There hasn't been any activity on this pull request in the past 90 days, so
it has been marked as stale and it will be closed automatically if no
further activity occurs in the next 7 days.
If you want to continue working on it, please leave a comment.
close-pr-message: >
This pull request was closed due to inactivity.
days-before-stale: -1
days-before-pr-stale: 90
days-before-pr-close: 7
================================================
FILE: .github/workflows/static-analysis.yml
================================================
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- src/**
- phpstan*
- tests/StaticAnalysis/**
push:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- src/**
- phpstan*
- tests/StaticAnalysis/**
jobs:
static-analysis-phpstan:
name: Static Analysis with PHPStan
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
- name: "Checkout code"
uses: "actions/checkout@v6"
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.4"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v2
- name: Run static analysis with phpstan/phpstan
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"
================================================
FILE: .github/workflows/website-schema.yml
================================================
name: "Website config validation"
on:
pull_request:
branches:
- "*.x"
paths:
- ".doctrine-project.json"
- ".github/workflows/website-schema.yml"
push:
branches:
- "*.x"
paths:
- ".doctrine-project.json"
- ".github/workflows/website-schema.yml"
jobs:
json-validate:
name: "Validate JSON schema"
uses: "doctrine/.github/.github/workflows/website-schema.yml@7.1.0"
================================================
FILE: .gitignore
================================================
build/
logs/
reports/
dist/
download/
/.settings/
.buildpath
.project
.idea
*.iml
vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
.phpunit.cache
.phpunit.result.cache
/*.phpunit.xml
================================================
FILE: CONTRIBUTING.md
================================================
# Contribute to Doctrine
Thank you for contributing to Doctrine!
Before we can merge your Pull-Request here are some guidelines that you need to follow.
These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof.
Doctrine has [general contributing guidelines][contributor workflow], make
sure you follow them.
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
## Coding Standard
This project follows [`doctrine/coding-standard`][coding standard homepage].
You may fix many some of the issues with `vendor/bin/phpcbf`.
[coding standard homepage]: https://github.com/doctrine/coding-standard
## Unit-Tests
Please try to add a test for your pull-request.
* If you want to fix a bug or provide a reproduce case, create a test file in
``tests/Tests/ORM/Functional/Ticket`` with the name of the ticket,
``DDC1234Test.php`` for example.
* If you want to contribute new functionality add unit- or functional tests
depending on the scope of the feature.
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
It will run all the tests with an in memory SQLite database.
In order to do that, you will need a fresh copy of the ORM, and you
will have to run a composer installation in the project:
```sh
git clone git@github.com:doctrine/orm.git
cd orm
composer install
```
You will also need to enable the PHP extension that provides the SQLite driver
for PDO: `pdo_sqlite`. How to do so depends on your system, but checking that it
is enabled can universally be done with `php -m`: that command should list the
extension.
To run the testsuite against another database, copy the ``phpunit.xml.dist``
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
take a look at the ``ci/github/phpunit`` directory for some examples. Then run:
vendor/bin/phpunit -c mysql.phpunit.xml
If you do not provide these parameters, the test suite will use an in-memory
sqlite database.
Tips for creating unit tests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/orm/tree/3.0.x/tests/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## Getting merged
Please allow us time to review your pull requests. We will give our best to review
everything as fast as possible, but cannot always live up to our own expectations.
Thank you very much again for your contribution!
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
| [4.0.x][4.0] | [3.7.x][3.7] | [3.6.x][3.6] | [2.21.x][2.21] | [2.20.x][2.20] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.7 image]][3.7 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.7 coverage image]][3.7 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
Doctrine ORM is an object-relational mapper for PHP 8.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)
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
[4.0 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A4.0.x
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
[3.7 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.7.x
[3.7]: https://github.com/doctrine/orm/tree/3.7.x
[3.7 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.7.x
[3.7 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.7.x/graph/badge.svg
[3.7 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.7.x
[3.6 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.6.x
[3.6]: https://github.com/doctrine/orm/tree/3.6.x
[3.6 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.6.x
[3.6 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.6.x/graph/badge.svg
[3.6 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.6.x
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
[2.21 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.21.x
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
[2.20 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.20.x
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
================================================
FILE: SECURITY.md
================================================
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 follow our
[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting).
================================================
FILE: UPGRADE.md
================================================
Note about upgrading: Doctrine uses static and runtime mechanisms to raise
awareness about deprecated code.
- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or
Static Analysis tools (like Psalm, phpstan)
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/
# Upgrade to 3.x General Notes
We recommend you upgrade to DBAL 3 first before upgrading to ORM 3. See
the DBAL upgrade docs: https://github.com/doctrine/dbal/blob/3.10.x/UPGRADE.md
Rather than doing several major upgrades at once, we recommend you do the following:
- upgrade to DBAL 3
- deploy and monitor
- upgrade to ORM 3
- deploy and monitor
- upgrade to DBAL 4
- deploy and monitor
If you are using Symfony, the recommended minimal Doctrine Bundle version is 2.15
to run with ORM 3.
At this point, we recommend upgrading to PHP 8.4 first and then directly from
ORM 2.19 to 3.5 and up so that you can skip the lazy ghost proxy generation
and directly start using native lazy objects.
# Upgrade to 3.6
## Deprecate using string expression for default values in mappings
Using a string expression for default values in field mappings is deprecated.
Use `Doctrine\DBAL\Schema\DefaultExpression` instances instead.
Here is how to address this deprecation when mapping entities using PHP attributes:
```diff
use DateTime;
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentDate;
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTime;
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
final class TimeEntity
{
#[ORM\Id]
#[ORM\Column]
public int $id;
- #[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'], insertable: false, updatable: false)]
+ #[ORM\Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
public DateTime $createdAt;
- #[ORM\Column(options: ['default' => 'CURRENT_TIME'], insertable: false, updatable: false)]
+ #[ORM\Column(options: ['default' => new CurrentTime()], insertable: false, updatable: false)]
public DateTime $createdTime;
- #[ORM\Column(options: ['default' => 'CURRENT_DATE'], insertable: false, updatable: false)]
+ #[ORM\Column(options: ['default' => new CurrentDate()], insertable: false, updatable: false)]
public DateTime $createdDate;
}
```
Here is how to do the same when mapping entities using XML:
```diff
-
+
-
+
-
+
-
+
```
## Deprecate `FieldMapping::$default`
The `default` property of `Doctrine\ORM\Mapping\FieldMapping` is deprecated and
will be removed in 4.0. Instead, use `FieldMapping::$options['default']`.
## Deprecate specifying `nullable` on columns that end up being used in a primary key
Specifying `nullable` on join columns that are part of a primary key is
deprecated and will be an error in 4.0.
This can happen when using a join column mapping together with an id mapping,
or when using a join column mapping or an inverse join column mapping on a
many-to-many relationship.
```diff
class User
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Id]
#[ORM\ManyToOne(targetEntity: Family::class, inversedBy: 'users')]
- #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id', nullable: true)]
+ #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id')]
private ?Family $family;
#[ORM\ManyToMany(targetEntity: Group::class)]
#[ORM\JoinTable(name: 'user_group')]
- #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true)]
- #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id', nullable: true)]
+ #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id')]
+ #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
private Collection $groups;
}
```
## Deprecate `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
Using `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
is deprecated in favor of using an associative array of join parts with the
root alias as key.
## Deprecate using the `WITH` keyword for arbitrary DQL joins
Using the `WITH` keyword to specify the condition for an arbitrary DQL join is
deprecated in favor of using the `ON` keyword (similar to the SQL syntax for
joins).
The `WITH` keyword is now meant to be used only for filtering conditions in
association joins.
# Upgrade to 3.5
See the General notes to upgrading to 3.x versions above.
## Deprecate not using native lazy objects on PHP 8.4+
Having native lazy objects disabled on PHP 8.4+ is deprecated and will not be
possible in 4.0.
You can enable them through configuration:
```php
$config->enableNativeLazyObjects(true);
```
As a consequence, methods, parameters and commands related to userland lazy
objects have been deprecated on PHP 8.4+:
- `Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand`
- `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
- `Doctrine\ORM\Configuration::getProxyDir()`
- `Doctrine\ORM\Configuration::getProxyNamespace()`
- `Doctrine\ORM\Configuration::setAutoGenerateProxyClasses()`
- `Doctrine\ORM\Configuration::setProxyDir()`
- `Doctrine\ORM\Configuration::setProxyNamespace()`
- Passing more than one argument to `Doctrine\ORM\Proxy\ProxyFactory::__construct()`
Additionally, some methods of ORMSetup have been deprecated in favor of a new
counterpart.
- `Doctrine\ORM\ORMSetup::createAttributeMetadataConfiguration()` is deprecated in favor of
`Doctrine\ORM\ORMSetup::createAttributeMetadataConfig()`
- `Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()` is deprecated in favor of
`Doctrine\ORM\ORMSetup::createXMLMetadataConfig()`
- `Doctrine\ORM\ORMSetup::createConfiguration()` is deprecated in favor of
`Doctrine\ORM\ORMSetup::createConfig()`
## Deprecate methods for configuring no longer configurable features
Since 3.0, lazy ghosts are enabled unconditionally, and so is rejecting ID
collisions in the identity map.
As a consequence, the following methods are deprecated and will be removed in 4.0:
* `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()`
* `Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()`
# Upgrade to 3.4.1
## BC BREAK: You can no longer use the `.*` notation to get all fields of an entity in a DTO
This feature was introduced in 3.4.0, and introduces several issues, so we
decide to remove it before it is used too widely.
# Upgrade to 3.4
See the General notes to upgrading to 3.x versions above.
## Discriminator Map class duplicates
Using the same class several times in a discriminator map is deprecated.
In 4.0, this will be an error.
## `Doctrine\ORM\Mapping\ClassMetadata::$reflFields` deprecated
To better support property hooks and lazy proxies in the future, `$reflFields` had to
be deprecated because we cannot use the PHP internal reflection API directly anymore.
The property was changed from an array to an object of type `LegacyReflectionFields`
that implements `ArrayAccess`.
Use the new `Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor` API and access
through `Doctrine\ORM\Mapping\ClassMetadata::$propertyAccessors` instead.
Companion accessor methods are deprecated as well.
# Upgrade to 3.3
See the General notes to upgrading to 3.x versions above.
## Deprecate `DatabaseDriver`
The class `Doctrine\ORM\Mapping\Driver\DatabaseDriver` is deprecated without replacement.
## Add `Doctrine\ORM\Query\OutputWalker` interface, deprecate `Doctrine\ORM\Query\SqlWalker::getExecutor()`
Output walkers should implement the new `\Doctrine\ORM\Query\OutputWalker` interface and create
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances instead of `Doctrine\ORM\Query\Exec\AbstractSqlExecutor`s.
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
`SqlFinalizer` can be kept in the query cache and used regardless of the actual `firstResult`/`maxResult` values.
Any operation dependent on `firstResult`/`maxResult` should take place within the `SqlFinalizer::createExecutor()`
method. Details can be found at https://github.com/doctrine/orm/pull/11188.
# Upgrade to 3.2
See the General notes to upgrading to 3.x versions above.
## Deprecate the `NotSupported` exception
The class `Doctrine\ORM\Exception\NotSupported` is deprecated without replacement.
## Deprecate remaining `Serializable` implementation
Relying on `SequenceGenerator` implementing the `Serializable` is deprecated
because that interface won't be implemented in ORM 4 anymore.
The following methods are deprecated:
* `SequenceGenerator::serialize()`
* `SequenceGenerator::unserialize()`
## `orm:schema-tool:update` option `--complete` is deprecated
That option behaves as a no-op, and is deprecated. It will be removed in 4.0.
## Deprecate properties `$indexes` and `$uniqueConstraints` of `Doctrine\ORM\Mapping\Table`
The properties `$indexes` and `$uniqueConstraints` have been deprecated since they had no effect at all.
The preferred way of defining indices and unique constraints is by
using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\Index` attributes.
# Upgrade to 3.1
See the General notes to upgrading to 3.x versions above.
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
This class is deprecated and will be removed in 4.0.
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
`doctrine/persistence`.
## Deprecate passing null to `ClassMetadata::fullyQualifiedClassName()`
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
deprecated and will no longer be possible in 4.0.
## Deprecate array access
Using array access on instances of the following classes is deprecated:
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
- `Doctrine\ORM\Mapping\EmbedClassMapping`
- `Doctrine\ORM\Mapping\FieldMapping`
- `Doctrine\ORM\Mapping\JoinColumnMapping`
- `Doctrine\ORM\Mapping\JoinTableMapping`
# Upgrade to 3.0
See the General notes to upgrading to 3.x versions above.
## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception
Previously, calling
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
the owning side of an association returned `null`, which was undocumented, and
wrong according to the phpdoc of the parent method.
If you do not know whether you are on the owning or inverse side of an association,
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
to find out.
## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader`
Make sure to use the former when writing a type declaration or an `instanceof` check.
## Minor BC BREAK: Changed order of arguments passed to `OneToOne`, `ManyToOne` and `Index` mapping PHP attributes
To keep PHP mapping attributes consistent, order of arguments passed to above attributes has been changed
so `$targetEntity` is a first argument now. This change affects only non-named arguments usage.
## BC BREAK: AUTO keyword for identity generation defaults to IDENTITY for PostgreSQL when using `doctrine/dbal` 4
When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for
an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY`
instead of `SEQUENCE` or `SERIAL`.
There are three ways to handle this change.
* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html)
* If you want to keep using SQL sequences, you need to configure the ORM this way:
```php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping\ClassMetadata;
assert($configuration instanceof Configuration);
$configuration->setIdentityGenerationPreferences([
PostgreSQLPlatform::CLASS => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
```
* You can change individual entities to use the `SEQUENCE` strategy instead of `AUTO`:
```php
diff --git a/src/Entity/Example.php b/src/Entity/Example.php
index 28be8df378..3b7d61bda6 100644
--- a/src/Entity/Example.php
+++ b/src/Entity/Example.php
@@ -38,7 +38,7 @@ class Example
#[ORM\Id]
#[ORM\Column(type: 'integer')]
- #[ORM\GeneratedValue(strategy: 'AUTO')]
+ #[ORM\GeneratedValue(strategy: 'SEQUENCE')]
private int $id;
#[Assert\Length(max: 255)]
```
The later two options require a small database migration that will remove the default
expression fetching the next value from the sequence. It's not strictly necessary to
do this migration because the code will work anyway. A benefit of this approach is
that you can just make and roll out the code changes first and then migrate the database later.
## BC BREAK: Throw exceptions when using illegal attributes on Embeddable
There are only a few attributes allowed on an embeddable such as `#[Column]` or
`#[Embedded]`. Previously all others that target entity classes where ignored,
now they throw an exception.
## BC BREAK: Partial objects are removed
WARNING: This was relaxed in ORM 3.2 when partial was re-allowed for array-hydration.
- The `PARTIAL` keyword in DQL no longer exists (reintroduced in ORM 3.2)
- `Doctrine\ORM\Query\AST\PartialObjectExpression` is removed. (reintroduced in ORM 3.2)
- `Doctrine\ORM\Query\SqlWalker::HINT_PARTIAL` (reintroduced in ORM 3.2) and
`Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD` are removed.
- `Doctrine\ORM\EntityManager*::getPartialReference()` is removed.
## BC BREAK: Enforce ArrayCollection Type on `\Doctrine\ORM\QueryBuilder::setParameters(ArrayCollection $parameters)`
The argument $parameters can no longer be a key=>value array. Only ArrayCollection types are allowed.
### Before
```php
$qb = $em->createQueryBuilder()
->select('u')
->from('User', 'u')
->where('u.id = :user_id1 OR u.id = :user_id2')
->setParameters(array(
'user_id1' => 1,
'user_id2' => 2
));
```
### After
```php
$qb = $em->createQueryBuilder()
->select('u')
->from('User', 'u')
->where('u.id = :user_id1 OR u.id = :user_id2')
->setParameters(new ArrayCollection(array(
new Parameter('user_id1', 1),
new Parameter('user_id2', 2)
)));
```
## BC BREAK: `Doctrine\ORM\Persister\Entity\EntityPersister::executeInserts()` return type changed to `void`
Implementors should adapt to the new signature, and should call
`UnitOfWork::assignPostInsertId()` for each entry in the previously returned
array.
## BC BREAK: `Doctrine\ORM\Proxy\ProxyFactory` no longer extends abstract factory from `doctrine/common`
It is no longer possible to call methods, constants or properties inherited
from that class on a `ProxyFactory` instance.
`Doctrine\ORM\Proxy\ProxyFactory::createProxyDefinition()` and
`Doctrine\ORM\Proxy\ProxyFactory::resetUninitializedProxy()` are removed as well.
## BC BREAK: lazy ghosts are enabled unconditionally
`Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()` and
`Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()` are now no-ops and
will be deprecated in 3.1.0
## BC BREAK: collisions in identity map are unconditionally rejected
`Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` and
`Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()` are now
no-ops and will be deprecated in 3.1.0.
## BC BREAK: Lifecycle callback mapping on embedded classes is now explicitly forbidden
Lifecycle callback mapping on embedded classes produced no effect, and is now
explicitly forbidden to point out mistakes.
## BC BREAK: The `NOTIFY` change tracking policy is removed
You should use `DEFERRED_EXPLICIT` instead.
## BC BREAK: `Mapping\Driver\XmlDriver::__construct()` third argument is now enabled by default
The third argument to
`Doctrine\ORM\Mapping\Driver\XmlDriver::__construct()` was introduced to
let users opt-in to XML validation, that is now always enabled by default.
As a consequence, the same goes for
`Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver`, and for
`Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()`.
## BC BREAK: `Mapping\Driver\AttributeDriver::__construct()` second argument is now a no-op
The second argument to
`Doctrine\ORM\Mapping\Driver\AttributeDriver::__construct()` was introduced to
let users opt-in to a new behavior, that is now always enforced, regardless of
the value of that argument.
## BC BREAK: `Query::setDQL()` and `Query::setFirstResult()` no longer accept `null`
The `$dqlQuery` argument of `Doctrine\ORM\Query::setDQL()` must always be a
string.
The `$firstResult` argument of `Doctrine\ORM\Query::setFirstResult()` must
always be an integer.
## BC BREAK: `orm:schema-tool:update` option `--complete` is now a no-op
`orm:schema-tool:update` now behaves as if `--complete` was provided,
regardless of whether it is provided or not.
## BC BREAK: Removed `Doctrine\ORM\Proxy\Proxy` interface.
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
## BC BREAK: Overriding fields or associations declared in other than mapped superclasses
As stated in the documentation, fields and associations may only be overridden when being inherited
from mapped superclasses. Overriding them for parent entity classes now throws a `MappingException`.
## BC BREAK: Undeclared entity inheritance now throws a `MappingException`
As soon as an entity class inherits from another entity class, inheritance has to
be declared by adding the appropriate configuration for the root entity.
## Removed `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs`
Use `getObjectManager()` instead.
## BC BREAK: Removed `Doctrine\ORM\Mapping\ClassMetadataInfo` class
Use `Doctrine\ORM\Mapping\ClassMetadata` instead.
## BC BREAK: Removed `Doctrine\ORM\Event\LifecycleEventArgs` class.
Use one of the dedicated event classes instead:
* `Doctrine\ORM\Event\PrePersistEventArgs`
* `Doctrine\ORM\Event\PreUpdateEventArgs`
* `Doctrine\ORM\Event\PreRemoveEventArgs`
* `Doctrine\ORM\Event\PostPersistEventArgs`
* `Doctrine\ORM\Event\PostUpdateEventArgs`
* `Doctrine\ORM\Event\PostRemoveEventArgs`
* `Doctrine\ORM\Event\PostLoadEventArgs`
## BC BREAK: Removed `AttributeDriver::$entityAnnotationClasses` and `AttributeDriver::getReader()`
* If you need to change the behavior of `AttributeDriver::isTransient()`,
override that method instead.
* The attribute reader is internal to the driver and should not be accessed from outside.
## BC BREAK: Removed `Doctrine\ORM\Query\AST\InExpression`
The AST parser will create a `InListExpression` or a `InSubselectExpression` when
encountering an `IN ()` DQL expression instead of a generic `InExpression`.
As a consequence, `SqlWalker::walkInExpression()` has been replaced by
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
## BC BREAK: Changed `EntityManagerInterface#refresh($entity)`, `EntityManagerDecorator#refresh($entity)` and `UnitOfWork#refresh($entity)` signatures
The new signatures of these methods add an optional `LockMode|int|null $lockMode`
param with default `null` value (no lock).
## BC Break: Removed AnnotationDriver
The annotation driver and anything related to annotation has been removed.
Please migrate to another mapping driver.
The `Doctrine\ORM\Mapping\Annotation` maker interface has been removed in favor of the new
`Doctrine\ORM\Mapping\MappingAttribute` interface.
## BC BREAK: Removed `EntityManager::create()`
The constructor of `EntityManager` is now public and must be used instead of the `create()` method.
However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters.
You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the
connection.
## BC BREAK: Removed `QueryBuilder` methods and constants.
The following `QueryBuilder` constants and methods have been removed:
1. `SELECT`,
2. `DELETE`,
3. `UPDATE`,
4. `STATE_DIRTY`,
5. `STATE_CLEAN`,
6. `getState()`,
7. `getType()`.
## BC BREAK: Omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete` is not supported anymore
When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted.
### Before
```php
$qb = $em->createQueryBuilder()
->delete('User u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
### After
```php
$qb = $em->createQueryBuilder()
->delete('User', 'u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
## BC BREAK: Split output walkers and tree walkers
`SqlWalker` and its child classes don't implement the `TreeWalker` interface
anymore.
The following methods have been removed from the `TreeWalker` interface and
from the `TreeWalkerAdapter` and `TreeWalkerChain` classes:
* `setQueryComponent()`
* `walkSelectClause()`
* `walkFromClause()`
* `walkFunction()`
* `walkOrderByClause()`
* `walkOrderByItem()`
* `walkHavingClause()`
* `walkJoin()`
* `walkSelectExpression()`
* `walkQuantifiedExpression()`
* `walkSubselect()`
* `walkSubselectFromClause()`
* `walkSimpleSelectClause()`
* `walkSimpleSelectExpression()`
* `walkAggregateExpression()`
* `walkGroupByClause()`
* `walkGroupByItem()`
* `walkDeleteClause()`
* `walkUpdateClause()`
* `walkUpdateItem()`
* `walkWhereClause()`
* `walkConditionalExpression()`
* `walkConditionalTerm()`
* `walkConditionalFactor()`
* `walkConditionalPrimary()`
* `walkExistsExpression()`
* `walkCollectionMemberExpression()`
* `walkEmptyCollectionComparisonExpression()`
* `walkNullComparisonExpression()`
* `walkInExpression()`
* `walkInstanceOfExpression()`
* `walkLiteral()`
* `walkBetweenExpression()`
* `walkLikeExpression()`
* `walkStateFieldPathExpression()`
* `walkComparisonExpression()`
* `walkInputParameter()`
* `walkArithmeticExpression()`
* `walkArithmeticTerm()`
* `walkStringPrimary()`
* `walkArithmeticFactor()`
* `walkSimpleArithmeticExpression()`
* `walkPathExpression()`
* `walkResultVariable()`
* `getExecutor()`
The following changes have been made to the abstract `TreeWalkerAdapter` class:
* The method `setQueryComponent()` is now protected.
* The method `_getQueryComponents()` has been removed in favor of
`getQueryComponents()`.
## BC BREAK: Removed identity columns emulation through sequences
If the platform you are using does not support identity columns, you should
switch to the `SEQUENCE` strategy.
## BC BREAK: Made setters parameters mandatory
The following methods require an argument when being called. Pass `null`
instead of omitting the argument.
* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()`
* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()`
* `Doctrine\ORM\AbstractQuery::setResultCache()`
* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()`
## BC BREAK: New argument to `NamingStrategy::joinColumnName()`
### Before
```php
`Exception\MissingMappingDriverImplementation::create()`
* `unrecognizedField()` => `Persisters\Exception\UnrecognizedField::byName()`
* `unexpectedAssociationValue()` => `Exception\UnexpectedAssociationValue::create()`
* `invalidOrientation()` => `Persisters\Exception\InvalidOrientation::fromClassNameAndField()`
* `entityManagerClosed()` => `Exception\EntityManagerClosed::create()`
* `invalidHydrationMode()` => `Exception\InvalidHydrationMode::fromMode()`
* `mismatchedEventManager()` => `Exception\MismatchedEventManager::create()`
* `findByRequiresParameter()` => `Repository\Exception\InvalidMagicMethodCall::onMissingParameter()`
* `invalidMagicCall()` => `Repository\Exception\InvalidMagicMethodCall::becauseFieldNotFoundIn()`
* `invalidFindByInverseAssociation()` => `Repository\Exception\InvalidFindByCall::fromInverseSideUsage()`
* `invalidResultCacheDriver()` => `Cache\Exception\InvalidResultCacheDriver::create()`
* `notSupported()` => `Exception\NotSupported::create()`
* `queryCacheNotConfigured()` => `QueryCacheNotConfigured::create()`
* `metadataCacheNotConfigured()` => `Cache\Exception\MetadataCacheNotConfigured::create()`
* `queryCacheUsesNonPersistentCache()` => `Cache\Exception\QueryCacheUsesNonPersistentCache::fromDriver()`
* `metadataCacheUsesNonPersistentCache()` => `Cache\Exception\MetadataCacheUsesNonPersistentCache::fromDriver()`
* `proxyClassesAlwaysRegenerating()` => `Exception\ProxyClassesAlwaysRegenerating::create()`
* `invalidEntityRepository()` => `Exception\InvalidEntityRepository::fromClassName()`
* `missingIdentifierField()` => `Exception\MissingIdentifierField::fromFieldAndClass()`
* `unrecognizedIdentifierFields()` => `Exception\UnrecognizedIdentifierFields::fromClassAndFieldNames()`
* `cantUseInOperatorOnCompositeKeys()` => `Persisters\Exception\CantUseInOperatorOnCompositeKeys::create()`
## BC Break: `CacheException` is no longer a class, but an interface
All methods in `Doctrine\ORM\Cache\CacheException` have been extracted to dedicated exceptions.
* `updateReadOnlyCollection()` => `Cache\Exception\CannotUpdateReadOnlyCollection::fromEntityAndField()`
* `updateReadOnlyEntity()` => `Cache\Exception\CannotUpdateReadOnlyEntity::fromEntity()`
* `nonCacheableEntity()` => `Cache\Exception\NonCacheableEntity::fromEntity()`
* `nonCacheableEntityAssociation()` => `Cache\Exception\NonCacheableEntityAssociation::fromEntityAndField()`
## BC Break: Missing type declaration added for identifier generators
Although undocumented, it was possible to configure a custom repository
class that implements `ObjectRepository` but does not extend the
`EntityRepository` base class. Repository classes have to extend
`EntityRepository` now.
## BC BREAK: Removed support for entity namespace alias
- `EntityManager::getRepository()` no longer accepts the entity namespace alias
notation.
- `Configuration::addEntityNamespace()` and
`Configuration::getEntityNamespace()` have been removed.
## BC BREAK: Remove helper methods from `AbstractCollectionPersister`
The following protected methods of
`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister`
have been removed.
* `evictCollectionCache()`
* `evictElementCache()`
## BC BREAK: `Doctrine\ORM\Query\TreeWalkerChainIterator`
This class has been removed without replacement.
## BC BREAK: Remove quoting methods from `ClassMetadata`
The following methods have been removed from the class metadata because
quoting is handled by implementations of `Doctrine\ORM\Mapping\QuoteStrategy`:
* `getQuotedIdentifierColumnNames()`
* `getQuotedColumnName()`
* `getQuotedTableName()`
* `getQuotedJoinTableName()`
## BC BREAK: Remove ability to merge detached entities
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 method `UnitOfWork::merge()` has been removed. The method
`EntityManager::merge()` will throw an exception on each call.
## BC BREAK: Removed ability to partially flush/commit entity manager and unit of work
The following methods don't accept a single entity or an array of entities anymore:
* `Doctrine\ORM\EntityManager::flush()`
* `Doctrine\ORM\Decorator\EntityManagerDecorator::flush()`
* `Doctrine\ORM\UnitOfWork::commit()`
The semantics of `flush()` and `commit()` 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 entities, as the parameter is now completely ignored.
## BC BREAK: Removed ability to partially clear entity manager and unit of work
* Passing an argument other than `null` to `EntityManager::clear()` will raise
an exception.
* The unit of work cannot be cleared partially anymore. Passing an argument to
`UnitOfWork::clear()` does not have any effect anymore; the unit of work is
cleared completely.
* The method `EntityRepository::clear()` has been removed.
* The methods `getEntityClass()` and `clearsAllEntities()` have been removed
from `OnClearEventArgs`.
## BC BREAK: Remove support for Doctrine Cache
The Doctrine Cache library is not supported anymore. The following methods
have been removed from `Doctrine\ORM\Configuration`:
* `getQueryCacheImpl()`
* `setQueryCacheImpl()`
* `getHydrationCacheImpl()`
* `setHydrationCacheImpl()`
* `getMetadataCacheImpl()`
* `setMetadataCacheImpl()`
The methods have been replaced by PSR-6 compatible counterparts
(just strip the `Impl` suffix from the old name to get the new one).
## BC BREAK: Remove `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
This functionality has been moved to the new `ORMSetup` class. Call
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
a new annotation driver.
## BC BREAK: Remove `Doctrine\ORM\Tools\Setup`
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
accepted a Doctrine Cache instance in each method has been removed.
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
cache instead.
## BC BREAK: Removed named queries
All APIs related to named queries have been removed.
## BC BREAK: Remove old cache accessors and mutators from query classes
The following methods have been removed from `AbstractQuery`:
* `setResultCacheDriver()`
* `getResultCacheDriver()`
* `useResultCache()`
* `getResultCacheLifetime()`
* `getResultCacheId()`
The following methods have been removed from `Query`:
* `setQueryCacheDriver()`
* `getQueryCacheDriver()`
## BC BREAK: Remove `Doctrine\ORM\Cache\MultiGetRegion`
The interface has been merged into `Doctrine\ORM\Cache\Region`.
## BC BREAK: Rename `AbstractIdGenerator::generate()` to `generateId()`
* Implementations of `AbstractIdGenerator` have to implement the method
`generateId()`.
* The method `generate()` has been removed from `AbstractIdGenerator`.
## BC BREAK: Remove 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 removed:
* `Configuration::ensureProductionSettings()`
* the `orm:ensure-production-settings` console command
## BC BREAK: 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 not supported anymore.
* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as
second argument now.
* `DefaultMultiGetRegion`: This class has been removed.
* `DefaultRegion`:
* The constructor expects a PSR-6 cache item pool as second argument now.
* The protected `$cache` property is removed.
* The properties `$name` and `$lifetime` as well as the constant
`REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are
`private` now.
* The method `getCache()` has been removed.
## BC Break: Remove `Doctrine\ORM\Mapping\Driver\PHPDriver`
Use `StaticPHPDriver` instead when you want to programmatically configure
entity metadata.
## BC BREAK: Remove `Doctrine\ORM\EntityManagerInterface#transactional()`
This method has been replaced by `Doctrine\ORM\EntityManagerInterface#wrapInTransaction()`.
## BC BREAK: Removed support for schema emulation.
The ORM no longer attempts to emulate schemas on SQLite.
## BC BREAK: Remove `Setup::registerAutoloadDirectory()`
Use Composer's autoloader instead.
## BC BREAK: Remove YAML mapping drivers.
If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** migrate to
attribute, annotation or XML drivers instead.
You can use the `orm:convert-mapping` command to convert your metadata mapping to XML
_before_ upgrading to 3.0:
```sh
php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
```
## BC BREAK: Remove code generators and related console commands
These console commands have been removed:
* `orm:convert-d1-schema`
* `orm:convert-mapping`
* `orm:generate:entities`
* `orm:generate-repositories`
These classes have been deprecated:
* `Doctrine\ORM\Tools\ConvertDoctrine1Schema`
* `Doctrine\ORM\Tools\EntityGenerator`
* `Doctrine\ORM\Tools\EntityRepositoryGenerator`
The entire `Doctrine\ORM\Tools\Export` namespace has been removed as well.
## BC BREAK: Removed `Doctrine\ORM\Version`
Use Composer's runtime API if you _really_ need to check the version of the ORM package at runtime.
## BC BREAK: EntityRepository::count() signature change
The argument `$criteria` of `Doctrine\ORM\EntityRepository::count()` is now
optional. Overrides in child classes should be made compatible.
## BC BREAK: changes in exception hierarchy
- `Doctrine\ORM\ORMException` has been removed
- `Doctrine\ORM\Exception\ORMException` is now an interface
## Variadic methods now use native variadics
The following methods were using `func_get_args()` to simulate a variadic argument:
- `Doctrine\ORM\Query\Expr#andX()`
- `Doctrine\ORM\Query\Expr#orX()`
- `Doctrine\ORM\QueryBuilder#select()`
- `Doctrine\ORM\QueryBuilder#addSelect()`
- `Doctrine\ORM\QueryBuilder#where()`
- `Doctrine\ORM\QueryBuilder#andWhere()`
- `Doctrine\ORM\QueryBuilder#orWhere()`
- `Doctrine\ORM\QueryBuilder#groupBy()`
- `Doctrine\ORM\QueryBuilder#andGroupBy()`
- `Doctrine\ORM\QueryBuilder#having()`
- `Doctrine\ORM\QueryBuilder#andHaving()`
- `Doctrine\ORM\QueryBuilder#orHaving()`
A variadic argument is now actually used in their signatures signature (`...$x`).
Signatures of overridden methods should be changed accordingly
## Minor BC BREAK: removed `Doctrine\ORM\EntityManagerInterface#copy()`
Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is removed in 3.0.
## BC BREAK: Removed classes related to UUID and TABLE generator strategies
The following classes have been removed:
- `Doctrine\ORM\Id\TableGenerator`
- `Doctrine\ORM\Id\UuidGenerator`
Using the `UUID` strategy for generating identifiers is not supported anymore.
## BC BREAK: Removed `Query::iterate()`
The deprecated method `Query::iterate()` has been removed along with the
following classes and methods:
- `AbstractHydrator::iterate()`
- `AbstractHydrator::hydrateRow()`
- `IterableResult`
Use `toIterable()` instead.
# Upgrade to 2.20
## Add `Doctrine\ORM\Query\OutputWalker` interface, deprecate `Doctrine\ORM\Query\SqlWalker::getExecutor()`
Output walkers should implement the new `\Doctrine\ORM\Query\OutputWalker` interface and create
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances instead of `Doctrine\ORM\Query\Exec\AbstractSqlExecutor`s.
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
`SqlFinalizer` can be kept in the query cache and used regardless of the actual `firstResult`/`maxResult` values.
Any operation dependent on `firstResult`/`maxResult` should take place within the `SqlFinalizer::createExecutor()`
method. Details can be found at https://github.com/doctrine/orm/pull/11188.
## Explictly forbid property hooks
Property hooks are not supported yet by Doctrine ORM. Until support is added,
they are explicitly forbidden because the support would result in a breaking
change in behavior.
Progress on this is tracked at https://github.com/doctrine/orm/issues/11624 .
## PARTIAL DQL syntax is undeprecated
Use of the PARTIAL keyword is not deprecated anymore in DQL, because we will be
able to support PARTIAL objects with PHP 8.4 Lazy Objects and
Symfony/VarExporter in a better way. When we decided to remove this feature
these two abstractions did not exist yet.
WARNING: If you want to upgrade to 3.x and still use PARTIAL keyword in DQL
with array or object hydrators, then you have to directly migrate to ORM 3.3.x or higher.
PARTIAL keyword in DQL is not available in 3.0, 3.1 and 3.2 of ORM.
## Deprecate `\Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker()`
Use the `\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER` query hint to set the output walker
class instead of setting it through the `\Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker()` method
on the parser instance.
# Upgrade to 2.19
## Deprecate calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association
Calling
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
the owning side of an association returns `null`, which is undocumented, and
wrong according to the phpdoc of the parent method.
If you do not know whether you are on the owning or inverse side of an association,
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
to find out.
## Deprecate `Doctrine\ORM\Query\Lexer::T_*` constants
Use `Doctrine\ORM\Query\TokenType::T_*` instead.
# Upgrade to 2.17
## Deprecate annotations classes for named queries
The following classes have been deprecated:
* `Doctrine\ORM\Mapping\NamedNativeQueries`
* `Doctrine\ORM\Mapping\NamedNativeQuery`
* `Doctrine\ORM\Mapping\NamedQueries`
* `Doctrine\ORM\Mapping\NamedQuery`
## Deprecate `Doctrine\ORM\Query\Exec\AbstractSqlExecutor::_sqlStatements`
Use `Doctrine\ORM\Query\Exec\AbstractSqlExecutor::sqlStatements` instead.
## Undeprecate `Doctrine\ORM\Proxy\Autoloader`
It will be a full-fledged class, no longer extending
`Doctrine\Common\Proxy\Autoloader` in 3.0.x.
## Deprecated: reliance on the non-optimal defaults that come with the `AUTO` identifier generation strategy
When the `AUTO` identifier generation strategy was introduced, the best
strategy at the time was selected for each database platform.
A lot of time has passed since then, and with ORM 3.0.0 and DBAL 4.0.0, support
for better strategies will be added.
Because of that, it is now deprecated to rely on the historical defaults when
they differ from what we will be recommended in the future.
Instead, you should pick a strategy for each database platform you use, and it
will be used when using `AUTO`. As of now, only PostgreSQL is affected by this.
It is recommended that PostgreSQL users configure their existing and new
applications to use `SEQUENCE` until `doctrine/dbal` 4.0.0 is released:
```php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
assert($configuration instanceof Configuration);
$configuration->setIdentityGenerationPreferences([
PostgreSQLPlatform::CLASS => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
```
When DBAL 4 is released, `AUTO` will result in `IDENTITY`, and the above
configuration should be removed to migrate to it.
## Deprecate `EntityManagerInterface::getPartialReference()`
This method does not have a replacement and will be removed in 3.0.
## Deprecate not-enabling lazy-ghosts
Not enabling lazy ghost objects is deprecated. In ORM 3.0, they will be always enabled.
Ensure `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled(true)` is called to enable them.
# Upgrade to 2.16
## Deprecated accepting duplicate IDs in the identity map
For any given entity class and ID value, there should be only one object instance
representing the entity.
In https://github.com/doctrine/orm/pull/10785, a check was added that will guard this
in the identity map. The most probable cause for violations of this rule are collisions
of application-provided IDs.
In ORM 2.16.0, the check was added by throwing an exception. In ORM 2.16.1, this will be
changed to a deprecation notice. ORM 3.0 will make it an exception again. Use
`\Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` if you want to opt-in
to the new mode.
## Potential changes to the order in which `INSERT`s are executed
In https://github.com/doctrine/orm/pull/10547, the commit order computation was improved
to fix a series of bugs where a correct (working) commit order was previously not found.
Also, the new computation may get away with fewer queries being executed: By inserting
referred-to entities first and using their ID values for foreign key fields in subsequent
`INSERT` statements, additional `UPDATE` statements that were previously necessary can be
avoided.
When using database-provided, auto-incrementing IDs, this may lead to IDs being assigned
to entities in a different order than it was previously the case.
## Deprecated returning post insert IDs from `EntityPersister::executeInserts()`
Persisters implementing `\Doctrine\ORM\Persisters\Entity\EntityPersister` should no longer
return an array of post insert IDs from their `::executeInserts()` method. Make the
persister call `Doctrine\ORM\UnitOfWork::assignPostInsertId()` instead.
## Changing the way how reflection-based mapping drivers report fields, deprecated the "old" mode
In ORM 3.0, a change will be made regarding how the `AttributeDriver` reports field mappings.
This change is necessary to be able to detect (and reject) some invalid mapping configurations.
To avoid surprises during 2.x upgrades, the new mode is opt-in. It can be activated on the
`AttributeDriver` and `AnnotationDriver` by setting the `$reportFieldsWhereDeclared`
constructor parameter to `true`. It will cause `MappingException`s to be thrown when invalid
configurations are detected.
Not enabling the new mode will cause a deprecation notice to be raised. In ORM 3.0,
only the new mode will be available.
# Upgrade to 2.15
## Deprecated configuring `JoinColumn` on the inverse side of one-to-one associations
For one-to-one associations, the side using the `mappedBy` attribute is the inverse side.
The owning side is the entity with the table containing the foreign key. Using `JoinColumn`
configuration on the _inverse_ side now triggers a deprecation notice and will be an error
in 3.0.
## Deprecated overriding fields or associations not declared in mapped superclasses
As stated in the documentation, fields and associations may only be overridden when being inherited
from mapped superclasses. Overriding them for parent entity classes now triggers a deprecation notice
and will be an error in 3.0.
## Deprecated undeclared entity inheritance
As soon as an entity class inherits from another entity class, inheritance has to
be declared by adding the appropriate configuration for the root entity.
## Deprecated stubs for "concrete table inheritance"
This third way of mapping class inheritance was never implemented. Code stubs are
now deprecated and will be removed in 3.0.
* `\Doctrine\ORM\Mapping\ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS` constant
* `\Doctrine\ORM\Mapping\ClassMetadataInfo::isInheritanceTypeTablePerClass()` method
* Using `TABLE_PER_CLASS` as the value for the `InheritanceType` attribute or annotation
or in XML configuration files.
# Upgrade to 2.14
## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method.
Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead.
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
The following public constants have been deprecated:
* `CommitOrderCalculator::NOT_VISITED`
* `CommitOrderCalculator::IN_PROGRESS`
* `CommitOrderCalculator::VISITED`
These constants were used for internal purposes. Relying on them is discouraged.
## Deprecated `Doctrine\ORM\Query\AST\InExpression`
The AST parser will create a `InListExpression` or a `InSubselectExpression` when
encountering an `IN ()` DQL expression instead of a generic `InExpression`.
As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
## Deprecated constructing a `CacheKey` without `$hash`
The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with
an optional parameter `$hash`. That parameter will become mandatory in 3.0.
## Deprecated `AttributeDriver::$entityAnnotationClasses`
If you need to change the behavior of `AttributeDriver::isTransient()`,
override that method instead.
## Deprecated incomplete schema updates
Using `orm:schema-tool:update` without passing the `--complete` flag is
deprecated. Use schema asset filtering if you need to preserve assets not
managed by DBAL.
Likewise, calling `SchemaTool::updateSchema()` or
`SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated.
## Deprecated annotation mapping driver.
Please switch to one of the other mapping drivers. Native attributes which PHP
supports since version 8.0 are probably your best option.
As a consequence, the following methods are deprecated:
- `ORMSetup::createAnnotationMetadataConfiguration`
- `ORMSetup::createDefaultAnnotationDriver`
The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well.
All annotation/attribute classes implement
`Doctrine\ORM\Mapping\MappingAttribute` now.
## Deprecated `Doctrine\ORM\Proxy\Proxy` interface.
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class.
It will be removed in 3.0. Use one of the dedicated event classes instead:
* `Doctrine\ORM\Event\PrePersistEventArgs`
* `Doctrine\ORM\Event\PreUpdateEventArgs`
* `Doctrine\ORM\Event\PreRemoveEventArgs`
* `Doctrine\ORM\Event\PostPersistEventArgs`
* `Doctrine\ORM\Event\PostUpdateEventArgs`
* `Doctrine\ORM\Event\PostRemoveEventArgs`
* `Doctrine\ORM\Event\PostLoadEventArgs`
# Upgrade to 2.13
## Deprecated `EntityManager::create()`
The constructor of `EntityManager` is now public and should be used instead of the `create()` method.
However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters.
You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the
connection.
## Deprecated `QueryBuilder` methods and constants.
1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern.
2. Relying on the type of the query being built by using `QueryBuilder::getType()` has been deprecated.
If necessary, track the type of the query being built outside of the builder.
The following `QueryBuilder` constants related to the above methods have been deprecated:
1. `SELECT`,
2. `DELETE`,
3. `UPDATE`,
4. `STATE_DIRTY`,
5. `STATE_CLEAN`.
## Deprecated omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete`
When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted.
### Before
```php
$qb = $em->createQueryBuilder()
->delete('User u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
### After
```php
$qb = $em->createQueryBuilder()
->delete('User', 'u')
->where('u.id = :user_id')
->setParameter('user_id', 1);
```
## Deprecated using the `IDENTITY` identifier strategy on platform that do not support identity columns
If identity columns are emulated with sequences on the platform you are using,
you should switch to the `SEQUENCE` strategy.
## Deprecated passing `null` to `Doctrine\ORM\Query::setFirstResult()`
`$query->setFirstResult(null);` is equivalent to `$query->setFirstResult(0)`.
## Deprecated calling setters without arguments
The following methods will require an argument in 3.0. Pass `null` instead of
omitting the argument.
* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()`
* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()`
* `Doctrine\ORM\AbstractQuery::setResultCache()`
* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()`
## Deprecated passing invalid fetch modes to `AbstractQuery::setFetchMode()`
Calling `AbstractQuery::setFetchMode()` with anything else than
`Doctrine\ORM\Mapping::FETCH_EAGER` results in
`Doctrine\ORM\Mapping::FETCH_LAZY` being used. Relying on that behavior is
deprecated and will result in an exception in 3.0.
## Deprecated `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs`
This method has been deprecated in:
* `Doctrine\ORM\Event\OnClearEventArgs`
* `Doctrine\ORM\Event\OnFlushEventArgs`
* `Doctrine\ORM\Event\PostFlushEventArgs`
* `Doctrine\ORM\Event\PreFlushEventArgs`
It will be removed in 3.0. Use `getObjectManager()` instead.
## Prepare split of output walkers and tree walkers
In 3.0, `SqlWalker` and its child classes won't implement the `TreeWalker`
interface anymore. Relying on that inheritance is deprecated.
The following methods of the `TreeWalker` interface have been deprecated:
* `setQueryComponent()`
* `walkSelectClause()`
* `walkFromClause()`
* `walkFunction()`
* `walkOrderByClause()`
* `walkOrderByItem()`
* `walkHavingClause()`
* `walkJoin()`
* `walkSelectExpression()`
* `walkQuantifiedExpression()`
* `walkSubselect()`
* `walkSubselectFromClause()`
* `walkSimpleSelectClause()`
* `walkSimpleSelectExpression()`
* `walkAggregateExpression()`
* `walkGroupByClause()`
* `walkGroupByItem()`
* `walkDeleteClause()`
* `walkUpdateClause()`
* `walkUpdateItem()`
* `walkWhereClause()`
* `walkConditionalExpression()`
* `walkConditionalTerm()`
* `walkConditionalFactor()`
* `walkConditionalPrimary()`
* `walkExistsExpression()`
* `walkCollectionMemberExpression()`
* `walkEmptyCollectionComparisonExpression()`
* `walkNullComparisonExpression()`
* `walkInExpression()`
* `walkInstanceOfExpression()`
* `walkLiteral()`
* `walkBetweenExpression()`
* `walkLikeExpression()`
* `walkStateFieldPathExpression()`
* `walkComparisonExpression()`
* `walkInputParameter()`
* `walkArithmeticExpression()`
* `walkArithmeticTerm()`
* `walkStringPrimary()`
* `walkArithmeticFactor()`
* `walkSimpleArithmeticExpression()`
* `walkPathExpression()`
* `walkResultVariable()`
* `getExecutor()`
The following changes have been made to the abstract `TreeWalkerAdapter` class:
* All implementations of now-deprecated `TreeWalker` methods have been
deprecated as well.
* The method `setQueryComponent()` will become protected in 3.0. Calling it
publicly is deprecated.
* The method `_getQueryComponents()` is deprecated, call `getQueryComponents()`
instead.
On the `TreeWalkerChain` class, all implementations of now-deprecated
`TreeWalker` methods have been deprecated as well. However, `SqlWalker` is
unaffected by those deprecations and will continue to implement all of those
methods.
## Deprecated passing `null` to `Doctrine\ORM\Query::setDQL()`
Doing `$query->setDQL(null);` achieves nothing.
## Deprecated omitting second argument to `NamingStrategy::joinColumnName`
When implementing `NamingStrategy`, it is deprecated to implement
`joinColumnName()` with only one argument.
### Before
```php
getConfiguration();
-$config->addEntityNamespace('CMS', 'My\App\Cms');
+use My\App\Cms\CmsUser;
-$entityManager->getRepository('CMS:CmsUser');
+$entityManager->getRepository(CmsUser::class);
```
## Deprecate `AttributeDriver::getReader()` and `AnnotationDriver::getReader()`
That method was inherited from the abstract `AnnotationDriver` class of
`doctrine/persistence`, and does not seem to serve any purpose.
## Un-deprecate `Doctrine\ORM\Proxy\Proxy`
Because no forward-compatible new proxy solution had been implemented yet, the
current proxy mechanism is not considered deprecated anymore for the time
being. This applies to the following interfaces/classes:
* `Doctrine\ORM\Proxy\Proxy`
* `Doctrine\ORM\Proxy\ProxyFactory`
These methods have been un-deprecated:
* `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
* `Doctrine\ORM\Configuration::getProxyDir()`
* `Doctrine\ORM\Configuration::getProxyNamespace()`
Note that the `Doctrine\ORM\Proxy\Autoloader` remains deprecated and will be removed in 3.0.
## Deprecate helper methods from `AbstractCollectionPersister`
The following protected methods of
`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister`
are not in use anymore and will be removed.
* `evictCollectionCache()`
* `evictElementCache()`
## Deprecate `Doctrine\ORM\Query\TreeWalkerChainIterator`
This class won't have a replacement.
## Deprecate `OnClearEventArgs::getEntityClass()` and `OnClearEventArgs::clearsAllEntities()`
These methods will be removed in 3.0 along with the ability to partially clear
the entity manager.
## Deprecate `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
This functionality has been moved to the new `ORMSetup` class. Call
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
a new annotation driver.
## Deprecate `Doctrine\ORM\Tools\Setup`
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
accepted a Doctrine Cache instance in each method has been deprecated.
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
cache instead.
## Deprecate `Doctrine\ORM\Cache\MultiGetRegion`
The interface will be merged with `Doctrine\ORM\Cache\Region` in 3.0.
# Upgrade to 2.11
## Rename `AbstractIdGenerator::generate()` to `generateId()`
Implementations of `AbstractIdGenerator` have to override the method
`generateId()` without calling the parent implementation. Not doing so is
deprecated. Calling `generate()` on any `AbstractIdGenerator` implementation
is deprecated.
## PSR-6-based second level cache
The second level cache has been reworked to consume a PSR-6 cache. Using a
Doctrine Cache instance is deprecated.
* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as
second argument now.
* `DefaultMultiGetRegion`: This class is deprecated in favor of `DefaultRegion`.
* `DefaultRegion`:
* The constructor expects a PSR-6 cache item pool as second argument now.
* The protected `$cache` property is deprecated.
* The properties `$name` and `$lifetime` as well as the constant
`REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are flagged as
`@internal` now. They all will become `private` in 3.0.
* The method `getCache()` is deprecated without replacement.
## Deprecated: `Doctrine\ORM\Mapping\Driver\PHPDriver`
Use `StaticPHPDriver` instead when you want to programmatically configure
entity metadata.
You can convert mappings with the `orm:convert-mapping` command or more simply
in this case, `include` the metadata file from the `loadMetadata` static method
used by the `StaticPHPDriver`.
## Deprecated: `Setup::registerAutoloadDirectory()`
Use Composer's autoloader instead.
## Deprecated: `AbstractHydrator::hydrateRow()`
Following the deprecation of the method `AbstractHydrator::iterate()`, the
method `hydrateRow()` has been deprecated as well.
## Deprecate cache settings inspection
Doctrine does not provide its own cache implementation anymore and relies on
the PSR-6 standard instead. As a consequence, we cannot determine anymore
whether a given cache adapter is suitable for a production environment.
Because of that, functionality that aims to do so has been deprecated:
* `Configuration::ensureProductionSettings()`
* the `orm:ensure-production-settings` console command
# Upgrade to 2.10
## BC Break: `UnitOfWork` now relies on SPL object IDs, not hashes
When calling the following methods, you are now supposed to use the result of
`spl_object_id()`, and not `spl_object_hash()`:
- `UnitOfWork::clearEntityChangeSet()`
- `UnitOfWork::setOriginalEntityProperty()`
## BC Break: Removed `TABLE` id generator strategy
The implementation was unfinished for 14 years.
It is now deprecated to rely on:
- `Doctrine\ORM\Id\TableGenerator`;
- `Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_TABLE`;
- `Doctrine\ORM\Mapping\ClassMetadata::$tableGeneratorDefinition`;
- or `Doctrine\ORM\Mapping\ClassMetadata::isIdGeneratorTable()`.
## New method `Doctrine\ORM\EntityManagerInterface#wrapInTransaction($func)`
Works the same as `Doctrine\ORM\EntityManagerInterface#transactional()` but returns any value returned from `$func` closure rather than just _non-empty value returned from the closure or true_.
Because of BC policy, the method does not exist on the interface yet. This is the example of safe usage:
```php
function foo(EntityManagerInterface $entityManager, callable $func) {
if (method_exists($entityManager, 'wrapInTransaction')) {
return $entityManager->wrapInTransaction($func);
}
return $entityManager->transactional($func);
}
```
`Doctrine\ORM\EntityManagerInterface#transactional()` has been deprecated.
## Minor BC BREAK: some exception methods have been removed
The following methods were not in use and are very unlikely to be used by
downstream packages or applications, and were consequently removed:
- `ORMException::entityMissingForeignAssignedId`
- `ORMException::entityMissingAssignedIdForField`
- `ORMException::invalidFlushMode`
## Deprecated: database-side UUID generation
[DB-generated UUIDs are deprecated as of `doctrine/dbal` 2.8][DBAL deprecation].
As a consequence, using the `UUID` strategy for generating identifiers is deprecated as well.
Furthermore, relying on the following classes and methods is deprecated:
- `Doctrine\ORM\Id\UuidGenerator`
- `Doctrine\ORM\Mapping\ClassMetadataInfo::isIdentifierUuid()`
[DBAL deprecation]: https://github.com/doctrine/dbal/pull/3212
## Minor BC BREAK: Custom hydrators and `toIterable()`
The type declaration of the `$stmt` parameter of `AbstractHydrator::toIterable()` has been removed. This change might
break custom hydrator implementations that override this very method.
Overriding this method is not recommended, which is why the method is documented as `@final` now.
```diff
- public function toIterable(ResultStatement $stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable
+ public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable
```
## Deprecated: Entity Namespace Aliases
Entity namespace aliases are deprecated, use the magic ::class constant to abbreviate full class names
in EntityManager, EntityRepository and DQL.
```diff
- $entityManager->find('MyBundle:User', $id);
+ $entityManager->find(User::class, $id);
```
# Upgrade to 2.9
## Minor BC BREAK: Setup tool needs cache implementation
With the deprecation of doctrine/cache, the setup tool might no longer work as expected without a different cache
implementation. To work around this:
* Install symfony/cache: `composer require symfony/cache`. This will keep previous behaviour without any changes
* Instantiate caches yourself: to use a different cache implementation, pass a cache instance when calling any
configuration factory in the setup tool:
```diff
- $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir);
+ $cache = \Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($anyPsr6Implementation);
+ $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache);
```
* As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`.
Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache
1.11.
## Deprecated: doctrine/cache for metadata caching
The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use
`Doctrine\ORM\Configuration#setMetadataCache()` with any PSR-6 cache adapter instead.
## Removed: flushing metadata cache
To support PSR-6 caches, the `--flush` option for the `orm:clear-cache:metadata` command is ignored. Metadata cache is
now always cleared regardless of the cache adapter being used.
# Upgrade to 2.8
## Minor BC BREAK: Failed commit now throw OptimisticLockException
Method `Doctrine\ORM\UnitOfWork#commit()` can throw an OptimisticLockException when a commit silently fails and returns false
since `Doctrine\DBAL\Connection#commit()` signature changed from returning void to boolean
## Deprecated: `Doctrine\ORM\AbstractQuery#iterate()`
The method `Doctrine\ORM\AbstractQuery#iterate()` is deprecated in favor of `Doctrine\ORM\AbstractQuery#toIterable()`.
Note that `toIterable()` yields results of the query, unlike `iterate()` which yielded each result wrapped into an array.
# Upgrade to 2.7
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
(depending on passed flag) was split into two.
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
perform the pagination with join collections when max results isn't set in the query.
## Minor BC BREAK: tables filtered with `schema_filter` are no longer created
When generating schema diffs, if a source table is filtered out by a `schema_filter` expression, then a `CREATE TABLE` was
always generated, even if the table already existed. This has been changed in this release and the table will no longer
be created.
## Deprecated number unaware `Doctrine\ORM\Mapping\UnderscoreNamingStrategy`
In the last patch of the `v2.6.x` series, we fixed a bug that was not converting names properly when they had numbers
(e.g.: `base64Encoded` was wrongly converted to `base64encoded` instead of `base64_encoded`).
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
argument will be removed in 3.0 and the default behavior will be the fixed one.
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
and `disableResultCache()`. It will be removed in 3.0.
## Deprecated code generators and related console commands
These console commands have been deprecated:
* `orm:convert-mapping`
* `orm:generate:entities`
* `orm:generate-repositories`
These classes have been deprecated:
* `Doctrine\ORM\Tools\EntityGenerator`
* `Doctrine\ORM\Tools\EntityRepositoryGenerator`
Whole Doctrine\ORM\Tools\Export namespace with all its members have been deprecated as well.
## Deprecated `Doctrine\ORM\Proxy\Proxy` marker interface
Proxy objects in Doctrine ORM 3.0 will no longer implement `Doctrine\ORM\Proxy\Proxy` nor
`Doctrine\Persistence\Proxy`: instead, they implement
`ProxyManager\Proxy\GhostObjectInterface`.
These related classes have been deprecated:
* `Doctrine\ORM\Proxy\ProxyFactory`
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
These methods have been deprecated:
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
* `Doctrine\ORM\Configuration#getProxyDir()`
* `Doctrine\ORM\Configuration#getProxyNamespace()`
## Deprecated `Doctrine\ORM\Version`
The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0:
please refrain from checking the ORM version at runtime or use Composer's [runtime API](https://getcomposer.org/doc/07-runtime.md#knowing-whether-package-x-is-installed-in-version-y).
## Deprecated `EntityManager#merge()` method
Merge semantics was a poor fit for the PHP "share-nothing" architecture.
In addition to that, merging caused multiple issues with data integrity
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
The following API methods were therefore deprecated:
* `EntityManager#merge()`
* `UnitOfWork#merge()`
An alternative to `EntityManager#merge()` will not be provided by ORM 3.0, since the merging
semantics should be part of the business domain rather than the persistence domain of an
application. If your application relies heavily on CRUD-alike interactions and/or `PATCH`
restful operations, you should look at alternatives such as [JMSSerializer](https://github.com/schmittjoh/serializer).
## Extending `EntityManager` is deprecated
Final keyword will be added to the `EntityManager::class` in Doctrine ORM 3.0 in order to ensure that EntityManager
is not used as valid extension point. Valid extension point should be EntityManagerInterface.
## Deprecated `EntityManager#clear($entityName)`
If your code relies on clearing a single entity type via `EntityManager#clear($entityName)`,
the signature has been changed to `EntityManager#clear()`.
The main reason is that partial clears caused multiple issues with data integrity
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
## Deprecated `EntityManager#flush($entity)` and `EntityManager#flush($entities)`
If your code relies on single entity flushing optimisations via
`EntityManager#flush($entity)`, the signature has been changed to
`EntityManager#flush()`.
Said API was affected by multiple data integrity bugs due to the fact
that change tracking was being restricted upon a subset of the managed
entities. The ORM cannot support committing subsets of the managed
entities while also guaranteeing data integrity, therefore this
utility was removed.
The `flush()` semantics will remain the same, but the change tracking will be performed
on all entities managed by the unit of work, and not just on the provided
`$entity` or `$entities`, as the parameter is now completely ignored.
The same applies to `UnitOfWork#commit($entity)`, which will simply be
`UnitOfWork#commit()`.
If you would still like to perform batching operations over small `UnitOfWork`
instances, it is suggested to follow these paths instead:
* eagerly use `EntityManager#clear()` in conjunction with a specific second level
cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/second-level-cache.html)
* use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/change-tracking-policies.html)
## Deprecated `YAML` mapping drivers.
If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** change to
annotation or XML drivers instead.
## Deprecated: `Doctrine\ORM\EntityManagerInterface#copy()`
Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is deprecated.
It will be removed in 3.0.
# Upgrade to 2.6
## Added `Doctrine\ORM\EntityRepository::count()` method
`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different
signature than `Countable::count()` (required parameter) and therefore are not compatible.
If your repository implemented the `Countable` interface, you will have to use
`$repository->count([])` instead and not implement `Countable` interface anymore.
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
Since it's just an utilitarian class and should not be inherited.
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
now has a required parameter `$pathExpr`.
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/orm/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500)
## PHP 7.1 is now required
Doctrine 2.6 now requires PHP 7.1 or newer.
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
- XCache support was dropped as it doesn't work with PHP 7.
# Upgrade to 2.5
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600).
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
As `$className` parameter was not used in the method, it was safely removed.
## Minor BC BREAK: query cache key time is now a float
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
instead of an integer in order to have more precision and also to be consistent
with the `TimestampCacheEntry#time`.
## Minor BC BREAK: discriminator map must now include all non-transient classes
It is now required that you declare the root of an inheritance in the
discriminator map.
When declaring an inheritance map, it was previously possible to skip the root
of the inheritance in the discriminator map. This was actually a validation
mistake by Doctrine2 and led to problems when trying to persist instances of
that class.
If you don't plan to persist instances some classes in your inheritance, then
either:
- make those classes `abstract`
- map those classes as `MappedSuperclass`
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
## Minor BC BREAK: Custom Hydrators API change
As of 2.5, `AbstractHydrator` does not enforce the usage of cache as part of
API, and now provides you a clean API for column information through the method
`hydrateColumnInfo($column)`.
Cache variable being passed around by reference is no longer needed since
Hydrators are per query instantiated since Doctrine 2.4.
## Minor BC BREAK: Entity based ``EntityManager#clear()`` calls follow cascade detach
Whenever ``EntityManager#clear()`` method gets called with a given entity class
name, until 2.4, it was only detaching the specific requested entity.
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
memory management since associations will be garbage collected, optimizing
resources consumption on long running jobs.
## BC BREAK: NamingStrategy interface changes
1. A new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)``
This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you
now also need to implement this new method.
2. A change to method ``joinColumnName()`` to include the $className
## Updates on entities scheduled for deletion are no longer processed
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
produce an UPDATE statement to be executed right before the DELETE statement. The entity in question
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
calculation logic is optimized away.
## Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
instead of the default READ COMMITTED transaction isolation level.
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
- ``Doctrine\ORM\EntityManager#find()``
- ``Doctrine\ORM\EntityRepository#find()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
You should update signatures for these methods if you have subclassed one of the above classes.
Please also check the calling code of these methods in your application and update if necessary.
**Note:**
This in fact is really a minor BC BREAK and should not have any affect on database vendors
other than SQL Server because it is the only one that supports and therefore cares about
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
## Minor BC BREAK: `__clone` method not called anymore when entities are instantiated via metadata API
As of PHP 5.6, instantiation of new entities is deferred to the
[`doctrine/instantiator`](https://github.com/doctrine/instantiator) library, which will avoid calling `__clone`
or any public API on instantiated objects.
## BC BREAK: `Doctrine\ORM\Repository\DefaultRepositoryFactory` is now `final`
Please implement the `Doctrine\ORM\Repository\RepositoryFactory` interface instead of extending
the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
## BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
array(
0=>array(
0=>{UserDTO object},
1=>{AddressDTO object},
2=>{u.id scalar},
3=>{u.name scalar},
4=>{a.street scalar},
5=>{a.postalCode scalar},
'addressId'=>{a.id scalar},
),
...
)
From now on, the resultset will look like this:
array(
0=>array(
'user'=>{UserDTO object},
'address'=>{AddressDTO object},
'addressId'=>{a.id scalar}
),
...
)
## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature
Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder()
# Upgrade to 2.4
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
In Doctrine 2.3 it was possible to use the new ``matching($criteria)``
functionality by adding constraints for assocations based on ID:
Criteria::expr()->eq('association', $assocation->getId());
This functionality does not work on InMemory collections however, because
in memory criteria compares object values based on reference.
As of 2.4 the above code will throw an exception. You need to change
offending code to pass the ``$assocation`` reference directly:
Criteria::expr()->eq('association', $assocation);
## Composer is now the default autoloader
The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated.
Support for GIT submodules is removed.
## OnFlush and PostFlush event always called
Before 2.4 the postFlush and onFlush events were only called when there were
actually entities that changed. Now these events are called no matter if there
are entities in the UoW or changes are found.
## Parenthesis are now considered in arithmetic expression
Before 2.4 parenthesis are not considered in arithmetic primary expression.
That's conceptually wrong, since it might result in wrong values. For example:
The DQL:
SELECT 100 / ( 2 * 2 ) FROM MyEntity
Before 2.4 it generates the SQL:
SELECT 100 / 2 * 2 FROM my_entity
Now parenthesis are considered, the previous DQL will generate:
SELECT 100 / (2 * 2) FROM my_entity
# Upgrade to 2.3
## Auto Discriminator Map breaks userland implementations with Listener
The new feature to detect discriminator maps automatically when none
are provided breaks userland implementations doing this with a
listener in ``loadClassMetadata`` event.
## EntityManager#find() not calls EntityRepository#find() anymore
Previous to 2.3, calling ``EntityManager#find()`` would be delegated to
``EntityRepository#find()``. This has lead to some unexpected behavior in the
core of Doctrine when people have overwritten the find method in their
repositories. That is why this behavior has been reversed in 2.3, and
``EntityRepository#find()`` calls ``EntityManager#find()`` instead.
## EntityGenerator add*() method generation
When generating an add*() method for a collection the EntityGenerator will now not
use the Type-Hint to get the singular for the collection name, but use the field-name
and strip a trailing "s" character if there is one.
## Merge copies non persisted properties too
When merging an entity in UoW not only mapped properties are copied, but also others.
## Query, QueryBuilder and NativeQuery parameters *BC break*
From now on, parameters in queries is an ArrayCollection instead of a simple array.
This affects heavily the usage of setParameters(), because it will not append anymore
parameters to query, but will actually override the already defined ones.
Whenever you are retrieving a parameter (ie. $query->getParameter(1)), you will
receive an instance of Query\Parameter, which contains the methods "getName",
"getValue" and "getType". Parameters are also only converted to when necessary, and
not when they are set.
Also, related functions were affected:
* execute($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance
* iterate($parameters, $hydrationMode) the argument $parameters can be either an key=>value array or an ArrayCollection instance
* setParameters($parameters) the argument $parameters can be either an key=>value array or an ArrayCollection instance
* getParameters() now returns ArrayCollection instead of array
* getParameter($key) now returns Parameter instance instead of parameter value
## Query TreeWalker method renamed
Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker,
you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin.
## New methods in TreeWalker interface *BC break*
Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations
including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the
above you must implement these new methods.
## Metadata Drivers
Metadata drivers have been rewritten to reuse code from `Doctrine\Persistence`. Anyone who is using the
`Doctrine\ORM\Mapping\Driver\Driver` interface should instead refer to
`Doctrine\Persistence\Mapping\Driver\MappingDriver`. Same applies to
`Doctrine\ORM\Mapping\Driver\AbstractFileDriver`: you should now refer to
`Doctrine\Persistence\Mapping\Driver\FileDriver`.
Also, following mapping drivers have been deprecated, please use their replacements in Doctrine\Common as listed:
* `Doctrine\ORM\Mapping\Driver\DriverChain` => `Doctrine\Persistence\Mapping\Driver\MappingDriverChain`
* `Doctrine\ORM\Mapping\Driver\PHPDriver` => `Doctrine\Persistence\Mapping\Driver\PHPDriver`
* `Doctrine\ORM\Mapping\Driver\StaticPHPDriver` => `Doctrine\Persistence\Mapping\Driver\StaticPHPDriver`
# Upgrade to 2.2
## ResultCache implementation rewritten
The result cache is completely rewritten and now works on the database result level, not inside the ORM AbstractQuery
anymore. This means that for result cached queries the hydration will now always be performed again, regardless of
the hydration mode. Affected areas are:
1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork
leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore.
2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result.
The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now
deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile`
instance with access to result cache driver, lifetime and cache key.
## EntityManager#getPartialReference() creates read-only entity
Entities returned from EntityManager#getPartialReference() are now marked as read-only if they
haven't been in the identity map before. This means objects of this kind never lead to changes
in the UnitOfWork.
## Fields omitted in a partial DQL query or a native query are never updated
Fields of an entity that are not returned from a partial DQL Query or native SQL query
will never be updated through an UPDATE statement.
## Removed support for onUpdate in @JoinColumn
The onUpdate foreign key handling makes absolutely no sense in an ORM. Additionally Oracle doesn't even support it. Support for it is removed.
## Changes in Annotation Handling
There have been some changes to the annotation handling in Common 2.2 again, that affect how people with old configurations
from 2.0 have to configure the annotation driver if they don't use `Configuration::newDefaultAnnotationDriver()`:
// Register the ORM Annotations in the AnnotationRegistry
AnnotationRegistry::registerFile('path/to/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
$reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
$driver = new AnnotationDriver($reader, (array)$paths);
$config->setMetadataDriverImpl($driver);
## Scalar mappings can now be omitted from DQL result
You are now allowed to mark scalar SELECT expressions as HIDDEN and 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/src/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)
================================================
FILE: ci/github/phpunit/mysqli.xml
================================================
../../../tests../../../srcperformancelocking_functional
================================================
FILE: ci/github/phpunit/pdo_mysql.xml
================================================
../../../tests../../../srcperformancelocking_functional
================================================
FILE: ci/github/phpunit/pdo_pgsql.xml
================================================
../../../tests../../../srcperformancelocking_functional
================================================
FILE: ci/github/phpunit/pdo_sqlite.xml
================================================
../../../tests../../../srcperformancelocking_functional
================================================
FILE: ci/github/phpunit/pgsql.xml
================================================
../../../tests../../../srcperformancelocking_functional
================================================
FILE: ci/github/phpunit/sqlite3.xml
================================================
../../../tests../../../srcperformancelocking_functional
================================================
FILE: composer.json
================================================
{
"name": "doctrine/orm",
"description": "Object-Relational-Mapper for PHP",
"license": "MIT",
"type": "library",
"keywords": [
"orm",
"database"
],
"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"
}
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"require": {
"php": "^8.1",
"ext-ctype": "*",
"composer-runtime-api": "^2",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^3.3.1 || ^4",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0",
"symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^14.0",
"phpbench/phpbench": "^1.0",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "2.1.23",
"phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^10.5.0 || ^11.5",
"psr/log": "^1 || ^2 || ^3",
"symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
},
"autoload": {
"psr-4": {
"Doctrine\\ORM\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Performance\\": "tests/Performance",
"Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis",
"Doctrine\\Tests\\": "tests/Tests"
}
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
}
}
================================================
FILE: docs/.gitignore
================================================
composer.lock
vendor/
output/
================================================
FILE: docs/LICENSE.md
================================================
The Doctrine ORM documentation is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
Creative Commons Legal Code
Attribution-NonCommercial-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(g) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
d. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, Noncommercial, ShareAlike.
e. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
f. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
g. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
h. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
i. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
j. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved, including but not limited to the
rights described in Section 4(e).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(d), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(d), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under: (i)
the terms of this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License").
You must include a copy of, or the URI, for Applicable License with
every copy of each Adaptation You Distribute or Publicly Perform. You
may not offer or impose any terms on the Adaptation that restrict the
terms of the Applicable License or the ability of the recipient of the
Adaptation to exercise the rights granted to that recipient under the
terms of the Applicable License. You must keep intact all notices that
refer to the Applicable License and to the disclaimer of warranties
with every copy of the Work as included in the Adaptation You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Adaptation, You may not impose any effective technological
measures on the Adaptation that restrict the ability of a recipient of
the Adaptation from You to exercise the rights granted to that
recipient under the terms of the Applicable License. This Section 4(b)
applies to the Adaptation as incorporated in a Collection, but this
does not require the Collection apart from the Adaptation itself to be
made subject to the terms of the Applicable License.
c. You may not exercise any of the rights granted to You in Section 3
above in any manner that is primarily intended for or directed toward
commercial advantage or private monetary compensation. The exchange of
the Work for other copyrighted works by means of digital file-sharing
or otherwise shall not be considered to be intended for or directed
toward commercial advantage or private monetary compensation, provided
there is no payment of any monetary compensation in con-nection with
the exchange of copyrighted works.
d. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and, (iv) consistent with Section 3(b), in the case of an Adaptation,
a credit identifying the use of the Work in the Adaptation (e.g.,
"French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by
this Section 4(d) may be implemented in any reasonable manner;
provided, however, that in the case of a Adaptation or Collection, at
a minimum such credit will appear, if a credit for all contributing
authors of the Adaptation or Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor reserves
the exclusive right to collect such royalties for any exercise by
You of the rights granted under this License if Your exercise of
such rights is for a purpose or use which is otherwise than
noncommercial as permitted under Section 4(c) and otherwise waives
the right to collect royalties through any statutory or compulsory
licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License that is for a
purpose or use which is otherwise than noncommercial as permitted
under Section 4(c).
f. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE
FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS
AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE
WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT
DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED
WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.
================================================
FILE: docs/README.md
================================================
# Doctrine ORM Documentation
The documentation is written in [ReStructured Text](https://docutils.sourceforge.io/rst.html).
## How to Generate:
In the project root, run
composer docs
This will generate the documentation into the `docs/output` subdirectory.
To browse the documentation, you need to run a webserver:
cd docs/output
php -S localhost:8000
Now the documentation is available at [http://localhost:8000](http://localhost:8000).
================================================
FILE: docs/composer.json
================================================
{
"name": "doctrine/orm-docs",
"description": "Documentation for the Object-Relational Mapper\"",
"type": "library",
"license": "MIT",
"require-dev": {
"doctrine/docs-builder": "^1.0"
}
}
================================================
FILE: docs/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst
================================================
Advanced field value conversion using custom mapping types
==========================================================
.. sectionauthor:: Jan Sorgalla
When creating entities, you sometimes have the need to transform field values
before they are saved to the database. In Doctrine you can use Custom Mapping
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.
This article describes the third way by implementing the MySQL specific column
type `Point `_.
The ``Point`` type is part of the `Spatial extension `_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a
longitude/latitude pair to represent a geographic location.
The entity
----------
We create a simple entity with a field ``$point`` which holds a value object
``Point`` representing the latitude and longitude of the position.
The entity class:
.. code-block:: php
point = $point;
}
public function getPoint(): Point
{
return $this->point;
}
public function setAddress(string $address): void
{
$this->address = $address;
}
public function getAddress(): string
{
return $this->address;
}
}
We use the custom type ``point`` in the ``#[Column]`` attribute of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
.. code-block:: php
latitude;
}
public function getLongitude(): float
{
return $this->longitude;
}
}
The mapping type
----------------
Now we're going to create the ``point`` type and implement all required methods.
.. code-block:: php
getLongitude(), $value->getLatitude());
}
return $value;
}
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('AsText(%s)', $sqlExpr);
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('PointFromText(%s)', $sqlExpr);
}
}
We do a 2-step conversion here. In the first step, we convert the ``Point``
object into a string representation before saving to the database (in the
``convertToDatabaseValue`` method) and back into an object after fetching the
value from the database (in the ``convertToPHPValue`` method).
The format of the string representation format is called
`Well-known text (WKT) `_.
The advantage of this format is, that it is both human readable and parsable by MySQL.
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
representation into its internal format.
This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
methods come into play.
This methods wrap a sql expression (the WKT representation of the Point) into
MySQL functions `ST_PointFromText `_
and `ST_AsText `_
which convert WKT strings to and from the internal format of MySQL.
.. note::
When using DQL queries, the ``convertToPHPValueSQL`` and
``convertToDatabaseValueSQL`` methods only apply to identification variables
and path expressions in SELECT clauses. Expressions in WHERE clauses are
**not** wrapped!
If you want to use Point values in WHERE clauses, you have to implement a
:doc:`user defined function ` for
``PointFromText``.
Example usage
-------------
.. code-block:: php
getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');
// Store a Location object
use Geo\Entity\Location;
use Geo\ValueObject\Point;
$location = new Location();
$location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA');
$location->setPoint(new Point(37.4220761, -122.0845187));
$em->persist($location);
$em->flush();
$em->clear();
// Fetch the Location object
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/** @var Geo\ValueObject\Point */
$point = $location->getPoint();
================================================
FILE: docs/en/cookbook/aggregate-fields.rst
================================================
Aggregate Fields
================
.. sectionauthor:: Benjamin Eberlei
You will often come across the requirement to display aggregate
values of data that can be computed by using the MIN, MAX, COUNT or
SUM SQL functions. For any ORM this is a tricky issue
traditionally. Doctrine ORM offers several ways to get access to
these values and this article will describe all of them from
different perspectives.
You will see that aggregate fields can become very explicit
features in your domain model and how this potentially complex
business rules can be easily tested.
An example model
----------------
Say you want to model a bank account and all their entries. Entries
into the account can either be of positive or negative money
values. Each account has a credit limit and the account is never
allowed to have a balance below that value.
For simplicity we live in a world where money is composed of
integers only. Also we omit the receiver/sender name, stated reason
for transfer and the execution date. These all would have to be
added on the ``Entry`` object.
Our entities look like:
.. code-block:: php
entries = new ArrayCollection();
}
}
#[ORM\Entity]
class Entry
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
public function __construct(
#[ORM\ManyToOne(targetEntity: Account::class, inversedBy: 'entries')]
private Account $account,
#[ORM\Column(type: 'integer')]
private int $amount,
) {
// more stuff here, from/to whom, stated reason, execution date and such
}
public function getAmount(): Amount
{
return $this->amount;
}
}
Using DQL
---------
The Doctrine Query Language allows you to select for aggregate
values computed from fields of your Domain Model. You can select
the current balance of your account by calling:
.. code-block:: php
createQuery($dql)
->setParameter(1, $myAccountId)
->getSingleScalarResult();
The ``$em`` variable in this (and forthcoming) example holds the
Doctrine ``EntityManager``. We create a query for the SUM of all
amounts (negative amounts are withdraws) and retrieve them as a
single scalar result, essentially return only the first column of
the first row.
This approach is simple and powerful, however it has a serious
drawback. We have to execute a specific query for the balance
whenever we need it.
To implement a powerful domain model we would rather have access to
the balance from our ``Account`` entity during all times (even if
the Account was not persisted in the database before!).
Also an additional requirement is the max credit per ``Account``
rule.
We cannot reliably enforce this rule in our ``Account`` entity with
the DQL retrieval of the balance. There are many different ways to
retrieve accounts. We cannot guarantee that we can execute the
aggregation query for all these use-cases, let alone that a
userland programmer checks this balance against newly added
entries.
Using your Domain Model
-----------------------
``Account`` and all the ``Entry`` instances are connected through a
collection, which means we can compute this value at runtime:
.. code-block:: php
entries as $entry) {
$balance += $entry->getAmount();
}
return $balance;
}
}
Now we can always call ``Account::getBalance()`` to access the
current account balance.
To enforce the max credit rule we have to implement the "Aggregate
Root" pattern as described in Eric Evans book on Domain Driven
Design. Described with one sentence, an aggregate root controls the
instance creation, access and manipulation of its children.
In our case we want to enforce that new entries can only added to
the ``Account`` by using a designated method. The ``Account`` is
the aggregate root of this relation. We can also enforce the
correctness of the bi-directional ``Account`` <-> ``Entry``
relation with this method:
.. code-block:: php
assertAcceptEntryAllowed($amount);
$this->entries[] = new Entry($this, $amount);
}
}
Now look at the following test-code for our entities:
.. code-block:: php
assertEquals(0, $account->getBalance());
$account->addEntry(500);
$this->assertEquals(500, $account->getBalance());
$account->addEntry(-700);
$this->assertEquals(-200, $account->getBalance());
}
public function testExceedMaxLimit()
{
$account = new Account("123456", maxCredit: 200);
$this->expectException(Exception::class);
$account->addEntry(-1000);
}
}
To enforce our rule we can now implement the assertion in
``Account::addEntry``:
.. code-block:: php
getBalance() + $amount;
$allowedMinimalBalance = ($this->maxCredit * -1);
if ($futureBalance < $allowedMinimalBalance) {
throw new Exception("Credit Limit exceeded, entry is not allowed!");
}
}
}
We haven't talked to the entity manager for persistence of our
account example before. You can call
``EntityManager::persist($account)`` and then
``EntityManager::flush()`` at any point to save the account to the
database. All the nested ``Entry`` objects are automatically
flushed to the database also.
.. code-block:: php
addEntry(500);
$account->addEntry(-200);
$em->persist($account);
$em->flush();
The current implementation has a considerable drawback. To get the
balance, we have to initialize the complete ``Account::$entries``
collection, possibly a very large one. This can considerably hurt
the performance of your application.
Using an Aggregate Field
------------------------
To overcome the previously mentioned issue (initializing the whole
entries collection) we want to add an aggregate field called
"balance" on the Account and adjust the code in
``Account::getBalance()`` and ``Account:addEntry()``:
.. code-block:: php
balance;
}
public function addEntry(int $amount): void
{
$this->assertAcceptEntryAllowed($amount);
$this->entries[] = new Entry($this, $amount);
$this->balance += $amount;
}
}
This is a very simple change, but all the tests still pass. Our
account entities return the correct balance. Now calling the
``Account::getBalance()`` method will not occur the overhead of
loading all entries anymore. Adding a new Entry to the
``Account::$entities`` will also not initialize the collection
internally.
Adding a new entry is therefore very performant and explicitly
hooked into the domain model. It will only update the account with
the current balance and insert the new entry into the database.
Tackling Race Conditions with Aggregate Fields
----------------------------------------------
Whenever you denormalize your database schema race-conditions can
potentially lead to inconsistent state. See this example:
.. code-block:: php
find(Account::class, $accId);
// request 2 account
$account2 = $em->find(Account::class, $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
// now request 1 and 2 both flush the changes.
The aggregate field ``Account::$balance`` is now -200, however the
SUM over all entries amounts yields -400. A violation of our max
credit rule.
You can use both optimistic or pessimistic locking to safe-guard
your aggregate fields against this kind of race-conditions. Reading
Eric Evans DDD carefully he mentions that the "Aggregate Root"
(Account in our example) needs a locking mechanism.
Optimistic locking is as easy as adding a version column:
.. code-block:: php
find(Account::class, $accId, LockMode::PESSIMISTIC_WRITE);
Keeping Updates and Deletes in Sync
-----------------------------------
The example shown in this article does not allow changes to the
value in ``Entry``, which considerably simplifies the effort to
keep ``Account::$balance`` in sync. If your use-case allows fields
to be updated or related entities to be removed you have to
encapsulate this logic in your "Aggregate Root" entity and adjust
the aggregate field accordingly.
Conclusion
----------
This article described how to obtain aggregate values using DQL or
your domain model. It showed how you can easily add an aggregate
field that offers serious performance benefits over iterating all
the related objects that make up an aggregate value. Finally I
showed how you can ensure that your aggregate fields do not get out
of sync due to race-conditions and concurrent access.
================================================
FILE: docs/en/cookbook/custom-mapping-types.rst
================================================
Custom Mapping Types
====================
Doctrine allows you to create new mapping types. This can come in
handy when you're missing a specific mapping type or when you want
to replace the existing implementation of a mapping type.
In order to create a new mapping type you need to subclass
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
you wish. Here is an example skeleton of such a custom type class:
.. code-block:: php
getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
When registering the custom types in the configuration you specify a unique
name for the mapping type and map that to the corresponding fully qualified
class name. Now the new type can be used when mapping columns:
.. code-block:: php
This recipe will show you a simple example of how you can use
Doctrine ORM to persist an implementation of the
`Decorator Pattern `_
Component
---------
The ``Component`` class needs to be persisted, so it's going to
be an ``Entity``. As the top of the inheritance hierarchy, it's going
to have to define the persistent inheritance. For this example, we
will use Single Table Inheritance, but Class Table Inheritance
would work as well. In the discriminator map, we will define two
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
.. code-block:: php
Component\ConcreteComponent::class,
'cd' => Decorator\ConcreteDecorator::class])]
abstract class Component
{
#[Id, Column]
#[GeneratedValue(strategy: 'AUTO')]
protected int|null $id = null;
#[Column(type: 'string', nullable: true)]
protected $name;
public function getId(): int|null
{
return $this->id;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
ConcreteComponent
-----------------
The ``ConcreteComponent`` class is pretty simple and doesn't do much
more than extend the abstract ``Component`` class (only for the
purpose of keeping this example simple).
.. code-block:: php
setDecorates($c);
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName(): string
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/** the component being decorated */
protected function getDecorates(): Component
{
return $this->decorates;
}
/** sets the component being decorated */
protected function setDecorates(Component $c): void
{
$this->decorates = $c;
}
}
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
cascade from the ``Decorator`` to the ``Component``. This means that
when we persist a ``Decorator``, Doctrine will take care of
persisting the chain of decorated objects for us. A ``Decorator`` can
be treated exactly as a ``Component`` when it comes time to
persisting it.
The ``Decorator's`` constructor accepts an instance of a
``Component``, as defined by the ``Decorator`` pattern. The
setDecorates/getDecorates methods have been defined as protected to
hide the fact that a ``Decorator`` is decorating a ``Component`` and
keeps the ``Component`` interface and the ``Decorator`` interface
identical.
To illustrate the intended result of the ``Decorator`` pattern, the
getName() method has been overridden to append a string to the
``Component's`` getName() method.
ConcreteDecorator
-----------------
The final class required to complete a simple implementation of the
Decorator pattern is the ``ConcreteDecorator``. In order to further
illustrate how the ``Decorator`` can alter data as it moves through
the chain of decoration, a new field, "special", has been added to
this class. The getName() has been overridden and appends the value
of the getSpecial() method to its return value.
.. code-block:: php
special = $special;
}
public function getSpecial(): string|null
{
return $this->special;
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName(): string
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
}
}
Examples
--------
Here is an example of how to persist and retrieve your decorated
objects
.. code-block:: php
setName('Test Component 1');
$em->persist($c); // assigned unique ID = 1
// create a new concrete decorator
$c = new ConcreteComponent();
$c->setName('Test Component 2');
$d = new ConcreteDecorator($c);
$d->setSpecial('Really');
$em->persist($d);
// assigns c as unique ID = 2, and d as unique ID = 3
$em->flush();
$c = $em->find('Test\Component', 1);
$d = $em->find('Test\Component', 3);
echo get_class($c);
// prints: Test\Component\ConcreteComponent
echo $c->getName();
// prints: Test Component 1
echo get_class($d)
// prints: Test\Component\ConcreteDecorator
echo $d->getName();
// prints: [Really] Decorated Test Component 2
================================================
FILE: docs/en/cookbook/dql-custom-walkers/InterpolateParametersSQLOutputWalker.php
================================================
getQuery()->getParameter($inputParam->name);
if ($parameter === null) {
return '?';
}
$value = $parameter->getValue();
/** @var ParameterType|ArrayParameterType|int|string $typeName */
/** @see \Doctrine\ORM\Query\ParameterTypeInferer::inferType() */
$typeName = $parameter->getType();
$platform = $this->getConnection()->getDatabasePlatform();
$processParameterType = static fn(ParameterType $type) => static fn($value): string =>
(match ($type) { /** @see Type::getBindingType() */
ParameterType::NULL => 'NULL',
ParameterType::INTEGER => $value,
ParameterType::BOOLEAN => (new BooleanType())->convertToDatabaseValue($value, $platform),
ParameterType::STRING, ParameterType::ASCII => $platform->quoteStringLiteral($value),
default => throw new ValueNotConvertible($value, $type->name)
});
if (is_string($typeName) && Type::hasType($typeName)) {
return Type::getType($typeName)->convertToDatabaseValue($value, $platform);
}
if ($typeName instanceof ParameterType) {
return $processParameterType($typeName)($value);
}
if ($typeName instanceof ArrayParameterType && is_array($value)) {
$type = ArrayParameterType::toElementParameterType($typeName);
return implode(', ', array_map($processParameterType($type), $value));
}
throw new ValueNotConvertible($value, $typeName);
}
}
================================================
FILE: docs/en/cookbook/dql-custom-walkers.rst
================================================
Extending DQL in Doctrine ORM: Custom AST Walkers
===============================================
.. sectionauthor:: Benjamin Eberlei
The Doctrine Query Language (DQL) is a proprietary sql-dialect that
substitutes tables and columns for Entity names and their fields.
Using DQL you write a query against the database using your
entities. With the help of the metadata you can write very concise,
compact and powerful queries that are then translated into SQL by
the Doctrine ORM.
In Doctrine 1 the DQL language was not implemented using a real
parser. This made modifications of the DQL by the user impossible.
Doctrine ORM in contrast has a real parser for the DQL language,
which transforms the DQL statement into an
`Abstract Syntax Tree `_
and generates the appropriate SQL statement for it. Since this
process is deterministic Doctrine heavily caches the SQL that is
generated from any given DQL query, which reduces the performance
overhead of the parsing process to zero.
You can modify the Abstract syntax tree by hooking into DQL parsing
process by adding a Custom Tree Walker. A walker is an interface
that walks each node of the Abstract syntax tree, thereby
generating the SQL statement.
There are two types of custom tree walkers that you can hook into
the DQL parser:
- An output walker. This one actually generates the SQL, and there
is only ever one of them. We implemented the default SqlWalker
implementation for it.
- A tree walker. There can be many tree walkers, they cannot
generate the SQL, however they can modify the AST before its
rendered to SQL.
Now this is all awfully technical, so let me come to some use-cases
fast to keep you motivated. Using walker implementation you can for
example:
- Modify the Output walker to get the raw SQL via ``Query->getSQL()``
with interpolated parameters.
- Modify the AST to generate a Count Query to be used with a
paginator for any given DQL query.
- Modify the Output Walker to generate vendor-specific SQL
(instead of ANSI).
- Modify the AST to add additional where clauses for specific
entities (example ACL, country-specific content...)
- Modify the Output walker to pretty print the SQL for debugging
purposes.
In this cookbook-entry I will show examples of the first three
points. There are probably much more use-cases.
Generic count query for pagination
----------------------------------
Say you have a blog and posts all with one category and one author.
A query for the front-page or any archive page might look something
like:
.. code-block:: sql
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now in this query the blog post is the root entity, meaning it's the
one that is hydrated directly from the query and returned as an
array of blog posts. In contrast the comment and author are loaded
for deeper use in the object tree.
A pagination for this query would want to approximate the number of
posts that match the WHERE clause of this query to be able to
predict the number of pages to show to the user. A draft of the DQL
query for pagination would look like:
.. code-block:: sql
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now you could go and write each of these queries by hand, or you
can use a tree walker to modify the AST for you. Let's see how the
API would look for this use-case:
.. code-block:: php
createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
The ``Paginate::count(Query $query)`` looks like:
.. code-block:: php
getEntityManager());
$countQuery->setDQL($query->getDQL());
$countQuery->setParameters(clone $query->getParameters());
foreach ($query->getHints() as $name => $value) {
$countQuery->setHint($name, $value);
}
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
This resets the limit clause first and max results
and registers the ``CountSqlWalker`` custom tree walker which
will modify the AST to execute a count query. The walkers
implementation is:
.. code-block:: php
_getQueryComponents() as $dqlAlias => $qComp) {
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
break;
}
}
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
$parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, true), null
)
);
}
}
This will delete any given select expressions and replace them with
a distinct count query for the root entities primary key. This will
only work if your entity has only one identifier field (composite
keys won't work).
Modify the Output Walker to generate Vendor specific SQL
--------------------------------------------------------
Most RMDBS have vendor-specific features for optimizing select
query execution plans. You can write your own output walker to
introduce certain keywords using the Query Hint API. A query hint
can be set via ``Query::setHint($name, $value)`` as shown in the
previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint.
We will implement a custom Output Walker that allows to specify the
``SQL_NO_CACHE`` query hint.
.. code-block:: php
createQuery($dql);
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
$query->setHint("mysqlWalker.sqlNoCache", true);
$results = $query->getResult();
Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
modify the generation of the SELECT clause, adding the
``SQL_NO_CACHE`` on those queries that need it:
.. code-block:: php
getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
if ($selectClause->isDistinct) {
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
} else {
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}
Writing extensions to the Output Walker requires a very deep
understanding of the DQL Parser and Walkers, but may offer your
huge benefits with using vendor specific features. This would still
allow you write DQL queries instead of NativeQueries to make use of
vendor specific features.
Modifying the Output Walker to get the raw SQL with interpolated parameters
---------------------------------------------------------------------------
Sometimes we may want to log or trace the raw SQL being generated from its DQL
for profiling slow queries afterwards or audit queries that changed many rows
``$query->getSQL()`` will give us the prepared statement being passed to database
with all values of SQL parameters being replaced by positional ``?`` or named ``:name``
as parameters are interpolated into prepared statements by the database while executing the SQL.
``$query->getParameters()`` will give us details about SQL parameters that we've provided.
So we can create an output walker to interpolate all SQL parameters that will be
passed into prepared statement in PHP before database handle them internally:
.. literalinclude:: dql-custom-walkers/InterpolateParametersSQLOutputWalker.php
:language: php
Then you may get the raw SQL with this output walker:
.. code-block:: php
where('t.int IN (:ints)')->setParameter(':ints', [1, 2])
->orWhere('t.string IN (?0)')->setParameter(0, ['3', '4'])
->orWhere("t.bool = ?1")->setParameter('?1', true)
->orWhere("t.string = :string")->setParameter(':string', 'ABC')
->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, InterpolateParametersSQLOutputWalker::class)
->getSQL();
The where clause of the returned SQL should be like:
.. code-block:: sql
WHERE t0_.int IN (1, 2)
OR t0_.string IN ('3', '4')
OR t0_.bool = 1
OR t0_.string = 'ABC'
================================================
FILE: docs/en/cookbook/dql-user-defined-functions.rst
================================================
DQL User Defined Functions
==========================
.. sectionauthor:: Benjamin Eberlei
By default DQL supports a limited subset of all the vendor-specific
SQL functions common between all the vendors. However in many cases
once you have decided on a specific database vendor, you will never
change it during the life of your project. This decision for a
specific vendor potentially allows you to make use of powerful SQL
features that are unique to the vendor.
It is worth to mention that Doctrine ORM also allows you to handwrite
your SQL instead of extending the DQL parser. Extending DQL is sort of an
advanced extension point. You can map arbitrary SQL to your objects
and gain access to vendor specific functionalities using the
``EntityManager#createNativeQuery()`` API as described in
the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the User-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
There are three types of functions in DQL, those that return a
numerical value, those that return a string and those that return a
Date. Your custom method has to be registered as either one of
those. The return type information is used by the DQL parser to
check possible syntax errors during the parsing process, for
example using a string function return value in a math expression.
Registering your own DQL functions
----------------------------------
You can register your functions adding them to the ORM
configuration:
.. code-block:: php
addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = new EntityManager($connection, $config);
The ``$name`` is the name the function will be referred to in the
DQL query. ``$class`` is a string of a class-name which has to
extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class
that offers all the necessary API and methods to implement a UDF.
Instead of providing the function class name, you can also provide
a callable that returns the function object:
.. code-block:: php
addCustomStringFunction($name, function () {
return new MyCustomFunction();
});
In this post we will implement some MySql specific Date calculation
methods, which are quite handy in my opinion:
Date Diff
---------
`Mysql's DateDiff function `_
takes two dates as argument and calculates the difference in days
with ``date1-date2``.
The DQL parser is a top-down recursive descent parser to generate
the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to
generate the appropriate SQL from the AST. This makes reading the
Parser/TreeWalker code manageable in a finite amount of time.
The ``FunctionNode`` class I referred to earlier requires you to
implement two methods, one for the parsing process (obviously)
called ``parse`` and one for the TreeWalker process called
``getSql()``. I show you the code for the DateDiff method and
discuss it step by step:
.. code-block:: php
match(TokenType::T_IDENTIFIER); // (2)
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(TokenType::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', ' .
$this->secondDateExpression->dispatch($sqlWalker) .
')'; // (7)
}
}
The Parsing process of the DATEDIFF function is going to find two
expressions the date1 and the date2 values, whose AST Node
representations will be saved in the variables of the DateDiff
FunctionNode instance at (1).
The parse() method has to cut the function call "DATEDIFF" and its
argument into pieces. Since the parser detects the function using a
lookahead the T\_IDENTIFIER of the function name has to be taken
from the stack (2), followed by a detection of the arguments in
(4)-(6). The opening and closing parenthesis have to be detected
also. This happens during the Parsing process and leads to the
generation of a DateDiff FunctionNode somewhere in the AST of the
dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the :ref:`DQL EBNF grammar
`
that matches our requirements for valid input into the DateDiff Dql
function. Picking the right tokens for your methods is a tricky
business, but the EBNF grammar is pretty helpful finding it, as is
looking at the Parser source code.
Now in the TreeWalker process we have to pick up this node and
generate SQL from it, which apparently is quite easy looking at the
code in (7). Since we don't know which type of AST Node the first
and second Date expression are we are just dispatching them back to
the SQL Walker to generate SQL from and then wrap our DATEDIFF
function call around this output.
Now registering this DateDiff FunctionNode with the ORM using:
.. code-block:: php
addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff');
We can do fancy stuff like:
.. code-block:: sql
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7
Date Add
--------
Often useful it the ability to do some simple date calculations in
your DQL query using
`MySql's DATE_ADD function `_.
I'll skip the blah and show the code for this function:
.. code-block:: php
match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_IDENTIFIER);
/** @var Lexer $lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' .
$this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit .
')';
}
}
The only difference compared to the DATEDIFF here is, we
additionally need the ``Lexer`` to access the value of the
``T_IDENTIFIER`` token for the Date Interval unit, for example the
MONTH in:
.. code-block:: sql
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created
The above method now only supports the specification using
``INTERVAL``, to also allow a real date in DATE\_ADD we need to add
some decision logic to the parsing process (makes up for a nice
exercise).
Now as you see, the Parsing process doesn't catch all the possible
SQL errors, here we don't match for all the valid inputs for the
interval unit. However where necessary we rely on the database
vendors SQL parser to show us further errors in the parsing
process, for example if the Unit would not be one of the supported
values by MySql.
Typed functions
---------------
By default, result of custom functions is fetched as-is from the database driver.
If you want to be sure that the type is always the same, then your custom function needs to
implement ``Doctrine\ORM\Query\AST\TypedExpression``. Then, the result is wired
through ``Doctrine\DBAL\Types\Type::convertToPhpValue()`` of the ``Type`` returned in ``getReturnType()``.
.. code-block:: php
`_.
================================================
FILE: docs/en/cookbook/entities-in-session.rst
================================================
Entities in the Session
=======================
There are several use-cases to save entities in the session, for example:
1. User data
2. Multi-step forms
To achieve this with Doctrine you have to pay attention to some details to get
this working.
Updating an entity
------------------
In Doctrine an entity objects has to be "managed" by an EntityManager to be
updatable. Entities saved into the session are not managed in the next request
anymore. This means that you have to update the entities with the stored session
data after you fetch the entities from the EntityManager again.
For a representative User object the code to get data from the session into a
managed Doctrine object can look like these examples:
Working with scalars
~~~~~~~~~~~~~~~~~~~~
In simpler applications there is no need to work with objects in sessions and you can use
separate session elements.
.. code-block:: php
find(User::class, $userId);
$user->setValue($_SESSION['storedValue']);
$em->flush();
}
Working with custom data transfer objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If objects are needed, we discourage the storage of entity objects in the session. It's
preferable to use a `DTO (data transfer object) `_
instead and merge the DTO data later with the entity.
.. code-block:: php
find(User::class, $userDto->getId());
$userEntity->populateFromDto($userDto);
$em->flush();
}
Serializing entity into the session
-----------------------------------
Entities that are serialized into the session normally contain references to
other entities as well. Think of the user entity has a reference to their
articles, groups, photos or many other different entities. If you serialize
this object into the session then you don't want to serialize the related
entities as well. This is why you shouldn't serialize an entity and use
only the needed values of it. This can happen with the help of a DTO.
.. code-block:: php
find("User", 1);
$userDto = new UserDto($user->getId(), $user->getFirstName(), $user->getLastName());
// or "UserDto::createFrom($user);", but don't store an entity in a property. Only its values without relations.
$_SESSION['user'] = $userDto;
================================================
FILE: docs/en/cookbook/generated-columns/Article.php
================================================
true])]
private array $content;
/**
* Because we specify NOT NULL, inserting will fail if the content does
* not have a string in the title field.
*/
#[ORM\Column(
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) generated always as (content->>'title') stored NOT NULL",
generated: 'ALWAYS',
)]
private string $title;
}
================================================
FILE: docs/en/cookbook/generated-columns/Person.php
================================================
`).
Be aware that specifying a column definition makes the ``SchemaTool``
completely ignore all other configuration for this column. See also
:ref:`#[Column] `
* ``generated``: Specifying that this column is always generated tells Doctrine
to update the field on the entity with the value from the database after
every write operation.
Advanced example: Extracting a value from a JSON structure
----------------------------------------------------------
Lets assume we have an entity that stores a blogpost as structured JSON.
To avoid extracting all titles on the fly when listing the posts, we create a
generated column with the field.
.. literalinclude:: generated-columns/Article.php
:language: php
================================================
FILE: docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst
================================================
Implementing ArrayAccess for Domain Objects
===========================================
.. sectionauthor:: Roman Borschel
This recipe will show you how to implement ArrayAccess for your
domain objects in order to allow more uniform access, for example
in templates. In these examples we will implement ArrayAccess on a
`Layer Supertype `_
for all our domain objects.
Option 1
--------
In this implementation we will make use of PHPs highly dynamic
nature to dynamically access properties of a subtype in a supertype
at runtime. Note that this implementation has 2 main caveats:
- It will not work with private fields
- It will not go through any getters/setters
.. code-block:: php
$offset);
}
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetUnset($offset) {
$this->$offset = null;
}
}
Option 2
--------
In this implementation we will dynamically invoke getters/setters.
Again we use PHPs dynamic nature to invoke methods on a subtype
from a supertype at runtime. This implementation has the following
caveats:
- It relies on a naming convention
- The semantics of offsetExists can differ
- offsetUnset will not work with typehinted setters
.. code-block:: php
{"get$offset"}();
return $value !== null;
}
public function offsetSet($offset, $value) {
$this->{"set$offset"}($value);
}
public function offsetGet($offset) {
return $this->{"get$offset"}();
}
public function offsetUnset($offset) {
$this->{"set$offset"}(null);
}
}
Read-only
---------
You can slightly tweak option 1 or option 2 in order to make array
access read-only. This will also circumvent some of the caveats of
each option. Simply make offsetSet and offsetUnset throw an
exception (i.e. BadMethodCallException).
.. code-block:: php
getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
In this case you have to ensure that each varchar field that is an enum in the
database only gets passed the allowed values. You can easily enforce this in your
entities:
.. code-block:: php
status = $status;
}
}
If you want to actively create enums through the Doctrine Schema-Tool by using
the **columnDefinition** attribute.
.. code-block:: php
"'".$val."'", $this->values);
return "ENUM(".implode(", ", $values).")";
}
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
{
return $value;
}
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
{
if (!in_array($value, $this->values, true)) {
throw new \InvalidArgumentException("Invalid '".$this->name."' value.");
}
return $value;
}
public function getName(): string
{
return $this->name;
}
}
With this base class you can define an enum as easily as:
.. code-block:: php
addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array());
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
Final Thoughts
--------------
With the ``ResolveTargetEntityListener``, we are able to decouple our
bundles, keeping them usable by themselves, but still being able to
define relationships between different objects. By using this method,
I've found my bundles end up being easier to maintain independently.
================================================
FILE: docs/en/cookbook/sql-table-prefixes.rst
================================================
SQL-Table Prefixes
==================
This recipe is intended as an example of implementing a
loadClassMetadata listener to provide a Table Prefix option for
your application. The method used below is not a hack, but fully
integrates into the Doctrine system, all SQL generated will include
the appropriate table prefix.
In most circumstances it is desirable to separate different
applications into individual databases, but in certain cases, it
may be beneficial to have a table prefix for your Entities to
separate them from other vendor products in the same database.
Implementing the listener
-------------------------
The listener in this example has been set up with the
DoctrineExtensions namespace. You create this file in your
library/DoctrineExtensions directory, but will need to set up
appropriate autoloaders.
.. code-block:: php
prefix = (string) $prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setPrimaryTable([
'name' => $this->prefix . $classMetadata->getTableName()
]);
}
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
$mappedTableName = $mapping['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}
Telling the EntityManager about our listener
--------------------------------------------
A listener of this type must be set up before the EntityManager has
been initialised, otherwise an Entity might be created or cached
before the prefix has been set.
.. note::
If you set this listener up, be aware that you will need
to clear your caches and drop then recreate your database schema.
.. code-block:: php
addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
================================================
FILE: docs/en/cookbook/strategy-cookbook-introduction.rst
================================================
Strategy-Pattern
================
This recipe will give you a short introduction on how to design
similar entities without using expensive (i.e. slow) inheritance
but with not more than *the well-known strategy pattern* event
listeners
Scenario / Problem
------------------
Given a Content-Management-System, we probably want to add / edit
some so-called "blocks" and "panels". What are they for?
- A block might be a registration form, some text content, a table
with information. A good example might also be a small calendar.
- A panel is by definition a block that can itself contain blocks.
A good example for a panel might be a sidebar box: You could easily
add a small calendar into it.
So, in this scenario, when building your CMS, you will surely add
lots of blocks and panels to your pages and you will find yourself
highly uncomfortable because of the following:
- Every existing page needs to know about the panels it contains -
therefore, you'll have an association to your panels. But if you've
got several types of panels - what do you do? Add an association to
every panel-type? This wouldn't be flexible. You might be tempted
to add an AbstractPanelEntity and an AbstractBlockEntity that use
class inheritance. Your page could then only confer to the
AbstractPanelType and Doctrine ORM would do the rest for you, i.e.
load the right entities. But - you'll for sure have lots of panels
and blocks, and even worse, you'd have to edit the discriminator
map *manually* every time you or another developer implements a new
block / entity. This would tear down any effort of modular
programming.
Therefore, we need something that's far more flexible.
Solution
--------
The solution itself is pretty easy. We will have one base class
that will be loaded via the page and that has specific behaviour -
a Block class might render the front-end and even the backend, for
example. Now, every block that you'll write might look different or
need different data - therefore, we'll offer an API to these
methods but internally, we use a strategy that exactly knows what
to do.
First of all, we need to make sure that we have an interface that
contains every needed action. Such actions would be rendering the
front-end or the backend, solving dependencies (blocks that are
supposed to be placed in the sidebar could refuse to be placed in
the middle of your page, for example).
Such an interface could look like this:
.. code-block:: php
blockStrategy. Will not be persisted by Doctrine ORM.
*
* @var BlockStrategyInterface
*/
protected $strategyInstance;
/**
* Returns the strategy that is used for this blockitem.
*
* The strategy itself defines how this block can be rendered etc.
*
* @return string
*/
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
* @return BlockStrategyInterface
*/
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
*
* @param BlockStrategyInterface $strategy
*/
public function setStrategy(BlockStrategyInterface $strategy) {
$this->strategyInstance = $strategy;
$this->strategyClassName = get_class($strategy);
$strategy->setBlockEntity($this);
}
Now, the important point is that $strategyClassName is a Doctrine ORM
field, i.e. Doctrine will persist this value. This is only the
class name of your strategy and not an instance!
Finishing your strategy pattern, we hook into the Doctrine postLoad
event and check whether a block has been loaded. If so, you will
initialize it - i.e. get the strategies classname, create an
instance of it and set it via setStrategyBlock().
This might look like this:
.. code-block:: php
view = $view;
}
public function getSubscribedEvents() {
return array(Events::postLoad);
}
public function postLoad(LifecycleEventArgs $args) {
$blockItem = $args->getObject();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
$strategy = $blockItem->getStrategyClassName();
$strategyInstance = new $strategy();
if (null !== $blockItem->getConfig()) {
$strategyInstance->setConfig($blockItem->getConfig());
}
$strategyInstance->setView($this->view);
$blockItem->setStrategy($strategyInstance);
}
}
}
In this example, even some variables are set - like a view object
or a specific configuration object.
================================================
FILE: docs/en/cookbook/validation-of-entities.rst
================================================
Validation of Entities
======================
.. sectionauthor:: Benjamin Eberlei
Doctrine ORM does not ship with any internal validators, the reason
being that we think all the frameworks out there already ship with
quite decent ones that can be integrated into your Domain easily.
What we offer are hooks to execute any kind of validation.
.. note::
You don't need to validate your entities in the lifecycle
events. It is only one of many options. Of course you can also
perform validations in value setters or any other method of your
entities that are used in your code.
Entities can register lifecycle event methods with Doctrine that
are called on different occasions. For validation we would need to
hook into the events called before persisting and updating. Even
though we don't support validation out of the box, the
implementation is even simpler than in Doctrine 1 and you will get
the additional benefit of being able to re-use your validation in
any other part of your domain.
Say we have an ``Order`` with several ``OrderLine`` instances. We
never want to allow any customer to order for a larger sum than they
are allowed to:
.. code-block:: php
customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines as $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
}
}
Now this is some pretty important piece of business logic in your
code, enforcing it at any time is important so that customers with
a unknown reputation don't owe your business too much money.
We can enforce this constraint in any of the metadata drivers.
First Attributes:
.. code-block:: php
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
Exception that happens in the lifecycle callbacks will be caught by
the EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null,
email-validation, string size, integer and date ranges in your
validation callbacks.
.. code-block:: php
plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}
}
}
What is nice about lifecycle events is, you can also re-use the
methods at other places in your domain, for example in combination
with your form library. Additionally there is no limitation in the
number of methods you register on one particular event, i.e. you
can register multiple methods for validation in "PrePersist" or
"PreUpdate" or mix and share them in any combinations between those
two events.
There is no limit to what you can and can't validate in
"PrePersist" and "PreUpdate" as long as you don't create new entity
instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :ref:`reference-events-lifecycle-events`
================================================
FILE: docs/en/cookbook/working-with-datetime.rst
================================================
Working with DateTime Instances
===============================
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
interesting pieces of information on how to work with PHP DateTime instances in ORM.
DateTime changes are detected by Reference
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities
and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object"))
these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database:
.. code-block:: php
updated->modify("now");
}
}
The way to go would be:
.. code-block:: php
updated = new DateTime("now");
}
}
Default Timezone Gotcha
~~~~~~~~~~~~~~~~~~~~~~~
By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that
is created by Doctrine will be assigned the timezone that is currently the default, either through
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
This is very important to handle correctly if your application runs on different servers or is moved from one to another server
(with different timezone settings). You have to make sure that the timezone is the correct one
on all this systems.
Handling different Timezones with the DateTime Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you first come across the requirement to save different timezones you may be still optimistic about how
to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine ORM)
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
in Databases `_.
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
in different offset directions depending on the real location.
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine ORM. However there is a workaround
that even allows correct date-time handling with timezones:
1. Always convert any DateTime instance to UTC.
2. Only set Timezones for displaying purposes
3. Save the Timezone in the Entity for persistence.
Say we have an application for an international postal company and employees insert events regarding postal-package
around the world, in their current timezones. To determine the exact time an event occurred means to save both
the UTC time at the time of the booking and the timezone the event happened in.
.. code-block:: php
setTimezone(self::getUtc());
}
return parent::convertToDatabaseValue($value, $platform);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value || $value instanceof \DateTime) {
return $value;
}
$converted = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
self::getUtc()
);
if (! $converted) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString()
);
}
return $converted;
}
private static function getUtc(): DateTimeZone
{
return self::$utc ??= new DateTimeZone('UTC');
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
to the current timezone that the passed DateTime instance has.
To actually use this new type instead of the default ``datetime`` type, you need to run following
code before bootstrapping the ORM:
.. code-block:: php
localized = true;
$this->created = $createDate;
$this->timezone = $createDate->getTimeZone()->getName();
}
public function getCreated()
{
if (!$this->localized) {
$this->created->setTimeZone(new \DateTimeZone($this->timezone));
}
return $this->created;
}
}
This snippet makes use of the previously discussed "changeset by reference only" property of
objects. That means a new DateTime will only be used during updating if the reference
changes between retrieval and flush operation. This means we can easily go and modify
the instance by setting the previous local timezone.
================================================
FILE: docs/en/index.rst
================================================
Welcome to Doctrine ORM's documentation!
==========================================
The Doctrine documentation is comprised of tutorials, a reference section and
cookbook articles that explain different parts of the Object Relational mapper.
Doctrine DBAL and Doctrine Common both have their own documentation.
Getting Help
------------
If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ ` with answers to frequent questions.
- Slack chat room `#orm `_
- Report a bug on `GitHub `_.
- On `StackOverflow `_
If you need more structure over the different topics you can browse the table
of contents.
Getting Started
---------------
* **Tutorial**:
:doc:`Getting Started with Doctrine `
* **Setup**:
:doc:`Installation & Configuration `
Mapping Objects onto a Database
-------------------------------
* **Mapping**:
:doc:`Objects ` \|
:doc:`Associations ` \|
:doc:`Inheritance `
* **Drivers**:
:doc:`Attributes ` \|
:doc:`XML ` \|
:doc:`PHP `
Working with Objects
--------------------
* **Basic Reference**:
:doc:`Entities ` \|
:doc:`Associations ` \|
:doc:`Events `
* **Query Reference**:
:doc:`DQL ` \|
:doc:`QueryBuilder ` \|
:doc:`Native SQL `
* **Internals**:
:doc:`Internals explained ` \|
:doc:`Associations `
Advanced Topics
---------------
* :doc:`Architecture `
* :doc:`Advanced Configuration `
* :doc:`Limitations and known issues `
* :doc:`Commandline Tools `
* :doc:`Transactions and Concurrency `
* :doc:`Filters `
* :doc:`NamingStrategy `
* :doc:`TypedFieldMapper `
* :doc:`Improving Performance `
* :doc:`Caching `
* :doc:`Partial Hydration `
* :doc:`Partial Objects `
* :doc:`Change Tracking Policies `
* :doc:`Best Practices `
* :doc:`Metadata Drivers `
* :doc:`Batch Processing `
* :doc:`Second Level Cache `
Tutorials
---------
* :doc:`Indexed associations `
* :doc:`Extra Lazy Associations `
* :doc:`Composite Primary Keys `
* :doc:`Ordered associations `
* :doc:`Pagination `
* :doc:`Override Field/Association Mappings In Subclasses `
* :doc:`Embeddables `
Changelogs
----------
* `Upgrade `_
Cookbook
--------
* **Patterns**:
:doc:`Aggregate Fields ` \|
:doc:`Generated/Virtual Columns ` \|
:doc:`Decorator Pattern ` \|
:doc:`Strategy Pattern `
* **DQL Extension Points**:
:doc:`DQL Custom Walkers ` \|
:doc:`DQL User-Defined-Functions `
* **Implementation**:
:doc:`Array Access ` \|
:doc:`Working with DateTime ` \|
:doc:`Validation ` \|
:doc:`Entities in the Session ` \|
:doc:`Keeping your Modules independent `
* **Hidden Gems**
:doc:`Prefixing Table Name `
* **Custom Datatypes**
:doc:`MySQL Enums `
:doc:`Custom Mapping Types `
:doc:`Advanced Field Value Conversion `
================================================
FILE: docs/en/reference/advanced-configuration.rst
================================================
Advanced Configuration
======================
The configuration of the EntityManager requires a
``Doctrine\ORM\Configuration`` instance as well as some database
connection parameters. This example shows all the potential
steps of configuration.
.. code-block:: php
setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
if (PHP_VERSION_ID > 80400) {
$config->enableNativeLazyObjects(true);
} else {
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
if ($applicationMode === "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
}
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite',
], $config);
$em = new EntityManager($connection, $config);
Doctrine and Caching
--------------------
Doctrine is optimized for working with caches. The main parts in Doctrine
that are optimized for caching are the metadata mapping information with
the metadata cache and the DQL to SQL conversions with the query cache.
These 2 caches require only an absolute minimum of memory yet they heavily
improve the runtime performance of Doctrine.
Doctrine does not bundle its own cache implementation anymore. Instead,
the PSR-6 standard interfaces are used to access the cache. In the examples
in this documentation, Symfony Cache is used as a reference implementation.
.. note::
Do not use Doctrine without a metadata and query cache!
Configuration Options
---------------------
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
.. _reference-native-lazy-objects:
Native Lazy Objects (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With PHP 8.4 we recommend that you use native lazy objects instead of
the code generation approach using the ``symfony/var-exporter`` Ghost trait.
With Doctrine 4, the minimal requirement will become PHP 8.4 and native lazy objects
will become the only approach to lazy loading.
.. code-block:: php
enableNativeLazyObjects(true);
Proxy Directory
~~~~~~~~~~~~~~~
Required except if you use native lazy objects with PHP 8.4.
This setting will be removed in the future.
.. code-block:: php
setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace
~~~~~~~~~~~~~~~
Required except if you use native lazy objects with PHP 8.4.
This setting will be removed in the future.
.. code-block:: php
setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
setMetadataDriverImpl($driver);
$config->getMetadataDriverImpl();
Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 3 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
Throughout the most part of this manual the AttributeDriver is
used in the examples. For information on the usage of the
XmlDriver please refer to the dedicated chapter ``XML Mapping``.
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
.. code-block:: php
setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the attribute
driver, because otherwise mass-operations on all entities through
the console could not work correctly. Metadata drivers can accept either
a single directory as a string or an array of directories.
AttributeDriver also accepts ``Doctrine\Persistence\Mapping\Driver\ClassLocator``,
allowing one to customize file discovery logic. You may choose to use Symfony Finder, or
utilize directory scan with ``FileClassLocator::createFromDirectories()``:
.. code-block:: php
setMetadataDriverImpl($driverImpl);
With this feature, you're empowered to provide a fine-grained iterator of only necessary
files to the Driver. For example, if you are using Vertical Slice architecture, you can
exclude ``*Test.php``, ``*Controller.php``, ``*Service.php``, etc.:
.. code-block:: php
files()->in($paths)
->name('*.php')
->notName(['*Test.php', '*Controller.php', '*Service.php']);
$classLocator = new FileClassLocator($finder);
If you know the list of class names you want to track, use
``Doctrine\Persistence\Mapping\Driver\ClassNames``:
.. code-block:: php
setMetadataDriverImpl($driverImpl);
Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
setMetadataCache($cache);
$config->getMetadataCache();
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via attributes,
xml, so that they do not need to be parsed and loaded from scratch on
every single request which is a waste of resources. The cache
implementation must implement the PSR-6
``Psr\Cache\CacheItemPoolInterface`` interface.
Usage of a metadata cache is highly recommended.
For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
Query Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
setQueryCache($cache);
$config->getQueryCache();
Gets or sets the cache implementation to use for caching DQL
queries, that is, the result of a DQL parsing process that includes
the final SQL as well as meta information about how to process the
SQL result set of a query. Note that the query cache does not
affect query results. You do not get stale data. This is a pure
optimization cache without any negative side-effects (except some
minimal memory usage in your cache).
Usage of a query cache is highly recommended.
For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
SQL Logger (**Optional**)
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
setSQLLogger($logger);
$config->getSQLLogger();
Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting is not required if you use native lazy objects with PHP 8.4
and will be removed in the future.
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
option that controls this behavior is:
.. code-block:: php
setAutoGenerateProxyClasses($mode);
Possible values for ``$mode`` are:
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
When you do this in a development environment,
be aware that you may get class/file not found errors if certain proxies
are not yet generated. You may also get failing lazy-loads if new
methods were added to the entity class that are not yet in the proxy class.
In such a case, simply use the Doctrine Console to (re)generate the
proxy classes.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
This strategy is only sane for development.
In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
``setAutoGenerateProxyClasses`` can accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
Development vs Production Configuration
---------------------------------------
You should code your Doctrine2 bootstrapping with two different
runtime models in mind. There are some serious benefits of using
APCu or Memcache in production. In development however this will
frequently give you fatal errors, when you change your entities and
the cache still keeps the outdated metadata. That is why we
recommend an array cache for development.
Furthermore you should have the Auto-generating Proxy Classes
option to true in development and to false in production. If this
option is set to ``TRUE`` it can seriously hurt your script
performance if several proxy classes are re-generated during script
execution. Filesystem calls of that magnitude can even slower than
all the database queries Doctrine issues. Additionally writing a
proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection
----------
The ``$connection`` passed as the first argument to the constructor of
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
to create such a connection. The DBAL configuration is explained in the
`DBAL section `_.
Proxy Objects
-------------
A proxy object is an object that is put in place or used instead of
the "real" object. A proxy object can add behavior to the object
being proxied without that object being aware of it. In ORM,
proxy objects are used to realize several features but mainly for
transparent lazy-loading.
Proxy objects with their lazy-loading facilities help to keep the
subset of objects that are already in memory connected to the rest
of the objects. This is an essential property as without it there
would always be fragile partial objects at the outer edges of your
object graph.
Doctrine ORM implements a variant of the proxy pattern where it
generates classes that extend your entity classes and adds
lazy-loading capabilities to them. Doctrine can then give you an
instance of such a proxy class whenever you request an object of
the class being proxied. This happens in two situations:
Reference Proxies
~~~~~~~~~~~~~~~~~
The method ``EntityManager#getReference($entityName, $identifier)``
lets you obtain a reference to an entity for which the identifier
is known, without necessarily loading that entity from the database.
This is useful, for example, as a performance enhancement, when you
want to establish an association to an entity for which you have the
identifier.
Consider the following example:
.. code-block:: php
getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Whether the object being returned from ``EntityManager#getReference()``
is a proxy or a direct instance of the entity class may depend on different
factors, including whether the entity has already been loaded into memory
or entity inheritance being used. But your code does not need to care
and in fact it **should not care**. Proxy objects should be transparent to your
code.
When using the ``EntityManager#getReference()`` method, you need to be aware
of a few peculiarities.
At the best case, the ORM can avoid querying the database at all. But, that
also means that this method will not throw an exception when an invalid value
for the ``$identifier`` parameter is passed. ``$identifier`` values are
not checked and there is no guarantee that the requested entity instance even
exists – the method will still return a proxy object.
Its only when the proxy has to be fully initialized or associations cannot
be written to the database that invalid ``$identifier`` values may lead to
exceptions.
The ``EntityManager#getReference()`` is mostly useful when you only
need a reference to some entity to make an association, like in the example
above. In that case, it can save you from loading data from the database
that you don't need. But remember – as soon as you read any property values
besides those making up the ID, a database request will be made to initialize
all fields.
Association proxies
~~~~~~~~~~~~~~~~~~~
The second most important situation where Doctrine uses proxy
objects is when querying for objects. Whenever you query for an
object that has a single-valued association to another object that
is configured LAZY, without joining that association in the same
query, Doctrine puts proxy objects in place where normally the
associated object would be. Just like other proxies it will
transparently initialize itself on first access.
.. note::
Joining an association in a DQL or native query
essentially means eager loading of that association in that query.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
In a production environment, it is highly recommended to use
``AUTOGENERATE_NEVER`` to allow for optimal performances.
However you will be required to generate the proxies manually
using the Doctrine Console:
.. code-block:: php
$ ./doctrine orm:generate-proxies
The other options are interesting in development environment:
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
a proxy directory. Proxies will be generated and written to file
on each request, so any modification to your code will be acknowledged.
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
proxy file. If your code changes, you will need to regenerate the
proxies manually.
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
but without writing them to disk.
Autoloading Proxies
-------------------
When you deserialize proxy objects from the session or any other storage
it is necessary to have an autoloading mechanism in place for these classes.
For implementation reasons Proxy class names are not PSR-0 compliant. This
means that you have to register a special autoloader for these classes:
.. code-block:: php
addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($phpDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
the fact that the driver loops through all namespaces and matches
the entity class name against the namespace using a
``strpos() === 0`` call. This means you need to order the drivers
correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (**OPTIONAL**)
-----------------------------------
Specifies the FQCN of a subclass of the EntityRepository.
That will be available for all entities without a custom repository class.
.. code-block:: php
setDefaultRepositoryClassName($fqcn);
$config->getDefaultRepositoryClassName();
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (**OPTIONAL**)
-----------------------------------
Specifies the Entity FQCNs to ignore.
SchemaTool will then skip these (e.g. when comparing schemas).
.. code-block:: php
setSchemaIgnoreClasses([$fqcn]);
$config->getSchemaIgnoreClasses();
Setting up the Console
----------------------
Doctrine uses the Symfony Console component for generating the command
line interface. You can take a look at the
:doc:`tools chapter <../reference/tools>` for inspiration how to setup the cli.
================================================
FILE: docs/en/reference/architecture.rst
================================================
Architecture
============
This chapter gives an overview of the overall architecture,
terminology and constraints of Doctrine ORM. It is recommended to
read this chapter carefully.
Using an Object-Relational Mapper
---------------------------------
As the term ORM already hints at, Doctrine ORM aims to simplify the
translation between database rows and the PHP object model. The
primary use case for Doctrine are therefore applications that
utilize the Object-Oriented Programming Paradigm. For applications
that do not primarily work with objects Doctrine ORM is not suited very
well.
Requirements
------------
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
-------------------
Doctrine ORM is divided into four main packages.
- `Collections `_
- `Event Manager `_
- `Persistence `_
- `DBAL `_
- ORM (depends on DBAL+Persistence+Collections)
This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Persistence packages. The Doctrine codebase
is split into these packages for a few reasons:
- to make things more maintainable and decoupled
- to allow you to use the code in Doctrine Persistence and Collections without the ORM or DBAL
- to allow you to use the DBAL without the ORM
Collection, Event Manager and Persistence
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Collection, Event Manager and Persistence packages contain highly
reusable components that have no dependencies beyond the packages
themselves (and PHP, of course). The root namespace of the Persistence
package is ``Doctrine\Persistence``. The root namespace of the
Collection package is ``Doctrine\Common\Collections``, for historical
reasons. The root namespace of the Event Manager package is just
``Doctrine\Common``, also for historical reasons.
The DBAL Package
~~~~~~~~~~~~~~~~
The DBAL package contains an enhanced database abstraction layer on
top of PDO but is not strongly bound to PDO. The purpose of this
layer is to provide a single API that bridges most of the
differences between the different RDBMS vendors. The root namespace
of the DBAL package is ``Doctrine\DBAL``.
The ORM Package
~~~~~~~~~~~~~~~
The ORM package contains the object-relational mapping toolkit that
provides transparent relational persistence for plain PHP objects.
The root namespace of the ORM package is ``Doctrine\ORM``.
Terminology
-----------
.. _terminology_entities:
Entities
~~~~~~~~
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class can be final or read-only when
you use :ref:`native lazy objects `.
It may contain final methods or read-only properties too.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
entities. Entities may extend non-entity classes as well as entity
classes, and non-entity classes may extend entity classes.
.. note::
The constructor of an entity is only ever invoked when
*you* construct a new instance with the *new* keyword. Doctrine
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Mapped Superclasses
~~~~~~~~~~~~~~~~~~~
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity.
Mapped superclasses are explained in greater detail in the chapter
on :doc:`inheritance mapping `.
Transient Classes
~~~~~~~~~~~~~~~~~
The term "transient class" appears in some places in the mapping
drivers as well as the code dealing with metadata handling.
A transient class is a class that is neither an entity nor a mapped
superclass. From the ORM's point of view, these classes can be
completely ignored, and no class metadata is loaded for them at all.
Entity states
~~~~~~~~~~~~~
An entity instance can be characterized as being NEW, MANAGED,
DETACHED or REMOVED.
- A NEW entity instance has no persistent identity, and is not yet
associated with an EntityManager and a UnitOfWork (i.e. those just
created with the "new" operator).
- A MANAGED entity instance is an instance with a persistent
identity that is associated with an EntityManager and whose
persistence is thus managed.
- A DETACHED entity instance is an instance with a persistent
identity that is not (or no longer) associated with an
EntityManager and a UnitOfWork.
- A REMOVED entity instance is an instance with a persistent
identity, associated with an EntityManager, that will be removed
from the database upon transaction commit.
.. _architecture_persistent_fields:
Persistent fields
~~~~~~~~~~~~~~~~~
The persistent state of an entity is represented by instance
variables. An instance variable must be directly accessed only from
within the methods of the entity by the entity instance itself.
Instance variables must not be accessed by clients of the entity.
The state of the entity is available to clients only through the
entity’s methods, i.e. accessor methods (getter/setter methods) or
other business methods.
Collection-valued persistent fields and properties must be defined
in terms of the ``Doctrine\Common\Collections\Collection``
interface. The collection implementation type may be used by the
application to initialize fields or properties before the entity is
made persistent. Once the entity becomes managed (or detached),
subsequent access must be through the interface type.
Serializing entities
~~~~~~~~~~~~~~~~~~~~
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an EntityManager.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialized anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.
The EntityManager
~~~~~~~~~~~~~~~~~
The ``EntityManager`` class is a central access point to the
functionality provided by Doctrine ORM. The ``EntityManager`` API is
used to manage the persistence of your objects and to query for
persistent objects.
Transactional write-behind
~~~~~~~~~~~~~~~~~~~~~~~~~~
An ``EntityManager`` and the underlying ``UnitOfWork`` employ a
strategy called "transactional write-behind" that delays the
execution of SQL statements in order to execute them in the most
efficient way and to execute them at the end of a transaction so
that all write locks are quickly released. You should see Doctrine
as a tool to synchronize your in-memory objects with the database
in well defined units of work. Work with your objects and modify
them as usual and when you're done call ``EntityManager#flush()``
to make your changes persistent.
.. _unit-of-work:
The Unit of Work
~~~~~~~~~~~~~~~~
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
typical implementation of the
`Unit of Work pattern `_,
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.
================================================
FILE: docs/en/reference/association-mapping.rst
================================================
Association Mapping
===================
This chapter explains mapping associations between objects.
Instead of working with foreign keys in your code, you will always work with
references to objects instead and Doctrine will convert those references
to foreign keys internally.
- A reference to a single object is represented by a foreign key.
- A collection of objects is represented by many foreign keys pointing to the object holding the collection
This chapter is split into three different sections.
- A list of all the possible association mapping use-cases is given.
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
- :ref:`collections` are introduced that contain entities in associations.
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
- OneToMany - One instance of the current Entity has Many instances (references) to the referred Entity.
- ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.
- OneToOne - One instance of the current Entity refers to One instance of the referred Entity.
See below for all the possible relations.
An association is considered to be unidirectional if only one side of the association has
a property referring to the other side.
To gain a full understanding of associations you should also read about :doc:`owning and
inverse sides of associations `
Many-To-One, Unidirectional
---------------------------
A many-to-one association is the most common association between objects. Example: Many Users have One Address:
.. configuration-block::
.. code-block:: attribute
.. note::
The above ``#[JoinColumn]`` is optional as it would default
to ``address_id`` and ``id`` anyways. You can omit it and let it
use the defaults.
Likewise, inside the ``#[ManyToOne]`` attribute you can omit the
``targetEntity`` argument and it will default to ``Address``.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
address_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Address (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);
One-To-One, Unidirectional
--------------------------
Here is an example of a one-to-one association with a ``Product`` entity that
references one ``Shipment`` entity.
.. configuration-block::
.. code-block:: attribute
Note that the ``#[JoinColumn]`` is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
shipment_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipment (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
One-To-One, Bidirectional
-------------------------
Here is a one-to-one relationship between a ``Customer`` and a
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
it is bidirectional.
Here we see the ``mappedBy`` and ``inversedBy`` attributes for the first time.
They are used to tell Doctrine which property on the other side refers to the
object.
.. configuration-block::
.. code-block:: attribute
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE Cart (
id INT AUTO_INCREMENT NOT NULL,
customer_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_BA388B79395C3F3 (customer_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Customer (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);
We had a choice of sides on which to place the ``inversedBy`` attribute. Because it
is on the ``Cart``, that is the owning side of the relation, and thus holds the
foreign key.
One-To-One, Self-referencing
----------------------------
You can define a self-referencing one-to-one relationships like
below.
.. code-block:: php
*/
#[OneToMany(targetEntity: Feature::class, mappedBy: 'product')]
private Collection $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
#[Entity]
class Feature
{
// ...
/** Many features have one product. This is the owning side. */
#[ManyToOne(targetEntity: Product::class, inversedBy: 'features')]
#[JoinColumn(name: 'product_id', referencedColumnName: 'id')]
private Product|null $product = null;
// ...
}
.. code-block:: xml
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Feature (
id INT AUTO_INCREMENT NOT NULL,
product_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);
One-To-Many, Unidirectional with Join Table
-------------------------------------------
A unidirectional one-to-many association can be mapped through a
join table. From Doctrine's point of view, it is simply mapped as a
unidirectional many-to-many whereby a unique constraint on one of
the join columns enforces the one-to-many cardinality.
The following example sets up such a unidirectional one-to-many association:
.. configuration-block::
.. code-block:: attribute
*/
#[JoinTable(name: 'users_phonenumbers')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)]
#[ManyToMany(targetEntity: 'Phonenumber')]
private Collection $phonenumbers;
public function __construct()
{
$this->phonenumbers = new ArrayCollection();
}
// ...
}
#[Entity]
class Phonenumber
{
// ...
}
.. code-block:: xml
Generates the following MySQL Schema:
.. code-block:: sql
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_phonenumbers (
user_id INT NOT NULL,
phonenumber_id INT NOT NULL,
UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id),
PRIMARY KEY(user_id, phonenumber_id)
) ENGINE = InnoDB;
CREATE TABLE Phonenumber (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
One-To-Many, Self-referencing
-----------------------------
You can also setup a one-to-many association that is
self-referencing. In this example we setup a hierarchy of
``Category`` objects by creating a self referencing relationship.
This effectively models a hierarchy of categories and from the
database perspective is known as an adjacency list approach.
.. configuration-block::
.. code-block:: attribute
*/
#[OneToMany(targetEntity: Category::class, mappedBy: 'parent')]
private Collection $children;
/** Many Categories have One Category. */
#[ManyToOne(targetEntity: Category::class, inversedBy: 'children')]
#[JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
private Category|null $parent = null;
// ...
public function __construct() {
$this->children = new ArrayCollection();
}
}
.. code-block:: xml
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE Category (
id INT AUTO_INCREMENT NOT NULL,
parent_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id);
Many-To-Many, Unidirectional
----------------------------
Real many-to-many associations are less common. The following
example shows a unidirectional association between User and Group
entities:
.. configuration-block::
.. code-block:: attribute
*/
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
public function __construct() {
$this->groups = new ArrayCollection();
}
}
#[Entity]
class Group
{
// ...
}
.. code-block:: xml
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE users_groups (
user_id INT NOT NULL,
group_id INT NOT NULL,
PRIMARY KEY(user_id, group_id)
) ENGINE = InnoDB;
CREATE TABLE Group (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id);
.. note::
Why are many-to-many associations less common? Because
frequently you want to associate additional attributes with an
association, in which case you introduce an association class.
Consequently, the direct many-to-many association disappears and is
replaced by one-to-many/many-to-one associations between the 3
participating classes.
.. note::
For many-to-many associations, the ORM takes care of managing rows
in the join table connecting both sides. Due to the way it deals
with entity removals, database-level constraints may not work the
way one might intuitively assume. Thus, be sure not to miss the section
on :ref:`join table management `.
Many-To-Many, Bidirectional
---------------------------
Here is a similar many-to-many relationship as above except this
one is bidirectional.
.. configuration-block::
.. code-block:: attribute
*/
#[ManyToMany(targetEntity: Group::class, inversedBy: 'users')]
#[JoinTable(name: 'users_groups')]
private Collection $groups;
public function __construct() {
$this->groups = new ArrayCollection();
}
// ...
}
#[Entity]
class Group
{
// ...
/**
* Many Groups have Many Users.
* @var Collection
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'groups')]
private Collection $users;
public function __construct() {
$this->users = new ArrayCollection();
}
// ...
}
.. code-block:: xml
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
.. note::
For many-to-many associations, the ORM takes care of managing rows
in the join table connecting both sides. Due to the way it deals
with entity removals, database-level constraints may not work the
way one might intuitively assume. Thus, be sure not to miss the section
on :ref:`join table management `.
Owning and Inverse Side on a ManyToMany Association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For Many-To-Many associations you can chose which entity is the
owning and which the inverse side. There is a very simple semantic
rule to decide which side is more suitable to be the owning side
from a developers perspective. You only have to ask yourself which
entity is responsible for the connection management, and pick that
as the owning side.
Take an example of two entities ``Article`` and ``Tag``. Whenever
you want to connect an Article to a Tag and vice-versa, it is
mostly the Article that is responsible for this relation. Whenever
you add a new article, you want to connect it with existing or new
tags. Your "Create Article" form will probably support this notion
and allow specifying the tags directly. This is why you should pick
the Article as owning side, as it makes the code more
understandable:
.. code-block:: php
addArticle($this); // synchronously updating inverse side
$this->tags[] = $tag;
}
}
class Tag
{
private Collection $articles;
public function addArticle(Article $article): void
{
$this->articles[] = $article;
}
}
This allows to group the tag adding on the ``Article`` side of the
association:
.. code-block:: php
addTag($tagA);
$article->addTag($tagB);
Many-To-Many, Self-referencing
------------------------------
You can even have a self-referencing many-to-many association. A
common scenario is where a ``User`` has friends and the target
entity of that relationship is a ``User`` so it is self
referencing. In this example it is bidirectional so ``User`` has a
field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
private Collection $friendsWithMe;
/**
* Many Users have many Users.
* @var Collection
*/
#[JoinTable(name: 'friends')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
private Collection $myFriends;
public function __construct() {
$this->friendsWithMe = new ArrayCollection();
$this->myFriends = new ArrayCollection();
}
// ...
}
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE friends (
user_id INT NOT NULL,
friend_user_id INT NOT NULL,
PRIMARY KEY(user_id, friend_user_id)
) ENGINE = InnoDB;
ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id);
.. _association_mapping_defaults:
Mapping Defaults
----------------
The ``@JoinColumn`` and ``@JoinTable`` definitions are usually optional and have
sensible default values. The defaults for a join column in a
one-to-one/many-to-one association is as follows:
::
name: "_id"
referencedColumnName: "id"
As an example, consider this mapping:
.. configuration-block::
.. code-block:: attribute
This is essentially the same as the following, more verbose,
mapping:
.. configuration-block::
.. code-block:: attribute
The @JoinTable definition used for many-to-many mappings has
similar defaults. As an example, consider this mapping:
.. configuration-block::
.. code-block:: attribute
*/
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: xml
This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
.. code-block:: attribute
*/
#[JoinTable(name: 'User_Group')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: xml
In that case, the name of the join table defaults to a combination
of the simple, unqualified class names of the participating
classes, separated by an underscore character. The names of the
join columns default to the simple, unqualified class name of the
targeted class followed by "\_id". The referencedColumnName always
defaults to "id", just as in one-to-one or many-to-one mappings.
Additionally, when using typed properties with Doctrine 2.9 or newer
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
associations as they will be set based on type. So that:
.. configuration-block::
.. code-block:: attribute
Is essentially the same as following:
.. configuration-block::
.. code-block:: attribute
If you accept these defaults, you can reduce the mapping code to a
minimum.
.. _collections:
Collections
-----------
Unfortunately, PHP arrays, while being great for many things, are missing
features that make them suitable for lazy loading in the context of an ORM.
This is why in all the examples of many-valued associations in this manual we
will make use of a ``Collection`` interface and its
default implementation ``ArrayCollection`` that are both defined in the
``Doctrine\Common\Collections`` namespace. A collection implements
the PHP interfaces ``ArrayAccess``, ``Traversable`` and ``Countable``.
.. note::
The Collection interface and ArrayCollection class,
like everything else in the Doctrine namespace, are neither part of
the ORM, nor the DBAL, it is a plain PHP class that has no outside
dependencies apart from dependencies on PHP itself (and the SPL).
Therefore using this class in your model and elsewhere
does not introduce a coupling to the ORM.
Initializing Collections
------------------------
You should always initialize the collections of your ``@OneToMany``
and ``@ManyToMany`` associations in the constructor of your entities:
.. code-block:: php
groups = new ArrayCollection();
}
public function getGroups(): Collection
{
return $this->groups;
}
}
The following code will then work even if the Entity hasn't
been associated with an EntityManager yet:
.. code-block:: php
getGroups()->add($group);
================================================
FILE: docs/en/reference/attributes-reference.rst
================================================
Attributes Reference
====================
PHP 8 adds native support for metadata with its "Attributes" feature.
Doctrine ORM provides support for mapping metadata using PHP attributes as of version 2.9.
The attributes metadata support is closely modelled after the already
existing and now removed annotation metadata supported since the first
version 2.0.
Index
-----
- :ref:`#[AssociationOverride] `
- :ref:`#[AttributeOverride] `
- :ref:`#[Column] `
- :ref:`#[Cache] `
- :ref:`#[ChangeTrackingPolicy] `
- :ref:`#[CustomIdGenerator] `
- :ref:`#[DiscriminatorColumn] `
- :ref:`#[DiscriminatorMap] `
- :ref:`#[Embeddable] `
- :ref:`#[Embedded] `
- :ref:`#[Entity] `
- :ref:`#[GeneratedValue] `
- :ref:`#[HasLifecycleCallbacks] `
- :ref:`#[Index] `
- :ref:`#[Id] `
- :ref:`#[InheritanceType] `
- :ref:`#[JoinColumn] `
- :ref:`#[JoinTable] `
- :ref:`#[ManyToOne] `
- :ref:`#[ManyToMany] `
- :ref:`#[MappedSuperclass] `
- :ref:`#[OneToOne] `
- :ref:`#[OneToMany] `
- :ref:`#[OrderBy] `
- :ref:`#[PostLoad] `
- :ref:`#[PostPersist] `
- :ref:`#[PostRemove] `
- :ref:`#[PostUpdate] `
- :ref:`#[PrePersist] `
- :ref:`#[PreRemove] `
- :ref:`#[PreUpdate] `
- :ref:`#[SequenceGenerator] `
- :ref:`#[Table] `
- :ref:`#[UniqueConstraint] `
- :ref:`#[Version] `
Reference
---------
.. _attrref_associationoverride:
#[AssociationOverride]
~~~~~~~~~~~~~~~~~~~~~~
In an inheritance hierarchy this attribute allows to override the
assocation mapping definitions of the parent mappings. It needs to be nested
within a ``#[AssociationOverrides]`` on the class level.
Required parameters:
- **name**: Name of the association mapping to overwrite.
Optional parameters:
- **joinColumns**: A list of nested ``#[JoinColumn]`` declarations.
- **joinTable**: A nested ``#[JoinTable]`` declaration in case of a many-to-many overwrite.
- **inversedBy**: The name of the inversedBy field on the target entity side.
- **fetch**: The fetch strategy, one of: EAGER, LAZY, EXTRA_LAZY.
Examples:
.. code-block:: php
`.
If not specified, default value is ``false``.
- **nullable**: Determines if NULL values allowed for this column.
If not specified, default value is ``false``.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
included when updating the row of the underlying entities table.
If not specified, default value is true.
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
used after an INSERT or UPDATE statement to determine if the database
generated this value and it needs to be fetched using a SELECT statement.
- **options**: Array of additional options:
- ``default``: The default value to set for the column if no value
is supplied.
- ``unsigned``: Boolean value to determine if the column should
be capable of representing only non-negative integers
(applies only for integer column and might not be supported by
all vendors).
- ``fixed``: Boolean value to determine if the specified length of
a string column should be fixed or varying (applies only for
string/binary column and might not be supported by all vendors).
- ``comment``: The comment of the column in the schema (might not
be supported by all vendors).
- ``charset``: The charset of the column (only supported by Mysql, PostgreSQL, Sqlite and SQLServer).
- ``collation``: The collation of the column (only supported by Mysql, PostgreSQL, Sqlite and SQLServer).
- ``check``: Adds a check constraint type to the column (might not
be supported by all vendors).
- **columnDefinition**: Specify the DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
However, as this needs to be specified in the DDL native to the database,
the resulting schema changes are no longer portable. If you specify a
``columnDefinition``, the ``SchemaTool`` ignores all other attributes
that are normally used to build the definition DDL. Changes to the
``columnDefinition`` are not detected, you will need to manually create a
migration to apply changes.
Additionally you should remember that the ``type``
attribute still handles the conversion between PHP and Database
values. If you use this attribute on a column that is used for
joins between tables you should also take a look at
:ref:`#[JoinColumn] `.
.. note::
For more detailed information on each attribute, please refer to
the DBAL ``Schema-Representation`` documentation.
Examples:
.. code-block:: php
true,
"comment" => "Initial letters of first and last name"
])]
protected $initials;
#[Column(
type: "integer",
name: "login_count",
nullable: false,
options: ["unsigned" => true, "default" => 0]
)]
protected $loginCount;
// columnDefinition is raw SQL, not DQL. This example works for MySQL:
#[Column(
type: "string",
name: "user_fullname",
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstname,' ',lastname))",
insertable: false,
updatable: false
)]
protected $fullname;
.. _attrref_cache:
#[Cache]
~~~~~~~~
Add caching strategy to a root entity or a collection.
Optional parameters:
- **usage**: One of ``READ_ONLY``, ``READ_WRITE`` or ``NONSTRICT_READ_WRITE``, By default this is ``READ_ONLY``.
- **region**: An specific region name
.. _attrref_changetrackingpolicy:
#[ChangeTrackingPolicy]
~~~~~~~~~~~~~~~~~~~~~~~
The Change Tracking Policy attribute allows to specify how the
Doctrine ORM ``UnitOfWork`` should detect changes in properties of
entities during flush. By default each entity is checked according
to a deferred implicit strategy, which means upon flush ``UnitOfWork``
compares all the properties of an entity to a previously stored
snapshot. This works out of the box, however you might want to
tweak the flush performance where using another change tracking
policy is an interesting option.
The :doc:`details on all the available change tracking policies `
can be found in the configuration section.
Example:
.. code-block:: php
` and :ref:`#[GeneratedValue(strategy: "CUSTOM")] ` are specified.
Required parameters:
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
Example:
.. code-block:: php
`
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
- **options**: See "options" attribute on :ref:`#[Column] `.
.. _attrref_discriminatormap:
#[DiscriminatorMap]
~~~~~~~~~~~~~~~~~~~
The discriminator map is a required attribute on the
root entity class in an inheritance hierarchy. Its only argument is an
array which defines which class should be saved under
which name in the database. Keys are the database value and values
are the classes, either as fully- or as unqualified class names
depending on whether the classes are in the namespace or not.
.. code-block:: php
Person::class, "employee" => Employee::class])]
class Person
{
// ...
}
.. _attrref_embeddable:
#[Embeddable]
~~~~~~~~~~~~~
The embeddable attribute is required on a class, in order to make it
embeddable inside an entity. It works together with the :ref:`#[Embedded] `
attribute to establish the relationship between the two classes.
.. code-block:: php
`. This
attribute is optional and only has meaning when used in
conjunction with #[Id].
If this attribute is not specified with ``#[Id]`` the ``NONE`` strategy is
used as default.
Optional parameters:
- **strategy**: Set the name of the identifier generation strategy.
Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``CUSTOM`` and
``NONE``. If not specified, the default value is ``AUTO``.
Example:
.. code-block:: php
"((category IS NOT NULL))"
]
)]
class ECommerceProduct
{
}
.. _attrref_id:
#[Id]
~~~~~
The annotated instance variable will be marked as entity
identifier, the primary key in the database. This attribute is a
marker only and has no required or optional attributes. For
entities that have multiple identifier columns each column has to
be marked with ``#[Id]``.
Example:
.. code-block:: php
` and
:ref:`#[DiscriminatorColumn] ` attributes.
Examples:
.. code-block:: php
"Person", "employee" => "Employee"])]
class Person
{
// ...
}
#[Entity]
#[InheritanceType("JOINED")]
#[DiscriminatorColumn(name: "discr", type: "string")]
#[DiscriminatorMap(["person" => "Person", "employee" => "Employee"])]
class Person
{
// ...
}
.. _attrref_joincolumn:
#[JoinColumn], #[InverseJoinColumn]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This attribute is used in the context of relations in
:ref:`#[ManyToOne] `, :ref:`#[OneToOne] ` fields
and in the Context of a :ref:`#[ManyToMany] `. If this attribute or both *name* and *referencedColumnName*
are missing they will be computed considering the field's name and the current
:doc:`naming strategy `.
Optional parameters:
- **name**: Column name that holds the foreign key identifier for
this relation. In the context of ``#[JoinTable]`` it specifies the column
name in the join table.
- **referencedColumnName**: Name of the primary key identifier that
is used for joining of this relation. Defaults to ``id``.
- **unique**: Determines whether this relation is exclusive between the
affected entities and should be enforced as such on the database
constraint level. Defaults to false.
- **deferrable**: Determines whether this relation constraint can be deferred. Defaults to false.
- **nullable**: Determine whether the related entity is required, or if
null is an allowed state for the relation. Defaults to true.
- **onDelete**: Cascade Action (Database-level)
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute enables the use of advanced RMDBS features. Note that you
need to reference columns by their database name (either explicitly set in
the mapping or per the current :doc:`naming strategy `).
Using this attribute on ``#[JoinColumn]`` is necessary if you need
different column definitions for joining columns, for example
regarding NULL/NOT NULL defaults. However by default a
"columnDefinition" attribute on :ref:`#[Column] ` also sets
the related ``#[JoinColumn]``'s columnDefinition. This is necessary to
make foreign keys work.
- **options**:
See "options" attribute on :ref:`#[Column] `.
Example:
.. code-block:: php
` on the owning side of the relation
requires to specify the #[JoinTable] attribute which describes the
details of the database join table. If you do not specify
``#[JoinTable]`` on these relations reasonable mapping defaults apply
using the affected table and the column names.
Required attribute:
- **name**: Database name of the join-table
Example:
.. code-block:: php
` is an
additional, optional attribute that has reasonable default
configuration values using the table and names of the two related
entities.
Required parameters:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional parameters:
- **mappedBy**: This option specifies the property name on the
targetEntity that is the owning side of this relation. It is a
required attribute for the inverse side of a relationship.
- **inversedBy**: The inversedBy attribute designates the field in the
entity that is the inverse side of the relationship.
- **cascade**: Cascade Option
- **fetch**: One of ``LAZY``, ``EXTRA_LAZY`` or ``EAGER``
- **indexBy**: Index the collection by a field on the target entity.
.. note::
For ``ManyToMany`` bidirectional relationships either side may
be the owning side (the side that defines the ``#[JoinTable]`` and/or
does not make use of the mappedBy attribute, thus using a default
join table).
Example:
.. code-block:: php
`.
Optional parameters:
- **repositoryClass**: Specifies the FQCN of a subclass of the EntityRepository.
That will be inherited for all subclasses of that Mapped Superclass.
Example:
.. code-block:: php
` with one additional option which can
be specified. When no
:ref:`#[JoinColumn] ` is specified it defaults to using the target entity table and
primary key column names and the current naming strategy to determine a name for the join column.
Required parameters:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional parameters:
- **cascade**: Cascade Option
- **fetch**: One of LAZY or EAGER
- **orphanRemoval**: Boolean that specifies if orphans, inverse
OneToOne entities that are not connected to any owning instance,
should be removed by Doctrine. Defaults to false.
- **inversedBy**: The inversedBy attribute designates the field in the
entity that is the inverse side of the relationship.
Example:
.. code-block:: php
` or :ref:`#[OneToMany] `
attribute to specify by which criteria the collection should be
retrieved from the database by using an ORDER BY clause.
Example:
.. code-block:: php
"ASC"])]
private $groups;
The key in ``OrderBy`` is only allowed to consist of
unqualified, unquoted field names and of an optional ``ASC``/``DESC``
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``#[ManyToMany]`` or ``#[OneToMany]`` attribute.
.. _attrref_postload:
#[PostLoad]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a ``#[PostLoad]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_postpersist:
#[PostPersist]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a ``#[PostPersist]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_postremove:
#[PostRemove]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a ``#[PostRemove]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_postupdate:
#[PostUpdate]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a ``#[PostUpdate]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_prepersist:
#[PrePersist]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a ``#[PrePersist]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_preremove:
#[PreRemove]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a #``[PreRemove]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_preupdate:
#[PreUpdate]
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a ``#[PreUpdate]`` event.
Only works with ``#[HasLifecycleCallbacks]`` in the entity class PHP
level.
.. _attrref_sequencegenerator:
#[SequenceGenerator]
~~~~~~~~~~~~~~~~~~~~~
For use with ``#[GeneratedValue(strategy: "SEQUENCE")]`` this
attribute allows to specify details about the sequence, such as
the increment size and initial values of the sequence.
Required parameters:
- **sequenceName**: Name of the sequence
Optional parameters:
- **allocationSize**: Increment the sequence by the allocation size
when its fetched. A value larger than 1 allows optimization for
scenarios where you create more than one new entity per request.
Defaults to 10
- **initialValue**: Where the sequence starts, defaults to 1.
Example:
.. code-block:: php
`
scenario. It only works on :ref:`#[Column] ` attributes that have
the type ``integer`` or ``datetime``. Setting ``#[Version]`` on a property with
:ref:`#[Id] ` is not supported.
Example:
.. code-block:: php
'Hello World!'])]
private string $text;
#[Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
private DateTime $createdAt;
}
================================================
FILE: docs/en/reference/basic-mapping/default-values.xml
================================================
================================================
FILE: docs/en/reference/basic-mapping.rst
================================================
Basic Mapping
=============
This guide explains the basic mapping of entities and properties.
After working through this guide you should know:
- How to create PHP objects that can be saved to the database with Doctrine;
- How to configure the mapping between columns on tables and properties on
entities;
- What Doctrine mapping types are;
- Defining primary keys and how identifiers are generated by Doctrine;
- How quoting of reserved symbols works in Doctrine.
Mapping of associations will be covered in the next chapter on
:doc:`Association Mapping `.
Creating Classes for the Database
---------------------------------
Every PHP object that you want to save in the database using Doctrine
is called an *Entity*. The term "Entity" describes objects
that have an identity over many independent requests. This identity is
usually achieved by assigning a unique identifier to an entity.
In this tutorial the following ``Message`` PHP class will serve as the
example Entity:
.. code-block:: php
`
- :doc:`XML `
- :doc:`PHP code `
This manual will usually show mapping metadata via attributes, though
many examples also show the equivalent configuration in XML.
.. note::
All metadata drivers perform equally. Once the metadata of a class has been
read from the source (attributes, XML, etc.) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
stored in the metadata cache. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
Marking our ``Message`` class as an entity for Doctrine is straightforward:
.. configuration-block::
.. code-block:: attribute
With no additional information, Doctrine expects the entity to be saved
into a table with the same name as the class in our case ``Message``.
You can change this by configuring information about the table:
.. configuration-block::
.. code-block:: attribute
Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
----------------
The next step is mapping its properties to columns in the table.
To configure a property use the ``Column`` attribute. The ``type``
argument specifies the :ref:`Doctrine Mapping Type
` to use for the field. If the type is not
specified, ``string`` is used as the default.
.. configuration-block::
.. code-block:: attribute
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. So in this example:
* the ``id`` property will map to the column ``id`` using the type ``integer``;
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
Here is a complete list of ``Column``s attributes (all optional):
- ``type`` (default: 'string'): The mapping type to use for the column.
- ``name`` (default: name of property): The name of the column in the database.
- ``length`` (default: 255): The length of the column in the database.
Applies only if a string-valued column is used.
- ``unique`` (default: ``false``): Whether the column is a unique key.
- ``nullable`` (default: ``false``): Whether the column is nullable.
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
which is the maximum number of digits that are stored for the values.
- ``scale`` (default: 0): The scale for a decimal (exact
numeric) column (applies only for decimal column), which represents
the number of digits to the right of the decimal point and must
not be greater than ``precision``.
- ``columnDefinition``: Allows to define a custom
DDL snippet that is used to create the column. Warning: This normally
confuses the :doc:`SchemaTool ` to always detect the column as changed.
- ``options``: Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
Specifying default values
~~~~~~~~~~~~~~~~~~~~~~~~~
While it is possible to specify default values for properties in your
PHP class, Doctrine also allows you to specify default values for
database columns using the ``default`` key in the ``options`` array of
the ``Column`` attribute.
When using XML, you can specify object instances using the ``` section on
details how to iterate large result sets.
Functions
~~~~~~~~~
The following methods exist on the ``AbstractQuery`` which both
``Query`` and ``NativeQuery`` extend from.
Parameters
^^^^^^^^^^
Prepared Statements that use numerical or named wildcards require
additional parameters to be executable against the database. To
pass parameters to the query the following methods can be used:
- ``AbstractQuery::setParameter($param, $value)`` - Set the
numerical or named wildcard to the given value.
- ``AbstractQuery::setParameters(array $params)`` - Set an array
of parameter key-value pairs.
- ``AbstractQuery::getParameter($param)``
- ``AbstractQuery::getParameters()``
Both named and positional parameters are passed to these methods without their ? or : prefix.
Cache related API
^^^^^^^^^^^^^^^^^
You can cache query results based either on all variables that
define the result (SQL, Hydration Mode, Parameters and Hints) or on
user-defined cache keys. However by default query results are not
cached at all. You have to enable the result cache on a per query
basis. The following example shows a complete workflow using the
Result Cache API:
.. code-block:: php
createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1');
$query->setParameter(1, 12);
$query->setResultCacheDriver(new ApcCache());
$query->enableResultCache(3600);
$result = $query->getResult(); // cache miss
$query->expireResultCache(true);
$result = $query->getResult(); // forced expire, cache miss
$query->setResultCacheId('my_query_result');
$result = $query->getResult(); // saved in given result cache id.
// or call enableResultCache() with all parameters:
$query->enableResultCache(3600, 'my_query_result');
$result = $query->getResult(); // cache hit!
// Introspection
$queryCacheProfile = $query->getQueryCacheProfile();
$cacheDriver = $query->getResultCacheDriver();
$lifetime = $query->getLifetime();
$key = $query->getCacheKey();
.. note::
You can set the Result Cache Driver globally on the
``Doctrine\ORM\Configuration`` instance so that it is passed to
every ``Query`` and ``NativeQuery`` instance.
Query Hints
^^^^^^^^^^^
You can pass hints to the query parser and hydrators by using the
``AbstractQuery::setHint($name, $value)`` method. Currently there
exist mostly internal query hints that are not be consumed in
userland. However the following few hints are to be used in
userland:
- ``Query::HINT_FORCE_PARTIAL_LOAD`` - Allows to hydrate objects
although not all their columns are fetched. This query hint can be
used to handle memory consumption problems with large result-sets
that contain char or binary data. Doctrine has no way of implicitly
reloading this data. Partially loaded objects have to be passed to
``EntityManager::refresh()`` if they are to be reloaded fully from
the database. This query hint is deprecated and will be removed
in the future (\ `Details `_)
- ``Query::HINT_REFRESH`` - This query is used internally by
``EntityManager::refresh()`` and can be used in userland as well.
If you specify this hint and a query returns the data for an entity
that is already managed by the UnitOfWork, the fields of the
existing entity will be refreshed. In normal operation a result-set
that loads data of an already existing entity is discarded in favor
of the already existing entity.
- ``Query::HINT_CUSTOM_TREE_WALKERS`` - An array of additional
``Doctrine\ORM\Query\TreeWalker`` instances that are attached to
the DQL query parsing process.
Query Cache (DQL Query Only)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Parsing a DQL query and converting it into a SQL query against the
underlying database platform obviously has some overhead in
contrast to directly executing Native SQL queries. That is why
there is a dedicated Query Cache for caching the DQL parser
results. In combination with the use of wildcards you can reduce
the number of parsed queries in production to zero.
The Query Cache Driver is passed from the
``Doctrine\ORM\Configuration`` instance to each
``Doctrine\ORM\Query`` instance by default and is also enabled by
default. This also means you don't regularly need to fiddle with
the parameters of the Query Cache, however if you do there are
several methods to interact with it:
- ``Query::setQueryCacheDriver($driver)`` - Allows to set a Cache
instance
- ``Query::setQueryCacheLifeTime($seconds)`` - Set lifetime
of the query caching.
- ``Query::expireQueryCache($bool)`` - Enforce the expiring of the
query cache if set to true.
- ``Query::getExpireQueryCache()``
- ``Query::getQueryCacheDriver()``
- ``Query::getQueryCacheLifeTime()``
First and Max Result Items (DQL Query Only)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can limit the number of results returned from a DQL query as
well as specify the starting offset, Doctrine then uses a strategy
of manipulating the select query to return only the requested
number of results:
- ``Query::setMaxResults($maxResults)``
- ``Query::setFirstResult($offset)``
.. note::
If your query contains a fetch-joined collection
specifying the result limit methods are not working as you would
expect. Set Max Results restricts the number of database result
rows, however in the case of fetch-joined collections one root
entity might appear in many rows, effectively hydrating less than
the specified number of results.
.. _dql-temporarily-change-fetch-mode:
Temporarily change fetch mode in DQL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
While normally all your associations are marked as lazy or extra lazy you will have cases where you are using DQL and don't want to
fetch join a second, third or fourth level of entities into your result, because of the increased cost of the SQL JOIN. You
can mark a many-to-one or one-to-one association as fetched temporarily to batch fetch these entities using a WHERE .. IN query.
.. code-block:: php
createQuery("SELECT u FROM MyProject\User u");
$query->setFetchMode("MyProject\User", "address", \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);
$query->execute();
Given that there are 10 users and corresponding addresses in the database the executed queries will look something like:
.. code-block:: sql
SELECT * FROM users;
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
.. note::
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
query per association can be executed to fetch all the referred-to entities (``address``).
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
a one-by-one basis once they are accessed.
.. _dql_ebnf_grammar:
EBNF
----
The following context-free grammar, written in an EBNF variant,
describes the Doctrine Query Language. You can consult this grammar
whenever you are unsure about what is possible with DQL or what the
correct syntax for a particular query should be.
Document syntax:
~~~~~~~~~~~~~~~~
- non-terminals begin with an upper case character
- terminals begin with a lower case character
- parentheses (...) are used for grouping
- square brackets [...] are used for defining an optional part,
e.g. zero or one time
- curly brackets {...} are used for repetition, e.g. zero or more
times
- double quotation marks "..." define a terminal string
- a vertical bar \| represents an alternative
Terminals
~~~~~~~~~
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
- string ('foo', 'bar''s house', '%ninja%', ...)
- char ('/', '\\', ' ', ...)
- integer (-1, 0, 1, 34, ...)
- float (-0.23, 0.007, 1.245342E+8, ...)
- boolean (false, true)
Query Language
~~~~~~~~~~~~~~
.. code-block:: php
QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
Statements
~~~~~~~~~~
.. code-block:: php
SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
UpdateStatement ::= UpdateClause [WhereClause]
DeleteStatement ::= DeleteClause [WhereClause]
Identifiers
~~~~~~~~~~~
.. code-block:: php
/* Alias Identification usage (the "u" of "u.name") */
IdentificationVariable ::= identifier
/* Alias Identification declaration (the "u" of "FROM User u") */
AliasIdentificationVariable :: = identifier
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name */
AbstractSchemaName ::= fully_qualified_name | identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
ResultVariable = identifier
/* identifier that must be a field (the "name" of "u.name") */
/* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */
FieldIdentificationVariable ::= identifier
/* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */
CollectionValuedAssociationField ::= FieldIdentificationVariable
/* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */
SingleValuedAssociationField ::= FieldIdentificationVariable
/* identifier that must be an embedded class state field */
EmbeddedClassStateField ::= FieldIdentificationVariable
/* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */
/* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */
SimpleStateField ::= FieldIdentificationVariable
Path Expressions
~~~~~~~~~~~~~~~~
.. code-block:: php
/* "u.Group" or "u.Phonenumbers" declarations */
JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
/* "u.Group" or "u.Phonenumbers" usages */
AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
/* "u.name" or "u.Group" */
SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
/* "u.name" or "u.Group.name" */
StateFieldPathExpression ::= IdentificationVariable "." StateField
/* "u.Group" */
SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
/* "u.Group.Permissions" */
CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
/* "name" */
StateField ::= {EmbeddedClassStateField "."}* SimpleStateField
Clauses
~~~~~~~
.. code-block:: php
SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}*
SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
WhereClause ::= "WHERE" ConditionalExpression
HavingClause ::= "HAVING" ConditionalExpression
GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
Items
~~~~~
.. code-block:: php
UpdateItem ::= SingleValuedPathExpression "=" NewValue
OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable | FunctionDeclaration) ["ASC" | "DESC"]
GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
NewValue ::= SimpleArithmeticExpression | "NULL"
From, Join and Index by
~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
.. note::
Using the ``WITH`` keyword for the ``ConditionalExpression`` of a
``RangeVariableDeclaration`` is deprecated and will be removed in
ORM 4.0. Use the ``ON`` keyword instead.
Select Expressions
~~~~~~~~~~~~~~~~~~
.. code-block:: php
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]
EntityAsDtoArgumentExpression ::= IdentificationVariable
Conditional Expressions
~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
ConditionalFactor ::= ["NOT"] ConditionalPrimary
ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression |
InExpression | NullComparisonExpression | ExistsExpression |
EmptyCollectionComparisonExpression | CollectionMemberExpression |
InstanceOfExpression
Collection Expressions
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
Literal Values
~~~~~~~~~~~~~~
.. code-block:: php
Literal ::= string | char | integer | float | boolean
InParameter ::= ArithmeticExpression | InputParameter
Input Parameter
~~~~~~~~~~~~~~~
.. code-block:: php
InputParameter ::= PositionalParameter | NamedParameter
PositionalParameter ::= "?" integer
NamedParameter ::= ":" string
Arithmetic Expressions
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
| FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
| FunctionsReturningDatetime | IdentificationVariable | ResultVariable
| InputParameter | CaseExpression
Scalar and Type Expressions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression
StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
BooleanExpression ::= BooleanPrimary | "(" Subselect ")"
BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter
EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
SimpleEntityExpression ::= IdentificationVariable | InputParameter
DatetimeExpression ::= DatetimePrimary | "(" Subselect ")"
DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression
.. note::
Parts of CASE expressions are not yet implemented.
Aggregate Expressions
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
Case Expressions
~~~~~~~~~~~~~~~~
.. code-block:: php
CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
Other Expressions
~~~~~~~~~~~~~~~~~
QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
.. code-block:: php
QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
InExpression ::= ArithmeticExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
InstanceOfParameter ::= AbstractSchemaName | InputParameter
LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
Functions
~~~~~~~~~
.. code-block:: php
FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime
FunctionsReturningNumerics ::=
"LENGTH" "(" StringPrimary ")" |
"LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
"ABS" "(" SimpleArithmeticExpression ")" |
"SQRT" "(" SimpleArithmeticExpression ")" |
"MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
"SIZE" "(" CollectionValuedPathExpression ")" |
"DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
"BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
"BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
FunctionsReturningDateTime ::=
"CURRENT_DATE" |
"CURRENT_TIME" |
"CURRENT_TIMESTAMP" |
"DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
"DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
FunctionsReturningStrings ::=
"CONCAT" "(" StringPrimary "," StringPrimary ")" |
"SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
"TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")" |
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
================================================
FILE: docs/en/reference/events.rst
================================================
Events
======
Doctrine ORM features a lightweight event system that is part of the
Common package. Doctrine uses it to dispatch system events, mainly
:ref:`lifecycle events `.
You can also use it for your own custom events.
The Event System
----------------
The event system is controlled by the ``EventManager``. It is the
central point of Doctrine's event listener system. Listeners are
registered on the manager and events are dispatched through the
manager.
.. code-block:: php
addEventListener(array(self::preFoo, self::postFoo), $this);
}
public function preFoo(EventArgs $e)
{
$this->preFooInvoked = true;
}
public function postFoo(EventArgs $e)
{
$this->postFooInvoked = true;
}
}
// Create a new instance
$test = new TestEvent($evm);
Events can be dispatched by using the ``dispatchEvent()`` method.
.. code-block:: php
dispatchEvent(TestEvent::preFoo);
$evm->dispatchEvent(TestEvent::postFoo);
You can easily remove a listener with the ``removeEventListener()``
method.
.. code-block:: php
removeEventListener(array(self::preFoo, self::postFoo), $this);
The Doctrine ORM event system also has a simple concept of event
subscribers. We can define a simple ``TestEventSubscriber`` class
which implements the ``\Doctrine\Common\EventSubscriber`` interface
and implements a ``getSubscribedEvents()`` method which returns an
array of events it should be subscribed to.
.. code-block:: php
preFooInvoked = true;
}
public function getSubscribedEvents()
{
return array(TestEvent::preFoo);
}
}
$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);
.. note::
The array to return in the ``getSubscribedEvents`` method is a simple array
with the values being the event names. The subscriber must have a method
that is named exactly like the event.
Now when you dispatch an event, any event subscribers will be
notified for that event.
.. code-block:: php
dispatchEvent(TestEvent::preFoo);
Now you can test the ``$eventSubscriber`` instance to see if the
``preFoo()`` method was invoked.
.. code-block:: php
preFooInvoked) {
echo 'pre foo invoked!';
}
Registering Event Handlers
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two ways to set up an event handler:
* For *all events* you can create a Lifecycle Event Listener or Subscriber class and register
it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``,
see
:ref:`Listening and subscribing to Lifecycle Events `
* For *some events* (see table below), you can create a *Lifecycle Callback* method in the
entity, see :ref:`Lifecycle Callbacks `.
.. _reference-events-lifecycle-events:
Events Overview
---------------
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+==================================================================+=======================+===========+=====================================+
| :ref:`preRemove ` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove ` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist ` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
| | on *initial* persist | | |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist ` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preUpdate ` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postUpdate ` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad ` | Loading from database | Yes | `PostLoadEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata ` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
| | metadata | | |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preFlush ` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onFlush ` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postFlush ` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onClear ` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
.. warning::
Making changes to entities and calling ``EntityManager::flush()`` from within
event handlers dispatched by ``EntityManager::flush()`` itself is strongly
discouraged, and might be deprecated and eventually prevented in the future.
The reason is that it causes re-entrance into ``UnitOfWork::commit()`` while a commit
is currently being processed. The ``UnitOfWork`` was never designed to support this,
and its behavior in this situation is not covered by any tests.
This may lead to entity or collection updates being missed, applied only in parts and
changes being lost at the end of the commit phase.
Naming convention
~~~~~~~~~~~~~~~~~
Events being used with the Doctrine ORM EventManager are best named
with camelcase and the value of the corresponding constant should
be the name of the constant itself, even with spelling. This has
several reasons:
- It is easy to read.
- Simplicity.
- Each method within an EventSubscriber is named after the
corresponding constant's value. If the constant's name and value differ
it contradicts the intention of using the constant and makes your code
harder to maintain.
An example for a correct notation can be found in the example
``TestEvent`` above.
.. _lifecycle-callbacks:
Lifecycle Callbacks
-------------------
Lifecycle Callbacks are defined on an entity class. They allow you to
trigger callbacks whenever an instance of that entity class experiences
a relevant lifecycle event. More than one callback can be defined for each
lifecycle event. Lifecycle Callbacks are best used for simple operations
specific to a particular entity class's lifecycle.
.. note::
Lifecycle Callbacks are not supported for :doc:`Embeddables `.
.. configuration-block::
.. code-block:: attribute
createdAt = date('Y-m-d H:i:s');
}
#[PrePersist]
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
#[PreUpdate]
public function doStuffOnPreUpdate(PreUpdateEventArgs $eventArgs)
{
$this->value = 'changed from preUpdate callback!';
}
}
.. code-block:: xml
Lifecycle Callbacks Event Argument
----------------------------------
The triggered event is also given to the lifecycle-callback.
With the additional argument you have access to the
``EntityManager`` and ``UnitOfWork`` APIs inside these callback methods.
.. code-block:: php
hasChangedField('username')) {
// Do something when the username is changed.
}
}
}
.. _listening-and-subscribing-to-lifecycle-events:
Listening and subscribing to Lifecycle Events
---------------------------------------------
Lifecycle event listeners are much more powerful than the simple
lifecycle callbacks that are defined on the entity classes. They
sit at a level above the entities and allow you to implement re-usable
behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
workings of the ``EntityManager`` and ``UnitOfWork`` classes. Please
read the :ref:`Implementing Event Listeners ` section
carefully if you are trying to write your own listener.
For event subscribers, there are no surprises. They declare the
lifecycle events in their ``getSubscribedEvents`` method and provide
public methods that expect the relevant arguments.
A lifecycle event listener looks like the following:
.. code-block:: php
getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
}
A lifecycle event subscriber may look like this:
.. code-block:: php
getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
.. note::
Lifecycle events are triggered for all entities. It is the responsibility
of the listeners and subscribers to check if the entity is of a type
it wants to handle.
To register an event listener or subscriber, you have to hook it into the
EventManager that is passed to the EntityManager factory:
.. code-block:: php
addEventListener([Events::preUpdate], new MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = new EntityManager($connection, $config, $eventManager);
You can also retrieve the event manager instance after the
EntityManager was created:
.. code-block:: php
getEventManager()->addEventListener([Events::preUpdate], new MyEventListener());
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
.. _reference-events-implementing-listeners:
Implementing Event Listeners
----------------------------
This section explains what is and what is not allowed during
specific lifecycle events of the ``UnitOfWork`` class. Although you get
passed the ``EntityManager`` instance in all of these events, you have
to follow these restrictions very carefully since operations in the
wrong event may produce lots of different errors, such as inconsistent
data and lost updates/persists/removes.
For the described events that are also lifecycle callback events
the restrictions apply as well, with the additional restriction
that (prior to version 2.4) you do not have access to the
``EntityManager`` or ``UnitOfWork`` APIs inside these events.
.. _reference-events-pre-persist:
prePersist
~~~~~~~~~~
There are two ways for the ``prePersist`` event to be triggered:
- One is when you call ``EntityManager::persist()``. The
event is also called for all :ref:`cascaded associations `.
- The other is inside the ``flush()`` method when changes to associations are computed and
this association is marked as :ref:`cascade: persist `. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called :ref:`persistence by reachability `.
In both cases you get passed a ``PrePersistEventArgs`` instance
which has access to the entity and the entity manager.
This event is only triggered on *initial* persist of an entity
(i.e. it does not trigger on future updates).
The following restrictions apply to ``prePersist``:
- If you are using a PrePersist Identity Generator such as
sequences the ID value will *NOT* be available within any
PrePersist events.
- Doctrine will not recognize changes made to relations in a prePersist
event. This includes modifications to
collections such as additions, removals or replacement.
.. _reference-events-pre-remove:
preRemove
~~~~~~~~~
The ``preRemove`` event is called on every entity immediately when it is passed
to the ``EntityManager::remove()`` method. It is cascaded for all
associations that are marked as :ref:`cascade: remove `
It is not called for a DQL ``DELETE`` statement.
There are no restrictions to what methods can be called inside the
``preRemove`` event, except when the remove method itself was
called during a flush operation.
.. _reference-events-pre-flush:
preFlush
~~~~~~~~
``preFlush`` is called inside ``EntityManager::flush()`` before
anything else. ``EntityManager::flush()`` must not be called inside
its listeners, since it would fire the ``preFlush`` event again, which would
result in an infinite loop.
.. code-block:: php
` API, which grants you access to the previously
mentioned sets. See this example:
.. code-block:: php
getObjectManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
}
foreach ($uow->getScheduledEntityDeletions() as $entity) {
}
foreach ($uow->getScheduledCollectionDeletions() as $col) {
}
foreach ($uow->getScheduledCollectionUpdates() as $col) {
}
}
}
The following restrictions apply to the ``onFlush`` event:
- If you create and persist a new entity in ``onFlush``, then
calling ``EntityManager::persist()`` is not enough.
You have to execute an additional call to
``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
- Changing primitive fields or associations requires you to
explicitly trigger a re-computation of the changeset of the
affected entity. This can be done by calling
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
.. _reference-events-post-flush:
postFlush
~~~~~~~~~
``postFlush`` is called at the end of ``EntityManager::flush()``.
``EntityManager::flush()`` can **NOT** be called safely inside its listeners.
This event is not a lifecycle callback.
.. code-block:: php
getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
// The following will only work if `status` is already present in the computed changeset.
// Otherwise it will throw an InvalidArgumentException:
$eventArgs->setNewValue('status', 'active');
}
}
}
}
You could also use this listener to implement validation of all the
fields that have changed. This is more efficient than using a
lifecycle callback when there are expensive validations to call:
.. code-block:: php
getEntity() instanceof Account) {
if ($eventArgs->hasChangedField('creditCard')) {
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
}
}
}
private function validateCreditCard($no)
{
// throw an exception to interrupt flush event. Transaction will be rolled back.
}
}
Restrictions for this event:
- Changes to associations of the passed entities are not
recognized by the flush operation anymore.
- Changes to fields of the passed entities are not recognized by
the flush operation anymore, use the computed change-set passed to
the event to modify primitive field values, e.g. use
``$eventArgs->setNewValue($field, $value);`` as in the Alice to Bob example above.
- Any calls to ``EntityManager::persist()`` or
``EntityManager::remove()``, even in combination with the ``UnitOfWork``
API are strongly discouraged and don't work as expected outside the
flush operation.
.. _reference-events-post-update-remove-persist:
postUpdate, postRemove, postPersist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These three ``post*`` events are called inside ``EntityManager::flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use these events to alter non-persistable items,
like non-mapped fields, logging or even associated classes that are
not directly mapped by Doctrine.
- The ``postUpdate`` event occurs after the database
update operations to entity data, but before the database transaction
has been committed. It is not called for a DQL ``UPDATE`` statement.
- The ``postPersist`` event occurs for an entity after the entity has
been made persistent. It will be invoked after all database insert
operations for new entities have been performed, but before the database
transaction has been committed. Generated primary key values will be
available for all entities at the time this event is triggered.
- The ``postRemove`` event occurs for an entity after the
entity has been deleted. It will be invoked after all database
delete operations for entity rows have been executed, but before the
database transaction has been committed. This event is not called for
a DQL ``DELETE`` statement.
.. note::
At the time ``postPersist`` is called, there may still be collection and/or
"extra" updates pending. The database may not yet be completely in
sync with the entity states in memory, not even for the new entities. Similarly,
also at the time ``postUpdate`` and ``postRemove`` are called, in-memory collections
may still be in a "dirty" state or still contain removed entities.
.. warning::
The ``postRemove`` event or any events triggered after an entity removal
can receive an uninitializable proxy in case you have configured an entity to
cascade remove relations. In this case, you should load yourself the proxy in
the associated ``pre*`` event.
.. _reference-events-post-load:
postLoad
~~~~~~~~
The postLoad event occurs after the entity has been loaded into the current
``EntityManager`` from the database or after ``refresh()`` has been applied to it.
.. warning::
When using ``Doctrine\ORM\AbstractQuery::toIterable()``, ``postLoad``
events will be executed immediately after objects are being hydrated, and therefore
associations are not guaranteed to be initialized. It is not safe to combine
usage of ``Doctrine\ORM\AbstractQuery::toIterable()`` and ``postLoad`` event
handlers.
.. _reference-events-on-clear:
onClear
~~~~~~~~
The ``onClear`` event occurs when the ``EntityManager::clear()`` operation is invoked,
after all references to entities have been removed from the unit of work.
This event is not a lifecycle callback.
Entity listeners
----------------
An entity listener is a lifecycle listener class used for an entity.
- The entity listener's mapping may be applied to an entity class or mapped superclass.
- An entity listener is defined by mapping the entity class with the corresponding mapping.
.. configuration-block::
.. code-block:: attribute
.. _reference-entity-listeners:
Entity listeners class
~~~~~~~~~~~~~~~~~~~~~~
An ``Entity Listener`` could be any class, by default it should be a class with a no-arg constructor.
- Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity
- An entity listener method receives two arguments, the entity instance and the lifecycle event.
- The callback method can be defined by naming convention or specifying a method mapping.
- When a listener mapping is not given the parser will use the naming convention to look for a matching method,
e.g. it will look for a public ``preUpdate()`` method if you are listening to the ``preUpdate`` event.
- When a listener mapping is given the parser will not look for any methods using the naming convention.
.. code-block:: php
.. note::
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver
~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine invokes the listener resolver to get the listener instance.
- A resolver allows you register a specific entity listener instance.
- You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver``
Specifying an entity listener instance :
.. code-block:: php
service = $service;
}
public function preUpdate(User $user, PreUpdateEventArgs $event)
{
$this->service->doSomething($user);
}
}
// register a entity listener.
$listener = $container->get('user_listener');
$em->getConfiguration()->getEntityListenerResolver()->register($listener);
Implementing your own resolver:
.. code-block:: php
container = $container;
}
public function resolve($className)
{
// resolve the service id by the given class name;
$id = 'user_listener';
return $this->container->get($id);
}
}
// Configure the listener resolver only before instantiating the EntityManager
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
$entityManager = new EntityManager(.., $configurations, ..);
.. _reference-events-load-class-metadata:
Load ClassMetadata Event
------------------------
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(attributes/xml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
You can hook in to this process and manipulate the instance.
This event is not a lifecycle callback.
.. code-block:: php
getEventManager();
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $test);
class TestEventListener
{
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$fieldMapping = array(
'fieldName' => 'about',
'type' => 'string',
'length' => 255
);
$classMetadata->mapField($fieldMapping);
}
}
If not class metadata can be found, an ``onClassMetadataNotFound`` event is dispatched.
Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
SchemaTool Events
-----------------
It is possible to access the schema metadata during schema changes that are happening in ``Doctrine\ORM\Tools\SchemaTool``.
There are two different events where you can hook in.
postGenerateSchemaTable
~~~~~~~~~~~~~~~~~~~~~~~
This event is fired for each ``Doctrine\DBAL\Schema\Table`` instance, after one was created and built up with the current class metadata
of an entity. It is possible to access to the current state of ``Doctrine\DBAL\Schema\Schema``, the current table schema
instance and class metadata.
.. code-block:: php
getEventManager();
$evm->addEventListener(ToolEvents::postGenerateSchemaTable, $test);
class TestEventListener
{
public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$schema = $eventArgs->getSchema();
$table = $eventArgs->getClassTable();
}
}
postGenerateSchema
~~~~~~~~~~~~~~~~~~
This event is fired after the schema instance was successfully built and before SQL queries are generated from the
schema information of ``Doctrine\DBAL\Schema\Schema``. It allows to access the full object representation of the database schema
and the EntityManager.
.. code-block:: php
getEventManager();
$evm->addEventListener(ToolEvents::postGenerateSchema, $test);
class TestEventListener
{
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs)
{
$schema = $eventArgs->getSchema();
$em = $eventArgs->getEntityManager();
}
}
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PrePersistEventArgs.php
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreRemoveEventArgs.php
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreUpdateEventArgs.php
.. _PostPersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostPersistEventArgs.php
.. _PostRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostRemoveEventArgs.php
.. _PostUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostUpdateEventArgs.php
.. _PostLoadEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostLoadEventArgs.php
.. _PreFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreFlushEventArgs.php
.. _PostFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PostFlushEventArgs.php
.. _OnFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/OnFlushEventArgs.php
.. _OnClearEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/OnClearEventArgs.php
.. _LoadClassMetadataEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/LoadClassMetadataEventArgs.php
.. _OnClassMetadataNotFoundEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/OnClassMetadataNotFoundEventArgs.php
================================================
FILE: docs/en/reference/faq.rst
================================================
Frequently Asked Questions
==========================
.. note::
This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember
what is often asked. If you stumble across an unanswered question please write a mail to the mailing-list or
join the #doctrine channel on Freenode IRC.
Database Schema
---------------
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In your mapping configuration, the column definition (for example, the
``#[Column]`` attribute) has an ``options`` parameter where you can specify
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
Mapping
-------
Why do I get exceptions about unique constraint failures during ``$em->flush()``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine does not check if you are re-adding entities with a primary key that already exists
or adding entities to a collection twice. You have to check for both conditions yourself
in the code before calling ``$em->flush()`` if you know that unique constraint failures
can occur.
In `Symfony2 `_ for example there is a Unique Entity Validator
to achieve this task.
For collections you can check with ``$collection->contains($entity)`` if an entity is already
part of this collection. For a FETCH=LAZY collection this will initialize the collection,
however for FETCH=EXTRA_LAZY this method will use SQL to determine if this entity is already
part of the collection.
Associations
------------
What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map
that contains a reference to an object that Doctrine does not know about. Say for example you grab
a "User"-entity from the database with a specific id and set a completely new object into one of the associations
of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about
this new object using ``EntityManager#persist($newObject)`` you will see this exception.
You can solve this exception by:
* Calling ``EntityManager#persist($newObject)`` on the new object
* Using cascade=persist on the association that contains the new object
How can I filter an association?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You should use DQL queries to query for the filtered set of entities.
I call clear() on a One-To-Many collection but the entities are not deleted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is an expected behavior that has to do with the inverse/owning side handling of Doctrine.
By definition a One-To-Many association is on the inverse side, that means changes to it
will not be recognized by Doctrine.
If you want to perform the equivalent of the clear operation you have to iterate the
collection and set the owning side many-to-one reference to NULL as well to detach all entities
from the collection. This will trigger the appropriate UPDATE statements on the database.
How can I add columns to a many-to-many table?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The many-to-many association is only supporting foreign keys in the table definition
To work with many-to-many tables containing extra columns you have to use the
foreign keys as primary keys feature of Doctrine ORM.
See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`.
How can i paginate fetch-joined collections?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are issuing a DQL statement that fetches a collection as well you cannot easily iterate
over this collection using a LIMIT statement (or vendor equivalent).
Doctrine does not offer a solution for this out of the box but there are several extensions
that do:
* `DoctrineExtensions `_
* `Pagerfanta `_
Why does pagination not work correctly with fetch joins?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results.
However when fetch-joining this is not returning the correct number of results since joining
with a one-to-many or many-to-many association multiplies the number of rows by the number
of associated entities.
See the previous question for a solution to this task.
Inheritance
-----------
Can I use Inheritance with Doctrine ORM?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, you can use Single- or Joined-Table Inheritance in ORM.
See the documentation chapter on :doc:`inheritance mapping ` for
the details.
Why does Doctrine not create proxy objects for my inheritance hierarchy?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you set a many-to-one or one-to-one association target-entity to any parent class of
an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of.
To find this out it has to execute a SQL query to look this information up in the database.
EntityGenerator
---------------
Why does the EntityGenerator not do X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
is supposed to kick-start you, but not towards 100%.
Why does the EntityGenerator not generate inheritance correctly?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
This is why the generation of inherited entities does not fully work. You have to adjust some additional
code to get this one working correctly.
Performance
-----------
Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If Doctrine detects that you are fetching an inverse side one-to-one association
it has to execute an additional query to load this object, because it cannot know
if there is no such object (setting null) or if it should set a proxy and which id this proxy has.
To solve this problem currently a query has to be executed to find out this information.
Doctrine Query Language
-----------------------
What is DQL?
~~~~~~~~~~~~
DQL stands for Doctrine Query Language, a query language that very much looks like SQL
but has some important benefits when using Doctrine:
- It uses class names and fields instead of tables and columns, separating concerns between backend and your object model.
- It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them.
- It adds some functionality that is related to object management and transforms them into SQL.
It also has some drawbacks of course:
- The syntax is slightly different to SQL so you have to learn and remember the differences.
- To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly.
- For some DQL constructs subselects are used which are known to be slow in MySQL.
Can I sort by a function (for example ORDER BY RAND()) in DQL?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No, it is not supported to sort by function in DQL. If you need this functionality you should either
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
starting with 1000 rows.
Is it better to write DQL or to generate it with the query builder?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
which is useful when you have optional filters, conditional joins, etc.
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
queries at runtime, which are then interpreted by Doctrine. This means that
using the ``QueryBuilder`` to build and run a query is actually always slower
than only running the corresponding DQL query.
So if you only need to generate a query and bind parameters to it,
you should use plain DQL, as this is a simpler and much more readable solution.
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
A Query fails, how can I debug it?
----------------------------------
First, if you are using the QueryBuilder you can use
``$queryBuilder->getDQL()`` to get the DQL string of this query. The
corresponding SQL you can get from the Query instance by calling
``$query->getSQL()``.
.. code-block:: php
createQuery($dql);
var_dump($query->getSQL());
$qb = $entityManager->createQueryBuilder();
$qb->select('u')->from('User', 'u');
var_dump($qb->getDQL());
================================================
FILE: docs/en/reference/filters.rst
================================================
Filters
=======
Doctrine ORM features a filter system that allows the developer to add SQL to
the conditional clauses of queries, regardless the place where the SQL is
generated (e.g. from a DQL query, or by loading associated entities).
The filter functionality works on SQL level. Whether a SQL query is generated
in a Persister, during lazy loading, in extra lazy collections or from DQL.
Each time the system iterates over all the enabled filters, adding a new SQL
part as a filter returns.
By adding SQL to the conditional clauses of queries, the filter system filters
out rows belonging to the entities at the level of the SQL result set. This
means that the filtered entities are never hydrated (which can be expensive).
Example filter class
--------------------
Throughout this document the example ``MyLocaleFilter`` class will be used to
illustrate how the filter feature works. A filter class must extend the base
``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint``
method. The method receives the ``ClassMetadata`` of the filtered entity and the
table alias of the SQL table of the entity.
.. note::
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values based on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
.. code-block:: php
reflClass->implementsInterface('LocaleAware')) {
return "";
}
return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParameter applies quoting automatically
}
}
If the parameter is an array and should be quoted as a list of values for an IN query
this is possible with the alternative ``SQLFilter#setParameterList()`` and
``SQLFilter#getParameterList()`` functions.
Configuration
-------------
Filter classes are added to the configuration as following:
.. code-block:: php
addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
The ``Configuration#addFilter()`` method takes a name for the filter and the name of the
class responsible for the actual filtering.
Disabling/Enabling Filters and Setting Parameters
---------------------------------------------------
Filters can be disabled and enabled via the ``FilterCollection`` which is
stored in the ``EntityManager``. The ``FilterCollection#enable($name)`` method
will retrieve the filter object. You can set the filter parameters on that
object.
.. code-block:: php
getFilters()->enable("locale");
$filter->setParameter('locale', 'en');
// Disable it
$filter = $em->getFilters()->disable("locale");
.. warning::
Disabling and enabling filters has no effect on managed entities. If you
want to refresh or reload an object after having modified a filter or the
FilterCollection, then you should clear the EntityManager and re-fetch your
entities, having the new rules for filtering applied.
Suspending/Restoring Filters
----------------------------
When a filter is disabled, the instance is fully deleted and all the filter
parameters previously set are lost. Then, if you enable it again, a new filter
is created without the previous filter parameters. If you want to keep a filter
(in order to use it later) but temporary disable it, you'll need to use the
``FilterCollection#suspend($name)`` and ``FilterCollection#restore($name)``
methods instead.
.. code-block:: php
getFilters()->enable("locale");
$filter->setParameter('locale', 'en');
// Temporary suspend the filter
$filter = $em->getFilters()->suspend("locale");
// Do things
// Then restore it, the locale parameter will still be set
$filter = $em->getFilters()->restore("locale");
.. warning::
If you enable a previously disabled filter, doctrine will create a new
one without keeping any of the previously parameter set with
``SQLFilter#setParameter()`` or ``SQLFilter#getParameterList()``. If you
want to restore the previously disabled filter instead, you must use the
``FilterCollection#restore($name)`` method.
================================================
FILE: docs/en/reference/improving-performance.rst
================================================
Improving Performance
=====================
Bytecode Cache
--------------
It is highly recommended to make use of a bytecode cache like OPcache.
A bytecode cache removes the need for parsing PHP code on every
request and can greatly improve performance.
"If you care about performance and don't use a bytecode
cache then you don't really care about performance. Please get one
and start using it."
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
Metadata and Query caches
-------------------------
As already mentioned earlier in the chapter about configuring
Doctrine, it is strongly discouraged to use Doctrine without a
Metadata and Query cache.
Operating Doctrine without these caches means
Doctrine will need to load your mapping information on every single
request and has to parse each DQL query on every single request.
This is a waste of resources.
The preferred cache adapter for metadata and query caches is a PHP file
cache like Symfony's
`PHP files adapter `_.
This kind of cache serializes cache items and writes them to a file.
This allows for opcode caching to be used and provides high performance in most scenarios.
See :ref:`types-of-caches`
Alternative Query Result Formats
--------------------------------
Make effective use of the available alternative query result
formats like nested array graphs or pure scalar results, especially
in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
You can mark entities as read only. For details, see :ref:`attrref_entity`
This means that the entity marked as read only is never considered for updates.
During flush on the EntityManager these entities are skipped even if properties
changed.
Read-Only allows to persist new entities of a kind and remove existing ones,
they are just not considered for updates.
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:
.. code-block:: php
$user = $entityManager->find(User::class, $id);
$entityManager->getUnitOfWork()->markReadOnly($user);
Or you can set all objects that are the result of a query hydration to be
marked as read only with the following query hint:
.. code-block:: php
$query = $entityManager->createQuery('SELECT u FROM App\\Entity\\User u');
$query->setHint(Query::HINT_READ_ONLY, true);
$users = $query->getResult();
Extra-Lazy Collections
----------------------
If entities hold references to large collections you will get performance and memory problems initializing them.
To solve this issue you can use the EXTRA_LAZY fetch-mode feature for collections. See the :doc:`tutorial <../tutorials/extra-lazy-associations>`
for more information on how this fetch mode works.
Temporarily change fetch mode in DQL
------------------------------------
See :ref:`dql-temporarily-change-fetch-mode`
Apply Best Practices
--------------------
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
See :doc:`Best Practices `
Change Tracking policies
------------------------
See: :doc:`Change Tracking Policies `
================================================
FILE: docs/en/reference/inheritance-mapping.rst
================================================
Inheritance Mapping
===================
This chapter explains the available options for mapping class
hierarchies.
Mapped Superclasses
-------------------
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. Typically, the purpose of such a
mapped superclass is to define state and mapping information that
is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance). They
are not query-able, and do not require an ``#[Id]`` property.
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. That implies that a
mapped superclass cannot be the ``targetEntity`` in associations.
In other words, a mapped superclass can use unidirectional One-To-One
and Many-To-One associations where it is the owning side.
Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For further
support of inheritance, the single or joined table inheritance features
have to be used.
.. note::
One-To-Many associations are not generally possible on a mapped
superclass, since they require the "many" side to hold the foreign
key.
It is, however, possible to use the :doc:`ResolveTargetEntityListener `
to replace references to a mapped superclass with an entity class at runtime.
As long as there is only one entity subclass inheriting from the mapped
superclass and all references to the mapped superclass are resolved to that
entity class at runtime, the mapped superclass *can* use One-To-Many associations
and be named as the ``targetEntity`` on the owning sides.
.. warning::
At least when using attributes or annotations to specify your mapping,
it *seems* as if you could inherit from a base class that is neither
an entity nor a mapped superclass, but has properties with mapping configuration
on them that would also be used in the inheriting class.
This, however, is due to how the corresponding mapping
drivers work and what the PHP reflection API reports for inherited fields.
Such a configuration is explicitly not supported. To give just one example,
it will break for ``private`` properties.
.. note::
You may be tempted to use traits to mix mapped fields or relationships
into your entity classes to circumvent some of the limitations of
mapped superclasses. Before doing that, please read the section on traits
in the :doc:`Limitations and Known Issues ` chapter.
Example:
.. code-block:: php
`_
is an inheritance mapping strategy where all classes of a hierarchy are
mapped to a single database table.
Example:
.. configuration-block::
.. code-block:: attribute
Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
#[Entity]
class Employee extends Person
{
// ...
}
.. code-block:: xml
In this example, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and employee" identifies a row as being of type ``Employee``.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
This mapping approach works well when the type hierarchy is fairly
simple and stable. Adding a new type to the hierarchy and adding
fields to existing supertypes simply involves adding new columns to
the table, though in large deployments this may have an adverse
impact on the index and column layout inside the database.
Performance impact
~~~~~~~~~~~~~~~~~~
This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a ``WHERE`` clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
null values. Columns that have ``NOT NULL`` constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
-----------------------
`Class Table Inheritance `_
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
of a parent class through a foreign key constraint.
The discriminator column is placed in the topmost table of the hierarchy,
because this is the easiest way to achieve polymorphic queries with Class
Table Inheritance.
Example:
.. code-block:: php
Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
#[Entity]
class Employee extends Person
{
// ...
}
As before, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type ``Employee``.
.. note::
When you do not use the SchemaTool to generate the
required SQL you should know that deleting a class table
inheritance makes use of the foreign key property
``ON DELETE CASCADE`` in all database implementations. A failure to
implement this yourself will lead to dead rows in the database.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
Introducing a new type to the hierarchy, at any level, simply
involves interjecting a new table into the schema. Subtypes of that
type will automatically join with that new type at runtime.
Similarly, modifying any entity type in the hierarchy by adding,
modifying or removing fields affects only the immediate table
mapped to that type. This mapping strategy provides the greatest
flexibility at design time, since changes to any type are always
limited to that type's dedicated table.
Performance impact
~~~~~~~~~~~~~~~~~~
This strategy inherently requires multiple JOIN operations to
perform just about any query which can have a negative impact on
performance, especially with large tables and/or large hierarchies.
When partial objects are allowed, either globally or on the
specific query, then querying for any type will not cause the
tables of subtypes to be ``OUTER JOIN``ed which can increase
performance but the resulting partial objects will not fully load
themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is also another important performance consideration that it is *not possible*
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
For each entity in the Class-Table Inheritance hierarchy all the
mapped fields have to be columns on the table of this entity.
Additionally each child table has to have an id column that matches
the id column definition on the root table (except for any sequence
or auto-increment details). Furthermore each child table has to
have a foreign key pointing from the id column to the root table id
column and cascading on delete.
.. _inheritence_mapping_overrides:
Overrides
---------
Overrides can only be applied to entities that extend a mapped superclass or
use traits. They are used to override a mapping for an entity field or
relationship defined in that mapped superclass or trait.
It is not supported to use overrides in entity inheritance scenarios.
.. note::
When using traits, make sure not to miss the warnings given in the
:doc:`Limitations and Known Issues ` chapter.
Association Override
~~~~~~~~~~~~~~~~~~~~
Override a mapping for an entity relationship.
Could be used by an entity that extends a mapped superclass
to override a relationship mapping defined by the mapped superclass.
Example:
.. configuration-block::
.. code-block:: attribute
*/
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'Group', inversedBy: 'users')]
protected Collection $groups;
#[ManyToOne(targetEntity: 'Address')]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
protected Address|null $address = null;
}
// admin mapping
namespace MyProject\Model;
#[Entity]
#[AssociationOverrides([
new AssociationOverride(
name: 'groups',
joinTable: new JoinTable(
name: 'users_admingroups',
),
joinColumns: [new JoinColumn(name: 'adminuser_id')],
inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]
),
new AssociationOverride(
name: 'address',
joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')]
)
])]
class Admin extends User
{
}
.. code-block:: xml
Things to note:
- The "association override" specifies the overrides based on the property
name.
- This feature is available for all kind of associations (OneToOne, OneToMany, ManyToOne, ManyToMany).
- The association type *cannot* be changed.
- The override could redefine the ``joinTables`` or ``joinColumns`` depending on the association type.
- The override could redefine ``inversedBy`` to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
Attribute Override
~~~~~~~~~~~~~~~~~~~~
Override the mapping of a field.
Could be used by an entity that extends a mapped superclass to override a field mapping defined by the mapped superclass.
.. configuration-block::
.. code-block:: attribute
Things to note:
- The "attribute override" specifies the overrides based on the property name.
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
- The override can redefine all the attributes except the type.
Query the Type
--------------
It may happen that the entities of a special type should be queried. Because there
is no direct access to the discriminator column, Doctrine provides the
``INSTANCE OF`` construct.
The following example shows how to use ``INSTANCE OF``. There is a three level hierarchy
with a base entity ``NaturalPerson`` which is extended by ``Staff`` which in turn
is extended by ``Technician``.
Querying for the staffs without getting any technicians can be achieved by this DQL:
.. code-block:: php
createQuery("SELECT staff FROM MyProject\Model\Staff staff WHERE staff NOT INSTANCE OF MyProject\Model\Technician");
$staffs = $query->getResult();
================================================
FILE: docs/en/reference/installation.rst
================================================
:orphan:
Installation
============
The installation chapter has moved to :doc:`Installation and Configuration `.
================================================
FILE: docs/en/reference/limitations-and-known-issues.rst
================================================
Limitations and Known Issues
============================
We try to make using Doctrine ORM a very pleasant experience.
Therefore we think it is very important to be honest about the
current limitations to our users. Much like every other piece of
software the ORM is not perfect and far from feature complete.
This section should give you an overview of current limitations of
Doctrine ORM as well as critical known issues that you should know
about.
Current Limitations
-------------------
There is a set of limitations that exist currently which might be
solved in the future. Any of this limitations now stated has at
least one ticket in the Tracker and is discussed for future
releases.
Join-Columns with non-primary keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary
keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance
reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
Mapping Arrays to a Join Table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Related to the previous limitation with "Foreign Keys as
Identifier" you might be interested in mapping the same table
structure as given above to an array. However this is not yet
possible either. See the following example:
.. code-block:: sql
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This schema should be mapped to a Product Entity as follows:
.. code-block:: php
class Product
{
private $id;
private $name;
private $attributes = array();
}
Where the ``attribute_name`` column contains the key and
``attribute_value`` contains the value of each array element in
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket `_.
Custom Persisters
~~~~~~~~~~~~~~~~~
A Persister in Doctrine is an object that is responsible for the
hydration and write operations of an entity against the database.
Currently there is no way to overwrite the persister implementation
for a given entity, however there are several use-cases that can
benefit from custom persister implementations:
- `Add Upsert Support `_
- `Evaluate possible ways in which stored-procedures can be used `_
Persist Keys of Collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~
PHP Arrays are ordered hash-maps and so should be the
``Doctrine\Common\Collections\Collection`` interface. We plan to
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 `_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is not possible to map several equally looking tables onto one
entity. For example if you have a production and an archive table
of a certain business concept then you cannot have both tables map
to the same entity.
Behaviors
~~~~~~~~~
Doctrine ORM will **never** include a behavior system like Doctrine 1
in the core library. We don't think behaviors add more value than
they cost pain and debugging hell. Please see the many different
blog posts we have written on this topics:
- `Doctrine2 "Behaviors" in a Nutshell `_
- `A re-usable Versionable behavior for Doctrine2 `_
- `Write your own ORM on top of Doctrine2 `_
- `Doctrine ORM Behavioral Extensions `_
Doctrine ORM has enough hooks and extension points so that **you** can
add whatever you want on top of it. None of this will ever become
core functionality of Doctrine2 however, you will have to rely on
third party extensions for magical behaviors.
Nested Set
~~~~~~~~~~
NestedSet was offered as a behavior in Doctrine 1 and will not be
included in the core of Doctrine ORM. However there are already two
extensions out there that offer support for Nested Set with
ORM:
- `Doctrine2 Hierarchical-Structural Behavior `_
- `Doctrine2 NestedSet `_
Using Traits in Entity Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The use of traits in entity or mapped superclasses, at least when they
include mapping configuration or mapped fields, is currently not
endorsed by the Doctrine project. The reasons for this are as follows.
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
more than two years after the initial Doctrine 2 release and the time where
core components were designed.
In fact, this documentation mentions traits only in the context of
:doc:`overriding field association mappings in subclasses `.
Coverage of traits in test cases is practically nonexistent.
Thus, you should at least be aware that when using traits in your entity and
mapped superclasses, you will be in uncharted terrain.
.. warning::
There be dragons.
From a more technical point of view, traits basically work at the language level
as if the code contained in them had been copied into the class where the trait
is used, and even private fields are accessible by the using class. In addition to
that, some precedence and conflict resolution rules apply.
When it comes to loading mapping configuration, the annotation and attribute drivers
rely on PHP reflection to inspect class properties including their docblocks.
As long as the results are consistent with what a solution *without* traits would
have produced, this is probably fine.
However, to mention known limitations, it is currently not possible to use "class"
level `annotations `_ or
`attributes `_ on traits, and attempts to
improve parser support for traits as `here `_
or `there `_ have been abandoned
due to complexity.
XML mapping configuration probably needs to completely re-configure or otherwise
copy-and-paste configuration for fields used from traits.
Mapping multiple private fields of the same name
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When two classes, say a mapped superclass and an entity inheriting from it,
both contain a ``private`` field of the same name, this will lead to a ``MappingException``.
Since the fields are ``private``, both are technically separate and can contain
different values at the same time. However, the ``ClassMetadata`` configuration used
internally by the ORM currently refers to fields by their name only, without taking the
class containing the field into consideration. This makes it impossible to keep separate
mapping configuration for both fields.
Apart from that, in the case of having multiple ``private`` fields of the same name within
the class hierarchy an entity or mapped superclass, the Collection filtering API cannot determine
the right field to look at. Even if only one of these fields is actually mapped, the ``ArrayCollection``
will not be able to tell, since it does not have access to any metadata.
Thus, to avoid problems in this regard, it is best to avoid having multiple ``private`` fields of the
same name in class hierarchies containing entity and mapped superclasses.
Known Issues
------------
The Known Issues section describes critical/blocker bugs and other
issues that are either complicated to fix, not fixable due to
backwards compatibility issues or where no simple fix exists (yet).
We don't plan to add every bug in the tracker there, just those
issues that can potentially cause nightmares or pain of any sort.
See bugs, improvement and feature requests on `Github issues `_.
Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For compatibility reasons between all the supported vendors and
edge case problems Doctrine ORM does **NOT** do automatic identifier
quoting. This can lead to problems when trying to get
legacy-databases to work with Doctrine ORM.
- You can quote column-names as described in the
:doc:`Basic-Mapping ` section.
- You cannot quote join column names.
- You cannot use non [a-zA-Z0-9\_]+ characters, they will break
several SQL statements.
Having problems with these kind of column names? Many databases
support all CRUD operations on views that semantically map to
certain tables. You can create views for all your problematic
tables and column names to avoid the legacy quoting nightmare.
Microsoft SQL Server and Doctrine "datetime"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime
datatypes then you have to add your own data-type (see Basic Mapping for an example).
MySQL with MyISAM tables
~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
other storage engines that support transactions if you need integrity.
================================================
FILE: docs/en/reference/metadata-drivers.rst
================================================
Metadata Drivers
================
The heart of an object relational mapper is the mapping information
that glues everything together. It instructs the EntityManager how
it should behave when dealing with the different entities.
Core Metadata Drivers
---------------------
Doctrine provides a few different ways for you to specify your
metadata:
- **XML files** (XmlDriver)
- **Attributes** (AttributeDriver)
- **PHP Code in files or static functions** (PhpDriver)
Something important to note about the above drivers is they are all
an intermediate step to the same end result. The mapping
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
instances. So in the end, Doctrine only ever has to work with the
API of the ``ClassMetadata`` class to get mapping information for
an entity.
.. note::
The populated ``ClassMetadata`` instances are also cached
so in a production environment the parsing and populating only ever
happens once. You can configure the metadata cache implementation
using the ``setMetadataCacheImpl()`` method on the
``Doctrine\ORM\Configuration`` class:
.. code-block:: php
getConfiguration()->setMetadataCacheImpl(new ApcuCache());
All the drivers are in the ``Doctrine\ORM\Mapping\Driver`` namespace:
.. code-block:: php
getConfiguration()->setMetadataDriverImpl($driver);
Implementing Metadata Drivers
-----------------------------
In addition to the included metadata drivers you can very easily
implement your own. All you need to do is define a class which
implements the ``MappingDriver`` interface:
.. code-block:: php
$className
* @param ClassMetadata $metadata
*
* @return void
*
* @template T of object
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return list The names of all mapped classes known to this driver.
*/
public function getAllClassNames();
/**
* Returns whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a MappedSuperclass.
*
* @param class-string $className
*
* @return bool
*/
public function isTransient(string $className);
}
If you want to write a metadata driver to parse information from
some file format we've made your life a little easier by providing
the ``FileDriver`` implementation for you to extend from:
.. code-block:: php
_loadMappingFile($file);
// populate ClassMetadata instance from $data
}
/**
* {@inheritDoc}
*/
protected function _loadMappingFile($file)
{
// parse contents of $file and return php data structure
}
}
.. note::
When using the ``FileDriver`` it requires that you only have one
entity defined per file and the file named after the class described
inside where namespace separators are replaced by periods. So if you
have an entity named ``Entities\User`` and you wanted to write a
mapping file for your driver above you would need to name the file
``Entities.User.dcm.ext`` for it to be recognized.
Now you can use your ``MyMetadataDriver`` implementation by setting
it with the ``setMetadataDriverImpl()`` method:
.. code-block:: php
getConfiguration()->setMetadataDriverImpl($driver);
ClassMetadata
-------------
The last piece you need to know and understand about metadata in
Doctrine ORM is the API of the ``ClassMetadata`` classes. You need to
be familiar with them in order to implement your own drivers but
more importantly to retrieve mapping information for a certain
entity when needed.
You have all the methods you need to manually specify the mapping
information instead of using some mapping file to populate it from.
You can read more about the API of the ``ClassMetadata`` classes in
the PHP Mapping chapter.
Getting ClassMetadata Instances
-------------------------------
If you want to get the ``ClassMetadata`` instance for an entity in
your project to programmatically use some mapping information to
generate some HTML or something similar you can retrieve it through
the ``ClassMetadataFactory``:
.. code-block:: php
getMetadataFactory();
$class = $cmf->getMetadataFor('MyEntityName');
Now you can learn about the entity and use the data stored in the
``ClassMetadata`` instance to get all mapped fields for example and
iterate over them:
.. code-block:: php
fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}
================================================
FILE: docs/en/reference/namingstrategy.rst
================================================
Implementing a NamingStrategy
==============================
Using a naming strategy you can provide rules for generating database identifiers,
column or table names. This feature helps
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
.. warning
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
Configuring a naming strategy
-----------------------------
The default strategy used by Doctrine is quite minimal.
By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy``
uses the simple class name and the attribute names to generate tables and columns.
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()``:
.. code-block:: php
setNamingStrategy($namingStrategy);
Underscore naming strategy
---------------------------
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy.
.. code-block:: php
setNamingStrategy($namingStrategy);
For SomeEntityName the strategy will generate the table SOME_ENTITY_NAME with the
``CASE_UPPER`` option, or some_entity_name with the ``CASE_LOWER`` option.
Naming strategy interface
-------------------------
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
a naming strategy for database tables and columns.
.. code-block:: php
referenceColumnName();
}
public function joinTableName(string $sourceEntity, string $targetEntity, string $propertyName): string
{
return strtolower($this->classToTableName($sourceEntity) . '_' .
$this->classToTableName($targetEntity));
}
public function joinKeyColumnName(string $entityName, ?string $referencedColumnName): string
{
return strtolower($this->classToTableName($entityName) . '_' .
($referencedColumnName ?: $this->referenceColumnName()));
}
}
================================================
FILE: docs/en/reference/native-sql.rst
================================================
Native SQL
==========
With ``NativeQuery`` you can execute native SELECT SQL statements
and map the results to Doctrine entities or any other result format
supported by Doctrine.
In order to make this mapping possible, you need to describe
to Doctrine what columns in the result map to which entity property.
This description is represented by a ``ResultSetMapping`` object.
With this feature you can map arbitrary SQL code to objects, such as highly
vendor-optimized SQL or stored-procedures.
Writing ``ResultSetMapping`` from scratch is complex, but there is a convenience
wrapper around it called a ``ResultSetMappingBuilder``. It can generate
the mappings for you based on Entities and even generates the ``SELECT``
clause based on this information for you.
.. note::
If you want to execute DELETE, UPDATE or INSERT statements
the Native SQL API cannot be used and will probably throw errors.
Use ``EntityManager#getConnection()`` to access the native database
connection and call the ``executeUpdate()`` method for these
queries.
The NativeQuery class
---------------------
To create a ``NativeQuery`` you use the method
``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As you can see in
the signature of this method, it expects 2 ingredients: The SQL you want to
execute and the ``ResultSetMapping`` that describes how the results will be
mapped.
Once you obtained an instance of a ``NativeQuery``, you can bind parameters to
it with the same API that ``Query`` has and execute it.
.. code-block:: php
createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
ResultSetMappingBuilder
-----------------------
An easy start into ResultSet mapping is the ``ResultSetMappingBuilder`` object.
This has several benefits:
- The builder takes care of automatically updating your ``ResultSetMapping``
when the fields or associations change on the metadata of an entity.
- You can generate the required ``SELECT`` expression for a builder
by converting it to a string.
- The API is much simpler than the usual ``ResultSetMapping`` API.
One downside is that the builder API does not yet support entities
with inheritance hierarchies.
.. code-block:: php
addRootEntityFromClassMetadata('MyProject\User', 'u');
$rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id'));
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
The ``SELECT`` clause can be generated
from a ``ResultSetMappingBuilder``. You can either cast the builder
object to ``(string)`` and the DQL aliases are used as SQL table aliases
or use the ``generateSelectClause($tableAliases)`` method and pass
a mapping from DQL alias (key) to SQL alias (value)
.. code-block:: php
generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
The ResultSetMapping
--------------------
Understanding the ``ResultSetMapping`` is the key to using a
``NativeQuery``. A Doctrine result can contain the following
components:
- Entity results. These represent root result elements.
- Joined entity results. These represent joined entities in
associations of root entity results.
- Field results. These represent a column in the result set that
maps to a field of an entity. A field result always belongs to an
entity result or joined entity result.
- Scalar results. These represent scalar values in the result set
that will appear in each result row. Adding scalar results to a
ResultSetMapping can also cause the overall result to become
**mixed** (see DQL - Doctrine Query Language) if the same
ResultSetMapping also contains entity results.
- Meta results. These represent columns that contain
meta-information, such as foreign keys and discriminator columns.
When querying for objects (``getResult()``), all meta columns of
root entities or joined entities must be present in the SQL query
and mapped accordingly using ``ResultSetMapping#addMetaResult``.
.. note::
It might not surprise you that Doctrine uses
``ResultSetMapping`` internally when you create DQL queries. As
the query gets parsed and transformed to SQL, Doctrine fills a
``ResultSetMapping`` that describes how the results should be
processed by the hydration routines.
We will now look at each of the result types that can appear in a
ResultSetMapping in detail.
Entity results
~~~~~~~~~~~~~~
An entity result describes an entity type that appears as a root
element in the transformed result. You add an entity result through
``ResultSetMapping#addEntityResult()``. Let's take a look at the
method signature in detail:
.. code-block:: php
addScalarResult('name', 1, 'string');
$rsm->addScalarResult('email', 2, 'string');
$rsm->addScalarResult('city', 3, 'string');
$rsm->newObjectMappings['name'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0, // a result can contain many DTOs, this is the index of the DTO to map to
'argIndex' => 0, // each scalar result can be mapped to a different argument of the DTO constructor
];
$rsm->newObjectMappings['email'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 1,
];
$rsm->newObjectMappings['city'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 2,
];
Meta results
~~~~~~~~~~~~
A meta result describes a single column in a SQL result set that
is either a foreign key or a discriminator column. These columns
are essential for Doctrine to properly construct objects out of SQL
result sets. To add a column as a meta result use
``ResultSetMapping#addMetaResult()``. The method signature in
detail:
.. code-block:: php
addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
.. code-block:: php
array(
[0] => User (Object)
)
Note that this would be a partial object if the entity has more
fields than just id and name. In the example above the column and
field names are identical but that is not necessary, of course.
Also note that the query string passed to createNativeQuery is
**real native SQL**. Doctrine does not touch this SQL in any way.
In the previous basic example, a User had no relations and the
table the class is mapped to owns no foreign keys. The next example
assumes User has a unidirectional or bidirectional one-to-one
association to a CmsAddress, where the User is the owning side and
thus owns the foreign key.
.. code-block:: php
addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when
querying for objects. In the previous example, each user object in
the result will have a proxy (a "ghost") in place of the address
that contains the address\_id. When the ghost proxy is accessed, it
loads itself based on this key.
Consequently, associations that are *fetch-joined* do not require
the foreign keys to be present in the SQL result set, only
associations that are lazy.
.. code-block:: php
addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address');
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity ``Address`` is registered with the
``ResultSetMapping#addJoinedEntityResult`` method, which notifies
Doctrine that this entity is not hydrated at the root level, but as
a joined entity somewhere inside the object graph. In this case we
specify the alias 'u' as third parameter and ``address`` as fourth
parameter, which means the ``Address`` is hydrated into the
``User::$address`` property.
If a fetched entity is part of a mapped hierarchy that requires a
discriminator column, this column must be present in the result set
as a meta column so that Doctrine can create the appropriate
concrete type. This is shown in the following example where we
assume that there are one or more subclasses that extend User and
either Class Table Inheritance or Single Table Inheritance is used
to map the hierarchy (both use a discriminator column).
.. code-block:: php
addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as
above would result in partial objects if any objects in the result
are actually a subtype of User. When using DQL, Doctrine
automatically includes the necessary joins for this mapping
strategy but with native SQL it is your responsibility.
================================================
FILE: docs/en/reference/partial-hydration.rst
================================================
Partial Hydration
=================
Partial hydration of entities is allowed in the array hydrator, when
only a subset of the fields of an entity are loaded from the database
and the nested results are still created based on the entity relationship structure.
.. code-block:: php
createQuery("SELECT PARTIAL u.{id,name}, partial a.{id,street} FROM MyApp\Domain\User u JOIN u.addresses a")
->getArrayResult();
This is a useful optimization when you are not interested in all fields of an entity
for performance reasons, for example in use-cases for exporting or rendering lots of data.
================================================
FILE: docs/en/reference/partial-objects.rst
================================================
Partial Objects
===============
A partial object is an object whose state is not fully initialized
after being reconstituted from the database and that is
disconnected from the rest of its data. The following section will
describe why partial objects are problematic and what the approach
of Doctrine to this problem is.
.. note::
The partial object problem in general does not apply to
methods or queries where you do not retrieve the query result as
objects. Examples are: ``Query#getArrayResult()``,
``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
etc.
.. warning::
Use of partial objects is tricky. Fields that are not retrieved
from the database will not be updated by the UnitOfWork even if they
get changed in your objects. You can only promote a partial object
to a fully-loaded object by calling ``EntityManager#refresh()``
or a DQL query with the refresh flag.
What is the problem?
--------------------
In short, partial objects are problematic because they are usually
objects with broken invariants. As such, code that uses these
partial objects tends to be very fragile and either needs to "know"
which fields or methods can be safely accessed or add checks around
every field access or method invocation. The same holds true for
the internals, i.e. the method implementations, of such objects.
You usually simply assume the state you need in the method is
available, after all you properly constructed this object before
you pushed it into the database, right? These blind assumptions can
quickly lead to null reference errors when working with such
partial objects.
It gets worse with the scenario of an optional association (0..1 to
1). When the associated field is NULL, you don't know whether this
object does not have an associated object or whether it was simply
not loaded when the owning object was loaded from the database.
These are reasons why many ORMs do not allow partial objects at all
and instead you always have to load an object with all its fields
(associations being proxied). One secure way to allow partial
objects is if the programming language/platform allows the ORM tool
to hook deeply into the object and instrument it in such a way that
individual fields (not only associations) can be loaded lazily on
first access. This is possible in Java, for example, through
bytecode instrumentation. In PHP though this is not possible, so
there is no way to have "secure" partial objects in an ORM with
transparent persistence.
Doctrine, by default, does not allow partial objects. That means,
any query that only selects partial object data and wants to
retrieve the result as objects (i.e. ``Query#getResult()``) will
raise an exception telling you that partial objects are dangerous.
If you want to force a query to return you partial objects,
possibly as a performance tweak, you can use the ``partial``
keyword as follows:
.. code-block:: php
createQuery("select partial u.{id,name} from MyApp\Domain\User u");
You can also get a partial reference instead of a proxy reference by
calling:
.. code-block:: php
getPartialReference('MyApp\Domain\User', 1);
Partial references are objects with only the identifiers set as they
are passed to the second argument of the ``getPartialReference()`` method.
All other fields are null.
When should I force partial objects?
------------------------------------
Mainly for optimization purposes, but be careful of premature
optimization as partial objects lead to potentially more fragile
code.
================================================
FILE: docs/en/reference/php-mapping.rst
================================================
PHP Mapping
===========
Doctrine ORM also allows you to provide the ORM metadata in the form of plain
PHP code using the ``ClassMetadata`` API. You can write the code in inside of a
static function named ``loadMetadata($class)`` on the entity class itself.
Static Function
---------------
In addition to other drivers using configuration languages you can also
programatically specify your mapping information inside of a static function
defined on the entity class itself.
This is useful for cases where you want to keep your entity and mapping
information together but don't want to use attributes. For this you just
need to use the ``StaticPHPDriver``:
.. code-block:: php
getConfiguration()->setMetadataDriverImpl($driver);
Now you just need to define a static function named
``loadMetadata($metadata)`` on your entity:
.. code-block:: php
mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
));
}
}
ClassMetadataBuilder
--------------------
To ease the use of the ClassMetadata API (which is very raw) there is a ``ClassMetadataBuilder`` that you can use.
.. code-block:: php
createField('id', 'integer')->isPrimaryKey()->generatedValue()->build();
$builder->addField('username', 'string');
}
}
The API of the ClassMetadataBuilder has the following methods with a fluent interface:
- ``addField($name, $type, array $mapping)``
- ``setMappedSuperclass()``
- ``setReadOnly()``
- ``setCustomRepositoryClass($className)``
- ``setTable($name)``
- ``addIndex(array $columns, $indexName)``
- ``addUniqueConstraint(array $columns, $constraintName)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null, $options = [])``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``addLifecycleEvent($methodName, $event)``
- ``addManyToOne($name, $targetEntity, $inversedBy = null)``
- ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
- ``addOwningOneToOne($name, $targetEntity, $inversedBy = null)``
- ``addOwningManyToMany($name, $targetEntity, $inversedBy = null)``
- ``addInverseManyToMany($name, $targetEntity, $mappedBy)``
- ``addOneToMany($name, $targetEntity, $mappedBy)``
It also has several methods that create builders (which are necessary for advanced mappings):
- ``createField($name, $type)`` returns a ``FieldBuilder`` instance
- ``createManyToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance
- ``createOneToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
ClassMetadata API
-----------------
The ``ClassMetadata`` class is the data object for storing the mapping
metadata for a single entity. It contains all the getters and setters
you need populate and retrieve information for an entity.
General Setters
~~~~~~~~~~~~~~~
- ``setTableName($tableName)``
- ``setPrimaryTable(array $primaryTableDefinition)``
- ``setCustomRepositoryClass($repositoryClassName)``
- ``setIdGeneratorType($generatorType)``
- ``setIdGenerator($generator)``
- ``setSequenceGeneratorDefinition(array $definition)``
- ``setChangeTrackingPolicy($policy)``
- ``setIdentifier(array $identifier)``
Inheritance Setters
~~~~~~~~~~~~~~~~~~~
- ``setInheritanceType($type)``
- ``setSubclasses(array $subclasses)``
- ``setParentClasses(array $classNames)``
- ``setDiscriminatorColumn($columnDef)``
- ``setDiscriminatorMap(array $map)``
Field Mapping Setters
~~~~~~~~~~~~~~~~~~~~~
- ``mapField(array $mapping)``
- ``mapOneToOne(array $mapping)``
- ``mapOneToMany(array $mapping)``
- ``mapManyToOne(array $mapping)``
- ``mapManyToMany(array $mapping)``
Lifecycle Callback Setters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``addLifecycleCallback($callback, $event)``
- ``setLifecycleCallbacks(array $callbacks)``
Versioning Setters
~~~~~~~~~~~~~~~~~~
- ``setVersionMapping(array &$mapping)``
- ``setVersioned($bool)``
- ``setVersionField()``
General Getters
~~~~~~~~~~~~~~~
- ``getTableName()``
- ``getSchemaName()``
- ``getTemporaryIdTableName()``
Identifier Getters
~~~~~~~~~~~~~~~~~~
- ``getIdentifierColumnNames()``
- ``usesIdGenerator()``
- ``isIdentifier($fieldName)``
- ``isIdGeneratorIdentity()``
- ``isIdGeneratorSequence()``
- ``isIdGeneratorTable()``
- ``isIdentifierNatural()``
- ``getIdentifierFieldNames()``
- ``getSingleIdentifierFieldName()``
- ``getSingleIdentifierColumnName()``
Inheritance Getters
~~~~~~~~~~~~~~~~~~~
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
Change Tracking Getters
~~~~~~~~~~~~~~~~~~~~~~~
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``isUniqueField($fieldName)``
- ``isNullable($fieldName)``
- ``isIndexed($fieldName)``
- ``getColumnName($fieldName)``
- ``getFieldMapping($fieldName)``
- ``getAssociationMapping($fieldName)``
- ``getAssociationMappings()``
- ``getFieldName($columnName)``
- ``hasField($fieldName)``
- ``getColumnNames(array $fieldNames = null)``
- ``getTypeOfField($fieldName)``
- ``getTypeOfColumn($columnName)``
- ``hasAssociation($fieldName)``
- ``isSingleValuedAssociation($fieldName)``
- ``isCollectionValuedAssociation($fieldName)``
Lifecycle Callback Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``hasLifecycleCallbacks($lifecycleEvent)``
- ``getLifecycleCallbacks($event)``
Runtime reflection methods
~~~~~~~~~~~~~~~~~~~~~~~~~~
These are methods related to runtime reflection for working with the
entities themselves.
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``
================================================
FILE: docs/en/reference/query-builder.rst
================================================
The QueryBuilder
================
A ``QueryBuilder`` provides an API that is designed for
conditionally constructing a DQL query in several steps.
It provides a set of classes and methods that is able to
programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
.. note::
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ `.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The same way you build a normal Query, you build a ``QueryBuilder``
object. Here is an example of how to build a ``QueryBuilder``
object:
.. code-block:: php
createQueryBuilder();
An instance of QueryBuilder has several informative methods. One
good example is to inspect what type of object the
``QueryBuilder`` is.
.. code-block:: php
getType(); // Prints: 0
There're currently 3 possible return values for ``getType()``:
- ``QueryBuilder::SELECT``, which returns value 0
- ``QueryBuilder::DELETE``, returning value 1
- ``QueryBuilder::UPDATE``, which returns value 2
It is possible to retrieve the associated ``EntityManager`` of the
current ``QueryBuilder``, its DQL and also a ``Query`` object when
you finish building your DQL.
.. code-block:: php
getEntityManager();
// example4: retrieve the DQL string of what was defined in QueryBuilder
$dql = $qb->getDql();
// example5: retrieve the associated Query object with the processed DQL
$q = $qb->getQuery();
Internally, ``QueryBuilder`` works with a DQL cache to increase
performance. Any changes that may affect the generated DQL actually
modifies the state of ``QueryBuilder`` to a stage we call
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
altered since last retrieval or nothing were added since its
instantiation
- ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will)
be processed on next retrieval
Working with QueryBuilder
~~~~~~~~~~~~~~~~~~~~~~~~~
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
``QueryBuilder`` helper methods:
.. code-block:: php
select('u')
->from('User', 'u')
->where('u.id = ?1')
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
build conditional expressions dynamically. Here is a converted example 8 to
suggested way to build queries with dynamic conditions:
.. code-block:: php
select(array('u')) // string 'u' is converted to array internally
->from('User', 'u')
->where($qb->expr()->orX(
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC');
Here is a complete list of helper methods available in ``QueryBuilder``:
.. code-block:: php
select('u')
// Example - $qb->select(array('u', 'p'))
// Example - $qb->select($qb->expr()->select('u', 'p'))
public function select($select = null);
// addSelect does not override previous calls to select
//
// Example - $qb->select('u');
// ->addSelect('p.area_code');
public function addSelect($select = null);
// Example - $qb->delete('User', 'u')
public function delete($delete = null, $alias = null);
// Example - $qb->update('Group', 'g')
public function update($update = null, $alias = null);
// Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold'))
// Example - $qb->set('u.numChilds', 'u.numChilds + ?1')
// Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1'))
public function set($key, $value);
// Example - $qb->from('Phonenumber', 'p')
// Example - $qb->from('Phonenumber', 'p', 'p.id')
public function from($from, $alias, $indexBy = null);
// Example - $qb->join('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
// Example - $qb->join('u.Group', 'g', 'WITH', 'u.status = ?1')
// Example - $qb->join('u.Group', 'g', 'WITH', 'u.status = ?1', 'g.id')
public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
// Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
// Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1')
// Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1', 'g.id')
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
// Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55))
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55', 'p.id')
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
// NOTE: ->where() overrides all previously set conditions
//
// Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
// Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
// Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
public function where($where);
// NOTE: ->andWhere() can be used directly, without any ->where() before
//
// Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
public function andWhere($where);
// Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
public function orWhere($where);
// NOTE: -> groupBy() overrides all previously set grouping conditions
//
// Example - $qb->groupBy('u.id')
public function groupBy($groupBy);
// Example - $qb->addGroupBy('g.name')
public function addGroupBy($groupBy);
// NOTE: -> having() overrides all previously set having conditions
//
// Example - $qb->having('u.salary >= ?1')
// Example - $qb->having($qb->expr()->gte('u.salary', '?1'))
public function having($having);
// Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0))
public function andHaving($having);
// Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))
public function orHaving($having);
// NOTE: -> orderBy() overrides all previously set ordering conditions
//
// Example - $qb->orderBy('u.surname', 'DESC')
public function orderBy($sort, $order = null);
// Example - $qb->addOrderBy('u.firstName')
public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
}
Binding parameters to your query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Doctrine supports dynamic binding of parameters to your query,
similar to preparing queries. You can use both strings and numbers
as placeholders, although both have a slightly different syntax.
Additionally, you must make your choice: Mixing both styles is not
allowed. Binding parameters can simply be achieved as follows:
.. code-block:: php
select('u')
->from('User', 'u')
->where('u.id = ?1')
->orderBy('u.name', 'ASC')
->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100
You are not forced to enumerate your placeholders as the
alternative syntax is available:
.. code-block:: php
select('u')
->from('User', 'u')
->where('u.id = :identifier')
->orderBy('u.name', 'ASC')
->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100
Note that numeric placeholders start with a ? followed by a number
while the named placeholders start with a : followed by a string.
Calling ``setParameter()`` automatically infers which type you are setting as
value. This works for integers, arrays of strings/integers, DateTime instances
and for managed entities. If you want to set a type explicitly you can call
the third argument to ``setParameter()`` explicitly. It accepts either a DBAL
``Doctrine\DBAL\ParameterType::*`` or a DBAL Type name for conversion.
.. note::
Even though passing DateTime instance is allowed, it impacts performance
as by default there is an attempt to load metadata for object, and if it's not found,
type is inferred from the original value.
.. code-block:: php
setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
following syntax:
.. code-block:: php
setParameters(new ArrayCollection([
new Parameter('1', 'value for ?1'),
new Parameter('2', 'value for ?2')
]));
Getting already bound parameters is easy - simply use the above
mentioned syntax with "getParameter()" or "getParameters()":
.. code-block:: php
getParameters();
// $params instanceof \Doctrine\Common\Collections\ArrayCollection
// Equivalent to
$param = $qb->getParameter(1);
// $param instanceof \Doctrine\ORM\Query\Parameter
Note: If you try to get a parameter that was not bound yet,
getParameter() simply returns NULL.
The API of a Query Parameter is:
.. code-block:: php
namespace Doctrine\ORM\Query;
class Parameter
{
public function getName();
public function getValue();
public function getType();
public function setValue($value, $type = null);
}
Limiting the Result
^^^^^^^^^^^^^^^^^^^
To limit a result the query builder has some methods in common with
the Query object which can be retrieved from ``EntityManager#createQuery()``.
.. code-block:: php
add('select', 'u')
->add('from', 'User u')
->add('orderBy', 'u.name ASC')
->setFirstResult( $offset )
->setMaxResults( $limit );
Executing a Query
^^^^^^^^^^^^^^^^^
The QueryBuilder is only a builder object - it has no means of actually
executing the Query. Additional functionality, such as enabling the result cache,
cannot be set on the QueryBuilder itself. This is why you must always convert
a QueryBuilder instance into a Query object:
.. code-block:: php
getQuery();
// Enable the result cache
$query->enableResultCache(3600, 'my_custom_id');
// Execute Query
$result = $query->getResult();
$iterableResult = $query->toIterable();
$single = $query->getSingleResult();
$array = $query->getArrayResult();
$scalar = $query->getScalarResult();
$singleScalar = $query->getSingleScalarResult();
The Expr class
^^^^^^^^^^^^^^
To workaround some of the issues that ``add()`` method may cause,
Doctrine created a class that can be considered as a helper for
building expressions. This class is called ``Expr``, which provides a
set of useful methods to help build expressions:
.. code-block:: php
add('select', new Expr\Select(array('u')))
->add('from', new Expr\From('User', 'u'))
->add('where', $qb->expr()->orX(
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Although it still sounds complex, the ability to programmatically
create conditions are the main feature of ``Expr``. Here it is a
complete list of supported helper methods available:
.. code-block:: php
expr()->andX($cond1 [, $condN])->add(...)->...
public function andX($x = null); // Returns Expr\AndX instance
// Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
public function orX($x = null); // Returns Expr\OrX instance
/** Comparison objects **/
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
public function eq($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1
public function neq($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1
public function lt($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1
public function lte($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1
public function gt($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1
public function gte($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isNull('u.id') => u.id IS NULL
public function isNull($x); // Returns string
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
// Example - $qb->expr()->isMemberOf('?1', 'u.groups') => ?1 MEMBER OF u.groups
public function isMemberOf($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isInstanceOf('u', Employee::class) => u INSTANCE OF Employee
public function isInstanceOf($x, $y); // Returns Expr\Comparison instance
/** Arithmetic objects **/
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
public function prod($x, $y); // Returns Expr\Math instance
// Example - $qb->expr()->diff('u.id', '2') => u.id - 2
public function diff($x, $y); // Returns Expr\Math instance
// Example - $qb->expr()->sum('u.id', '2') => u.id + 2
public function sum($x, $y); // Returns Expr\Math instance
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
public function quot($x, $y); // Returns Expr\Math instance
/** Pseudo-function objects **/
// Example - $qb->expr()->exists($qb2->getDql())
public function exists($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->all($qb2->getDql())
public function all($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->some($qb2->getDql())
public function some($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->any($qb2->getDql())
public function any($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1'))
public function not($restriction); // Returns Expr\Func instance
// Example - $qb->expr()->in('u.id', array(1, 2, 3))
// Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception.
// Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
public function in($x, $y); // Returns Expr\Func instance
// Example - $qb->expr()->notIn('u.id', '2')
public function notIn($x, $y); // Returns Expr\Func instance
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
public function like($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->notLike('u.firstname', $qb->expr()->literal('Gui%'))
public function notLike($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->between('u.id', '1', '10')
public function between($val, $x, $y); // Returns Expr\Func
/** Function objects **/
// Example - $qb->expr()->trim('u.firstname')
public function trim($x); // Returns Expr\Func
// Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat($qb->expr()->literal(' '), 'u.lastname'))
public function concat($x, $y); // Returns Expr\Func
// Example - $qb->expr()->substring('u.firstname', 0, 1)
public function substring($x, $from, $len); // Returns Expr\Func
// Example - $qb->expr()->lower('u.firstname')
public function lower($x); // Returns Expr\Func
// Example - $qb->expr()->upper('u.firstname')
public function upper($x); // Returns Expr\Func
// Example - $qb->expr()->length('u.firstname')
public function length($x); // Returns Expr\Func
// Example - $qb->expr()->avg('u.age')
public function avg($x); // Returns Expr\Func
// Example - $qb->expr()->max('u.age')
public function max($x); // Returns Expr\Func
// Example - $qb->expr()->min('u.age')
public function min($x); // Returns Expr\Func
// Example - $qb->expr()->abs('u.currentBalance')
public function abs($x); // Returns Expr\Func
// Example - $qb->expr()->sqrt('u.currentBalance')
public function sqrt($x); // Returns Expr\Func
// Example - $qb->expr()->mod('u.currentBalance', '10')
public function mod($x); // Returns Expr\Func
// Example - $qb->expr()->count('u.firstname')
public function count($x); // Returns Expr\Func
// Example - $qb->expr()->countDistinct('u.surname')
public function countDistinct($x); // Returns Expr\Func
}
Adding a Criteria to a Query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also add a :ref:`filtering-collections` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php
orderBy(['firstName' => Criteria::ASC]);
// $qb instanceof QueryBuilder
$qb->addCriteria($criteria);
// then execute your query like normal
Adding hints to a Query
^^^^^^^^^^^^^^^^^^^^^^^
You can also set query hints to a QueryBuilder by using ``setHint``:
.. code-block:: php
setHint('hintName', 'hintValue');
// then execute your query like normal
The query hint can hold anything the usual query hints can hold
except null. Those hints will be applied to the query when the
query is created.
Low Level API
^^^^^^^^^^^^^
Now we will describe the low level method of creating queries.
It may be useful to work at this level for optimization purposes,
but most of the time it is preferred to work at a higher level of
abstraction.
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
``$append`` (default=false)
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
Possible values: select, from, where, groupBy, having, orderBy
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
a string or any instance of ``Doctrine\ORM\Query\Expr\*``
- ``$append``: Optional flag (default=false) if the ``$dqlPart``
should override all previously defined items in ``$dqlPartName`` or
not (no effect on the ``where`` and ``having`` DQL query parts,
which always override all previously defined items)
.. code-block:: php
add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = ?1')
->add('orderBy', 'u.name ASC');
Expr\* classes
^^^^^^^^^^^^^^
When you call ``add()`` with string, it internally evaluates to an
instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the
same query of example 6 written using
``Doctrine\ORM\Query\Expr\Expr\*`` classes:
.. code-block:: php
add('select', new Expr\Select(array('u')))
->add('from', new Expr\From('User', 'u'))
->add('where', new Expr\Comparison('u.id', '=', '?1'))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Binding Parameters to Placeholders
----------------------------------
It is often not necessary to know about the exact placeholder names when
building a query. You can use a helper method to bind a value to a placeholder
and directly use that placeholder in your query as a return value:
.. code-block:: php
select('u')
->from('User', 'u')
->where('u.email = ' . $qb->createNamedParameter($userInputEmail))
;
// SELECT u FROM User u WHERE email = :dcValue1
================================================
FILE: docs/en/reference/second-level-cache.rst
================================================
The Second Level Cache
======================
.. note::
The second level cache functionality is marked as experimental for now. It
is a very complex feature and we cannot guarantee yet that it works stable
in all cases.
The Second Level Cache is designed to reduce the amount of necessary database access.
It sits between your application and the database to avoid the number of database hits as much as possible.
When turned on, entities will be first searched in cache and if they are not found,
a database query will be fired and then the entity result will be stored in a cache provider.
There are some flavors of caching available, but is better to cache read-only data.
Be aware that caches are not aware of changes made to the persistent store by another application.
They can, however, be configured to regularly expire cached data.
Caching Regions
---------------
Second level cache does not store instances of an entity, instead it caches only entity identifier and values.
Each entity class, collection association and query has its region, where values of each instance are stored.
Caching Regions are specific region into the cache provider that might store entities, collection or queries.
Each cache region resides in a specific cache namespace and has its own lifetime configuration.
Notice that when caching collection and queries only identifiers are stored.
The entity values will be stored in its own region
Something like below for an entity region:
.. code-block:: php
['id' => 1, 'name' => 'FooBar', 'associationName' => null],
'region_name:entity_2_hash' => ['id' => 2, 'name' => 'Foo', 'associationName' => ['id' => 11]],
'region_name:entity_3_hash' => ['id' => 3, 'name' => 'Bar', 'associationName' => ['id' => 22]]
];
If the entity holds a collection that also needs to be cached.
A collection region could look something like:
.. code-block:: php
['ownerId' => 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId' => 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId' => 3, 'list' => [2, 4]]
];
A query region might be something like:
.. code-block:: php
['list' => [1, 2, 3]],
'region_name:query_2_hash' => ['list' => [2, 3]],
'region_name:query_3_hash' => ['list' => [2, 4]]
];
.. note::
The following data structures represents now the cache will looks like, this is not actual cached data.
.. _reference-second-level-cache-regions:
Cache Regions
-------------
``Doctrine\ORM\Cache\Region\DefaultRegion`` is the default implementation.
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
define contracts that should be implemented by a cache provider.
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise.
Cache region
~~~~~~~~~~~~
``Doctrine\ORM\Cache\Region`` defines a contract for accessing a particular
cache region.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
A ``Doctrine\ORM\Cache\ConcurrentRegion`` is designed to store concurrently managed data region.
By default, Doctrine provides a very simple implementation based on file locks ``Doctrine\ORM\Cache\Region\FileLockRegion``.
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
``Doctrine\ORM\Cache\ConcurrentRegion`` defines a contract for concurrently managed data region.
Timestamp region
~~~~~~~~~~~~~~~~
``Doctrine\ORM\Cache\TimestampRegion``
Tracks the timestamps of the most recent updates to particular entity.
.. _reference-second-level-cache-mode:
Caching mode
------------
* ``READ_ONLY`` (DEFAULT)
* Can do reads, inserts and deletes, cannot perform updates or employ any locks.
* Useful for data that is read frequently but never updated.
* Best performer.
* It is Simple.
* ``NONSTRICT_READ_WRITE``
* Read Write Cache doesn’t employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
* Read Write cache employs locks before update/delete.
* Use if data needs to be updated.
* Slowest strategy.
* To use it the cache region implementation must support locking.
Built-in cached persisters
~~~~~~~~~~~~~~~~~~~~~~~~~~
Cached persisters are responsible to access cache regions.
+-----------------------+------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+==========================================================================================+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
Configuration
-------------
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
Enable Second Level Cache
~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory.
``Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
setSecondLevelCacheEnabled();
// Cache factory
$config->getSecondLevelCacheConfiguration()
->setCacheFactory($factory);
Cache Factory
~~~~~~~~~~~~~
Cache Factory is the main point of extension.
It allows you to provide a specific implementation of the following components:
``QueryCache``
stores and retrieves query cache results.
``CachedEntityPersister``
stores and retrieves entity results.
``CachedCollectionPersister``
stores and retrieves query results.
``EntityHydrator``
transforms entities into a cache entries and cache entries into entities
``CollectionHydrator``
transforms collections into cache entries and cache entries into collections
Region Lifetime
~~~~~~~~~~~~~~~
To specify a default lifetime for all regions or specify a different lifetime for a specific region.
.. code-block:: php
getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
// Cache Region lifetime
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region (in seconds)
$regionConfig->setDefaultLifetime(7200); // Default time to live (in seconds)
Cache Log
~~~~~~~~~
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
``Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
.. code-block:: php
setSecondLevelCacheEnabled(true);
$config->getSecondLevelCacheConfiguration()
->setCacheLogger($logger);
// Collect cache statistics
// Get the number of entries successfully retrieved from a specific region.
$logger->getRegionHitCount('my_entity_region');
// Get the number of cached entries *not* found in a specific region.
$logger->getRegionMissCount('my_entity_region');
// Get the number of cacheable entries put in cache.
$logger->getRegionPutCount('my_entity_region');
// Get the total number of put in all regions.
$logger->getPutCount();
// Get the total number of entries successfully retrieved from all regions.
$logger->getHitCount();
// Get the total number of cached entries *not* found in all regions.
$logger->getMissCount();
If you want to get more information you should implement
``Doctrine\ORM\Cache\Logging\CacheLogger`` and collect
all the information you want.
Entity cache definition
-----------------------
* Entity cache configuration allows you to define the caching strategy and region for an entity.
* ``usage`` specifies the caching strategy: ``READ_ONLY``,
``NONSTRICT_READ_WRITE``, ``READ_WRITE``.
See :ref:`reference-second-level-cache-mode`.
* ``region`` is an optional value that specifies the name of the second
level cache region.
.. configuration-block::
.. code-block:: attribute
Association cache definition
----------------------------
The most common use case is to cache entities. But we can also cache relationships.
It caches the primary keys of association and cache each element will be cached into its region.
.. configuration-block::
.. code-block:: attribute
*/
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
protected Collection $cities;
// other properties and methods
}
.. code-block:: xml
.. note::
for this to work, the target entity must also be marked as cacheable.
Cache usage
~~~~~~~~~~~
Basic entity cache
.. code-block:: php
persist(new Country($name));
$em->flush(); // Hit database to insert the row and put into cache
$em->clear(); // Clear entity manager
$country1 = $em->find('Country', 1); // Retrieve item from cache
$country1->setName('New Name');
$em->flush(); // Hit database to update the row and update cache
$em->clear(); // Clear entity manager
$country2 = $em->find('Country', 1); // Retrieve item from cache
// Notice that $country1 and $country2 are not the same instance.
Association cache
.. code-block:: php
persist(new State($name, $country));
$em->flush();
// Clear entity manager
$em->clear();
// Retrieve item from cache
$state = $em->find('State', 1);
// Hit database to update the row and update cache entry
$state->setName('New Name');
$em->persist($state);
$em->flush();
// Create a new collection item
$city = new City($name, $state);
$state->addCity($city);
// Hit database to insert new collection item,
// put entity and collection cache into cache.
$em->persist($city);
$em->persist($state);
$em->flush();
// Clear entity manager
$em->clear();
// Retrieve item from cache
$state = $em->find('State', 1);
// Retrieve association from cache
$country = $state->getCountry();
// Retrieve collection from cache
$cities = $state->getCities();
echo $country->getName();
echo $state->getName();
// Retrieve each collection item from cache
foreach ($cities as $city) {
echo $city->getName();
}
.. note::
Notice that all entities should be marked as cacheable.
Using the query cache
---------------------
The second level cache stores the entities, associations and collections.
The query cache stores the results of the query but as identifiers, entity values are actually stored in the 2nd level cache.
.. note::
Query cache should always be used in conjunction with the second-level-cache for those entities which should be cached.
.. code-block:: php
createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
$em->clear();
// Check if query result is valid and load entities from cache
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
Cache mode
~~~~~~~~~~
The Cache Mode controls how a particular query interacts with the second-level cache:
* ``Cache::MODE_GET`` - May read items from the cache, but will not add items.
* ``Cache::MODE_PUT`` - Will never read items from the cache, but will add items to the cache as it reads them from the database.
* ``Cache::MODE_NORMAL`` - May read items from the cache, and add items to the cache.
* ``Cache::MODE_REFRESH`` - The query will never read items from the cache, but will refresh items to the cache as it reads them from the database.
.. code-block:: php
createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheMode(\Doctrine\ORM\Cache::MODE_GET)
->setCacheable(true)
->getResult();
.. note::
The default query cache mode is ```Cache::MODE_NORMAL```
DELETE / UPDATE queries
~~~~~~~~~~~~~~~~~~~~~~~
DQL UPDATE / DELETE statements are ported directly into a database and bypass
the second-level cache.
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or a special query hint.
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
.. code-block:: php
_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(\Doctrine\ORM\Query::HINT_CACHE_EVICT, true)
->execute();
Execute the ``UPDATE`` and invalidate ``all cache entries`` using the cache API
.. code-block:: php
_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntityRegion('Entity\Country');
Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache API
.. code-block:: php
_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntity('Entity\Country', 1);
Using the repository query cache
--------------------------------
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
All persisters use a single timestamp cache region to keep track of the last update for each persister,
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
.. code-block:: php
getRepository('Entity\Country')->findAll();
// load from query and entities from cache..
$entities = $em->getRepository('Entity\Country')->findAll();
// update the timestamp cache region for Country
$em->persist(new Country('zombieland'));
$em->flush();
$em->clear();
// Reload from database.
// At this point the query cache key is no longer valid, the select goes straight to the database
$entities = $em->getRepository('Entity\Country')->findAll();
Cache API
---------
Caches are not aware of changes made by another application.
However, you can use the cache API to check / invalidate cache entries.
.. code-block:: php
getCache();
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
$cache->evictEntity('Entity\State', 1); // Remove an entity from cache
$cache->evictEntityRegion('Entity\State'); // Remove all entities from cache
$cache->containsCollection('Entity\State', 'cities', 1); // Check if the cache exists
$cache->evictCollection('Entity\State', 'cities', 1); // Remove an entity collection from cache
$cache->evictCollectionRegion('Entity\State', 'cities'); // Remove all collections from cache
Limitations
-----------
Composite primary key
~~~~~~~~~~~~~~~~~~~~~
Composite primary key are supported by second level cache,
however when one of the keys is an association the cached entity should always be retrieved using the association identifier.
For performance reasons the cache API does not extract from composite primary key.
.. code-block:: php
find('Article', 1);
// Supported
/** @var Article $article */
$article = $em->find('Article', $article);
// Supported
$id = ['source' => 1, 'target' => 2];
$reference = $em->find('Reference', $id);
// NOT Supported
$id = ['source' => new Article(1), 'target' => new Article(2)];
$reference = $em->find('Reference', $id);
Distributed environments
~~~~~~~~~~~~~~~~~~~~~~~~
Some cache driver are not meant to be used in a distributed environment.
Load-balancer for distributing workloads across multiple computing resources
should be used in conjunction with distributed caching system such as memcached, redis, riak ...
Caches should be used with care when using a load-balancer if you don't share the cache.
While using APC or any file based cache update occurred in a specific machine would not reflect to the cache in other machines.
Paginator
~~~~~~~~~
Count queries generated by ``Doctrine\ORM\Tools\Pagination\Paginator`` are not cached by second-level cache.
Although entities and query result are cached, count queries will hit the
database every time.
================================================
FILE: docs/en/reference/security.rst
================================================
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 also read the documentation chapter on Security in Doctrine DBAL. This
page only handles Security issues in the ORM.
- `DBAL Security Page `
If you find a Security bug in Doctrine, please follow our
`Security reporting guidelines `_.
User input and Doctrine ORM
---------------------------
The ORM is much better at protecting against SQL injection than the DBAL alone.
You can consider the following APIs to be safe from SQL injection:
- ``\Doctrine\ORM\EntityManager#find()`` and ``getReference()``.
- All values on Objects inserted and updated through ``Doctrine\ORM\EntityManager#persist()``
- All find methods on ``Doctrine\ORM\EntityRepository``.
- User Input set to DQL Queries or QueryBuilder methods through
- ``setParameter()`` or variants
- ``setMaxResults()``
- ``setFirstResult()``
- Queries through the Criteria API on ``Doctrine\ORM\PersistentCollection`` and
``Doctrine\ORM\EntityRepository``.
You are **NOT** safe from SQL injection when using user input with:
- Expression API of ``Doctrine\ORM\QueryBuilder``
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
Native SQL.
This means SQL injections can only occur with Doctrine ORM when working with
Query Objects of any kind. The safe rule is to always use prepared statement
parameters for user objects when using a Query object.
.. warning::
Insecure code follows, don't copy paste this.
The following example shows insecure DQL usage:
.. code-block:: php
createQuery($dql);
$query->setParameter(1, $_GET['status']);
Preventing Mass Assignment Vulnerabilities
------------------------------------------
ORMs are very convenient for CRUD applications and Doctrine is no exception.
However CRUD apps are often vulnerable to mass assignment security problems
when implemented naively.
Doctrine is not vulnerable to this problem out of the box, but you can easily
make your entities vulnerable to mass assignment when you add methods of
the kind ``updateFromArray()`` or ``updateFromJson()`` to them. A vulnerable
entity might look like this:
.. code-block:: php
$userInput */
public function fromArray(array $userInput): void
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
}
}
}
Now the possibility of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:
.. code-block:: php
fromArray($_POST);
$entityManager->persist($entity);
$entityManager->flush();
You can spot this problem in this very simple example easily. However
in combination with frameworks and form libraries it might not be
so obvious when this issue arises. Be careful to avoid this
kind of mistake.
How to fix this problem? You should always have a whitelist
of allowed key to set via mass assignment functions.
.. code-block:: php
public function fromArray(array $userInput, $allowedFields = array())
{
foreach ($userInput as $key => $value) {
if (in_array($key, $allowedFields)) {
$this->$key = $value;
}
}
}
================================================
FILE: docs/en/reference/tools.rst
================================================
Tools
=====
Doctrine Console
----------------
The Doctrine Console is a Command Line Interface tool for simplifying common
administration tasks during the development of a project that uses ORM.
For the following examples, we will set up the CLI as ``bin/doctrine``.
Setting Up the Console
~~~~~~~~~~~~~~~~~~~~~~
Whenever the ``doctrine`` command line tool is invoked, it can
access all Commands that were registered by a developer. There is no
auto-detection mechanism at work. The Doctrine binary
already registers all the commands that currently ship with
Doctrine DBAL and ORM. If you want to use additional commands you
have to register them yourself.
All the commands of the Doctrine Console require access to the
``EntityManager``. You have to inject it into the console application.
Here is an example of a project-specific ``bin/doctrine`` binary.
.. code-block:: php
#!/usr/bin/env php
php bin/doctrine orm:generate-entities --help
Command Overview
~~~~~~~~~~~~~~~~
The following Commands are currently available:
- ``help`` Displays help for a command (?)
- ``list`` Lists commands
- ``dbal:import`` Import SQL file(s) directly to Database.
- ``dbal:run-sql`` Executes arbitrary SQL directly from the
command line.
- ``orm:clear-cache:metadata`` Clear all metadata cache of the
various cache drivers.
- ``orm:clear-cache:query`` Clear all query cache of the various
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes. Deprecated in favor of using native lazy objects.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either
create it directly on EntityManager Storage Connection or generate
the SQL output.
- ``orm:schema-tool:drop`` Processes the schema and either drop
the database schema of EntityManager Storage Connection or generate
the SQL output.
- ``orm:schema-tool:update`` Processes the schema and either
update the database schema of EntityManager Storage Connection or
generate the SQL output.
- ``orm:debug:event-manager`` Lists event listeners for an entity
manager, optionally filtered by event name.
- ``orm:debug:entity-listeners`` Lists entity listeners for a given
entity, optionally filtered by event name.
The following alias is defined:
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
.. note::
Console also supports auto completion, for example, instead of
``orm:clear-cache:query`` you can use just ``o:c:q``.
Database Schema Generation
--------------------------
.. note::
SchemaTool can do harm to your database. It will drop or alter
tables, indexes, sequences and such. Please use this tool with
caution in development and not on a production server. It is meant
for helping you develop your Database Schema, but NOT with
migrating schema from A to B in production. A safe approach would
be generating the SQL on development server and saving it into SQL
Migration files that are executed manually on the production
server.
SchemaTool assumes your Doctrine Project uses the given database on
its own. Update and Drop commands will mess with other tables if
they are not related to the current project that is using Doctrine.
Please be careful!
To generate your database schema from your Doctrine mapping files
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
Command.
When using the SchemaTool class directly, create your schema using
the ``createSchema()`` method. First create an instance of the
``SchemaTool`` and pass it an instance of the ``EntityManager``
that you want to use to create the schema. This method receives an
array of ``ClassMetadata`` instances.
.. code-block:: php
getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$tool->createSchema($classes);
To drop the schema you can use the ``dropSchema()`` method.
.. code-block:: php
dropSchema($classes);
This drops all the tables that are currently used by your metadata
model. When you are changing your metadata a lot during development
you might want to drop the complete database instead of only the
tables of the current model to clean up with orphaned tables.
.. code-block:: php
dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE);
You can also use database introspection to update your schema
easily with the ``updateSchema()`` method. It will compare your
existing database schema to the passed array of ``ClassMetadata``
instances.
.. code-block:: php
updateSchema($classes);
If you want to use this functionality from the command line you can
use the ``schema-tool`` command.
To create the schema use the ``create`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:create
To drop the schema use the ``drop`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:drop
If you want to drop and then recreate the schema then use both
options:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:drop
$ php bin/doctrine orm:schema-tool:create
As you would think, if you want to update your schema use the
``update`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:update
All of the above commands also accept a ``--dump-sql`` option that
will output the SQL for the ran operation.
.. code-block:: php
$ php bin/doctrine orm:schema-tool:create --dump-sql
Runtime vs Development Mapping Validation
-----------------------------------------
For performance reasons Doctrine ORM has to skip some of the
necessary validation of metadata mappings. You have to execute
this validation in your development workflow to verify the
associations are correctly defined.
You can either use the Doctrine Command Line Tool:
.. code-block:: php
doctrine orm:validate-schema
If the validation fails, you can change the verbosity level to
check the detected errors:
doctrine orm:validate-schema -v
Or you can trigger the validation manually:
.. code-block:: php
validateMapping();
if (count($errors) > 0) {
// Lots of errors!
echo implode("\n\n", $errors);
}
If the mapping is invalid the errors array contains a positive
number of elements with error messages.
.. warning::
One mapping option that is not validated is the use of the referenced column name.
It has to point to the equivalent primary key otherwise Doctrine will not work.
.. note::
One common error is to use a backlash in front of the
fully-qualified class-name. Whenever a FQCN is represented inside a
string (such as in your mapping definitions) you have to drop the
prefix backslash. PHP does this with ``get_class()`` or Reflection
methods for backwards compatibility reasons.
Adding own commands
-------------------
You can also add your own commands on-top of the Doctrine supported
tools if you are using a manually built console script.
To include a new command on Doctrine Console, you need to do modify the
``doctrine.php`` file a little:
.. code-block:: php
setCatchExceptions(true);
$cli->setHelperSet($helperSet);
// Register All Doctrine Commands
ConsoleRunner::addCommands($cli);
// Register your own command
$cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand);
// Runs console application
$cli->run();
Additionally, include multiple commands (and overriding previously
defined ones) is possible through the command:
.. code-block:: php
addCommands(array(
new \MyProject\Tools\Console\Commands\MyCustomCommand(),
new \MyProject\Tools\Console\Commands\SomethingCommand(),
new \MyProject\Tools\Console\Commands\AnotherCommand(),
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
));
Re-use console application
--------------------------
You are also able to retrieve and re-use the default console application.
Just call ``ConsoleRunner::createApplication(...)`` with an appropriate
HelperSet, like it is described in the configuration section.
.. code-block:: php
run();
================================================
FILE: docs/en/reference/transactions-and-concurrency.rst
================================================
Transactions and Concurrency
============================
.. _transactions-and-concurrency_transaction-demarcation:
Transaction Demarcation
-----------------------
Transaction demarcation is the task of defining your transaction
boundaries. Proper transaction demarcation is very important
because if not done properly it can negatively affect the
performance of your application. Many databases and database
abstraction layers like PDO by default operate in auto-commit mode,
which means that every single SQL statement is wrapped in a small
transaction. Without any explicit transaction demarcation from your
side, this quickly results in poor performance because transactions
are not cheap.
For the most part, Doctrine ORM already takes care of proper
transaction demarcation for you: All the write operations
(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()``
is invoked which wraps all of these changes in a single
transaction.
However, Doctrine ORM also allows (and encourages) you to take over
and control transaction demarcation yourself.
These are two ways to deal with transactions when using the
Doctrine ORM and are now described in more detail.
.. _transactions-and-concurrency_approach-implicitly:
Approach 1: Implicitly
~~~~~~~~~~~~~~~~~~~~~~
The first approach is to use the implicit transaction handling
provided by the Doctrine ORM EntityManager. Given the following
code snippet, without any explicit transaction demarcation:
.. code-block:: php
setName('George');
$em->persist($user);
$em->flush();
Since we do not do any custom transaction demarcation in the above
code, ``EntityManager#flush()`` will begin and commit/rollback a
transaction. This behavior is made possible by the aggregation of
the DML operations by the Doctrine ORM and is sufficient if all the
data manipulation that is part of a unit of work happens through
the domain model and thus the ORM.
.. _transactions-and-concurrency_approach-explicitly:
Approach 2: Explicitly
~~~~~~~~~~~~~~~~~~~~~~
The explicit alternative is to use the ``Doctrine\DBAL\Connection``
API directly to control the transaction boundaries. The code then
looks like this:
.. code-block:: php
getConnection()->beginTransaction(); // suspend auto-commit
try {
// ... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollBack();
throw $e;
}
Explicit transaction demarcation is required when you want to
include custom DBAL operations in a unit of work or when you want
to make use of some methods of the ``EntityManager`` API that
require an active transaction. Such methods will throw a
``TransactionRequiredException`` to inform you of that
requirement.
A more convenient alternative for explicit transaction demarcation is the use
of provided control abstractions in the form of
``Connection#transactional($func)`` and ``EntityManager#wrapInTransaction($func)``.
When used, these control abstractions ensure that you never forget to rollback
the transaction, in addition to the obvious code reduction. An example that is
functionally equivalent to the previously shown code looks as follows:
.. code-block:: php
transactional(function($conn) {
// ... do some work
$user = new User;
$user->setName('George');
});
// transactional with EntityManager instance
// $em instanceof EntityManager
$em->wrapInTransaction(function($em) {
// ... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
});
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and in case of an exception the ``EntityManager`` gets closed
in addition to the transaction rollback.
.. _transactions-and-concurrency_exception-handling:
Exception Handling
~~~~~~~~~~~~~~~~~~
When using implicit transaction demarcation and an exception occurs
during ``EntityManager#flush()``, the transaction is automatically
rolled back and the ``EntityManager`` closed.
When using explicit transaction demarcation and an exception
occurs, the transaction should be rolled back immediately and the
``EntityManager`` closed by invoking ``EntityManager#close()`` and
subsequently discarded, as demonstrated in the example above. This
can be handled elegantly by the control abstractions shown earlier.
Note that when catching ``Exception`` you should generally re-throw
the exception. If you intend to recover from some exceptions, catch
them explicitly in earlier catch blocks (but do not forget to
rollback the transaction and close the ``EntityManager`` there as
well). All other best practices of exception handling apply
similarly (i.e. either log or re-throw, not both, etc.).
As a result of this procedure, all previously managed or removed
instances of the ``EntityManager`` become detached. The state of
the detached objects will be the state at the point at which the
transaction was rolled back. The state of the objects is in no way
rolled back and thus the objects are now out of synch with the
database. The application can continue to use the detached objects,
knowing that their state is potentially no longer accurate.
If you intend to start another unit of work after an exception has
occurred you should do that with a new ``EntityManager``.
.. _transactions-and-concurrency_locking-support:
Locking Support
---------------
Doctrine ORM offers support for Pessimistic- and Optimistic-locking
strategies natively. This allows to take very fine-grained control
over what kind of locking is required for your Entities in your
application.
.. _transactions-and-concurrency_optimistic-locking:
Optimistic Locking
~~~~~~~~~~~~~~~~~~
Database transactions are fine for concurrency control during a
single request. However, a database transaction should not span
across requests, the so-called "user think time". Therefore a
long-running "business transaction" that spans multiple requests
needs to involve several database transactions. Thus, database
transactions alone can no longer control concurrency during such a
long-running business transaction. Concurrency control becomes the
partial responsibility of the application itself.
Doctrine has integrated support for automatic optimistic locking
via a version field. In this approach any entity that should be
protected against concurrent modifications during long-running
business transactions gets a version field that is either a simple
number (mapping type: integer) or a timestamp (mapping type:
datetime). When changes to such an entity are persisted at the end
of a long-running conversation the version of the entity is
compared to the version in the database and if they don't match, an
``OptimisticLockException`` is thrown, indicating that the entity
has been modified by someone else already.
You designate a version field in an entity as follows. In this
example we'll use an integer.
.. configuration-block::
.. code-block:: attribute
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
.. configuration-block::
.. code-block:: attribute
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent
environment, unlike timestamps where this is a possibility,
depending on the resolution of the timestamp on the particular
database platform.
When a version conflict is encountered during
``EntityManager#flush()``, an ``OptimisticLockException`` is thrown
and the active transaction rolled back (or marked for rollback).
This exception can be caught and handled. Potential responses to an
OptimisticLockException are to present the conflict to the user or
to refresh or reload objects in a new transaction and then retrying
the transaction.
With PHP promoting a share-nothing architecture, the time between
showing an update form and actually modifying the entity can in the
worst scenario be as long as your applications session timeout. If
changes happen to the entity in that time frame you want to know
directly when retrieving the entity that you will hit an optimistic
locking exception:
You can always verify the version of an entity during a request
either when calling ``EntityManager#find()``:
.. code-block:: php
find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Or you can use ``EntityManager#lock()`` to find out:
.. code-block:: php
find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Important Implementation Notes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can easily get the optimistic locking workflow wrong if you
compare the wrong versions. Say you have Alice and Bob editing a
hypothetical blog post:
- Alice reads the headline of the blog post being "Foo", at
optimistic lock version 1 (GET Request)
- Bob reads the headline of the blog post being "Foo", at
optimistic lock version 1 (GET Request)
- Bob updates the headline to "Bar", upgrading the optimistic lock
version to 2 (POST Request of a Form)
- Alice updates the headline to "Baz", ... (POST Request of a
Form)
Now at the last stage of this scenario the blog post has to be read
again from the database before Alice's headline can be applied. At
this point you will want to check if the blog post is still at
version 1 (which it is not in this scenario).
Using optimistic locking correctly, you *have* to add the version
as an additional hidden field (or into the SESSION for more
safety). Otherwise you cannot verify the version is still the one
being originally read from the database when Alice performed her
GET request for the blog post. If this happens you might see lost
updates you wanted to prevent with Optimistic Locking.
See the example code, The form (GET Request):
.. code-block:: php
find('BlogPost', 123456);
echo '';
echo '';
And the change headline action (POST Request):
.. code-block:: php
find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~
Doctrine ORM supports Pessimistic Locking at the database level. No
attempt is being made to implement pessimistic locking inside
Doctrine, rather vendor-specific and ANSI-SQL commands are used to
acquire row-level locks. Every Entity can be part of a pessimistic
lock, there is no special metadata required to use this feature.
However for Pessimistic Locking to work you have to disable the
Auto-Commit Mode of your Database and start a transaction around
your pessimistic lock use-case using the "Approach 2: Explicit
Transaction Demarcation" described above. Doctrine ORM will throw an
Exception if you attempt to acquire an pessimistic lock and no
transaction is running.
Doctrine ORM currently supports two pessimistic lock modes:
- Pessimistic Write
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
underlying database rows for concurrent Read and Write Operations.
- Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``),
locks other concurrent requests that attempt to update or lock rows
in write mode.
You can use pessimistic locks in four different scenarios:
1. Using
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
2. Using
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
3. Using
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
4. Using
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
================================================
FILE: docs/en/reference/typedfieldmapper.rst
================================================
Implementing a TypedFieldMapper
===============================
.. versionadded:: 2.14
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
.. code-block:: php
setTypedFieldMapper(new CustomTypedFieldMapper());
DefaultTypedFieldMapper
-----------------------
By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.
.. code-block:: php
setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
]));
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
(``CustomIdObjectType``) without the need of explicit declaration.
.. configuration-block::
.. code-block:: attribute
.. code-block:: yaml
UserTypedWithCustomTypedField:
type: entity
fields:
customId: ~
It is perfectly valid to override even the "automatic" mapping rules mentioned above:
.. code-block:: php
setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
]));
.. note::
If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.
TypedFieldMapper interface
-------------------------
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
typed field mapping logic. It consists of just one function
.. code-block:: php
setTypedFieldMapper(
new ChainTypedFieldMapper(
new DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
Implementing a TypedFieldMapper
-------------------------------
If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
typed field mapper.
You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.
.. code-block:: php
getType();
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['type'] = BackedEnumDBALType::class;
}
}
return $mapping;
}
}
.. note::
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
================================================
FILE: docs/en/reference/unitofwork-associations.rst
================================================
Association Updates: Owning Side and Inverse Side
=================================================
When mapping bidirectional associations it is important to
understand the concept of the owning and inverse sides. The
following general rules apply:
- Relationships may be bidirectional or unidirectional.
- A bidirectional relationship has both an owning side and an inverse side
- A unidirectional relationship only has an owning side.
- Doctrine will **only** check the owning side of an association for changes.
Bidirectional Associations
--------------------------
The following rules apply to **bidirectional** associations:
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
OneToMany, or ManyToMany mapping declaration. The ``mappedBy``
attribute contains the name of the association-field on the owning side.
- The owning side has to have the ``inversedBy`` attribute of the
OneToOne, ManyToOne, or ManyToMany mapping declaration.
The ``inversedBy`` attribute contains the name of the association-field
on the inverse-side.
- ManyToOne is always the owning side of a bidirectional association.
- OneToMany is always the inverse side of a bidirectional association.
- The owning side of a OneToOne association is the entity with the table
containing the foreign key.
- You can pick the owning side of a many-to-many association yourself.
Important concepts
------------------
**Doctrine will only check the owning side of an association for changes.**
To fully understand this, remember how bidirectional associations
are maintained in the object world. There are 2 references on each
side of the association and these 2 references both represent the
same association but can change independently of one another. Of
course, in a correct application the semantics of the bidirectional
association are properly maintained by the application developer
(that's their responsibility). Doctrine needs to know which of these
two in-memory references is the one that should be persisted and
which not. This is what the owning/inverse concept is mainly used
for.
**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)**
The owning side of a bidirectional association is the side Doctrine
"looks at" when determining the state of the association, and
consequently whether there is anything to do to update the
association in the database.
.. note::
"Owning side" and "inverse side" are technical concepts of
the ORM technology, not concepts of your domain model. What you
consider as the owning side in your domain model can be different
from what the owning side is for Doctrine. These are unrelated.
================================================
FILE: docs/en/reference/unitofwork.rst
================================================
Doctrine Internals explained
============================
Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power.
How Doctrine keeps track of Objects
-----------------------------------
Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an
object from the database, Doctrine will keep a reference to this object inside
its UnitOfWork. The array holding all the entity references is two-levels deep
and has the keys "root entity name" and "id". Since Doctrine allows composite
keys the id is a sorted, serialized version of all the key columns.
This allows Doctrine room for optimizations. If you call the EntityManager and
ask for an entity with a specific ID twice, it will return the same instance:
.. code-block:: php
public function testIdentityMap(): void
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
$this->assertSame($objectA, $objectB)
}
Only one SELECT query will be fired against the database here. In the second
``EntityManager#find()`` call Doctrine will check the identity map first and
doesn't need to make that database roundtrip.
Even if you get a proxy object first then fetch the object by the same id you
will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference(): void
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check entity is not initialized
$this->assertTrue($this->entityManager->isUninitializedObject($objectA));
$objectB = $this->entityManager->find('EntityName', 1);
$this->assertSame($objectA, $objectB)
}
The identity map being indexed by primary keys only allows shortcuts when you
ask for objects by primary key. Assume you have the following ``persons``
table:
::
id | name
-------------
1 | Benjamin
2 | Bud
Take the following example where two
consecutive calls are made against a repository to fetch an entity by a set of
criteria:
.. code-block:: php
public function testIdentityMapRepositoryFindBy()
{
$repository = $this->entityManager->getRepository('Person');
$objectA = $repository->findOneBy(array('name' => 'Benjamin'));
$objectB = $repository->findOneBy(array('name' => 'Benjamin'));
$this->assertSame($objectA, $objectB);
}
This query will still return the same references and `$objectA` and `$objectB`
are indeed referencing the same object. However when checking your SQL logs you
will realize that two queries have been executed against the database. Doctrine
only knows objects by id, so a query for different criteria has to go to the
database, even if it was executed just before.
But instead of creating a second Person object Doctrine first gets the primary
key from the row and check if it already has an object inside the UnitOfWork
with that primary key. In our example it finds an object and decides to return
this instead of creating a new one.
The identity map has a second use-case. When you call ``EntityManager#flush``
Doctrine will ask the identity map for all objects that are currently managed.
This means you don't have to call ``EntityManager#persist`` over and over again
to pass known objects to the EntityManager. This is a NO-OP for known entities,
but leads to much code written that is confusing to other developers.
The following code WILL update your database with the changes made to the
``Person`` object, even if you did not call ``EntityManager#persist``:
.. code-block:: php
find("Person", 1);
$user->setName("Guilherme");
$entityManager->flush();
How Doctrine Detects Changes
----------------------------
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map PHP objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
associations inside the UnitOfWork. Because variables in the PHP language are
subject to "copy-on-write" the memory usage of a PHP request that only reads
objects from the database is the same as if Doctrine did not keep this variable
copy. Only if you start changing variables PHP will create new variables internally
that consume new memory.
Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the
Identity Map and for each object compares the original property and association
values with the values that are currently set on the object. If changes are
detected then the object is queued for a SQL UPDATE operation. Only the fields
that actually changed are updated.
This process has an obvious performance impact. The larger the size of the
UnitOfWork is, the longer this computation takes. There are several ways to
optimize the performance of the Flush Operation:
- Mark entities as read only. These entities can only be inserted or removed,
but are never updated. They are omitted in the changeset calculation.
- Temporarily mark entities as read only. If you have a very large UnitOfWork
but know that a large set of entities has not changed, just mark them as read
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
- Use :doc:`Change Tracking Policies ` to use more
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
Query Internals
---------------
The different ORM Layers
------------------------
Doctrine ships with a set of layers with different responsibilities. This
section gives a short explanation of each layer.
Hydration
~~~~~~~~~
Responsible for creating a final result from a raw database statement and a
result-set mapping object. The developer can choose which kind of result they
wish to be hydrated. Default result-types include:
- SQL to Entities
- SQL to structured Arrays
- SQL to simple scalar result arrays
- SQL to a single result variable
Hydration to entities and arrays is one of the most complex parts of Doctrine
algorithm-wise. It can build results with for example:
- Single table selects
- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.
- Mixed results of objects and scalar values
- Hydration of results by a given scalar value as key.
Persisters
~~~~~~~~~~
tbr
UnitOfWork
~~~~~~~~~~
tbr
ResultSetMapping
~~~~~~~~~~~~~~~~
tbr
DQL Parser
~~~~~~~~~~
tbr
SQLWalker
~~~~~~~~~
tbr
EntityManager
~~~~~~~~~~~~~
tbr
ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr
================================================
FILE: docs/en/reference/working-with-associations.rst
================================================
Working with Associations
=========================
Associations between entities are represented just like in regular
object-oriented PHP code using references to other objects or
collections of objects.
Changes to associations in your code are not synchronized to the
database directly, only when calling ``EntityManager#flush()``.
There are other concepts you should know about when working
with associations in Doctrine:
- If an entity is removed from a collection, the association is
removed, not the entity itself. A collection of entities always
only represents the association to the containing entities, not the
entity itself.
- When a bidirectional association is updated, Doctrine only checks
on one of both sides for these changes. This is called the :doc:`owning side `
of the association.
- A property with a reference to many entities has to be instances of the
``Doctrine\Common\Collections\Collection`` interface.
Association Example Entities
----------------------------
We will use a simple comment system with Users and Comments as
entities to show examples of association management. See the PHP
docblocks of each association in the following example for
information about its type and if it's the owning or inverse side.
.. code-block:: php
*/
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
#[JoinTable(name: 'user_favorite_comments')]
private Collection $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @var Collection
*/
#[ManyToMany(targetEntity: Comment::class)]
#[JoinTable(name: 'user_read_comments')]
private Collection $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @var Collection
*/
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
private Collection $commentsAuthored;
/** Unidirectional - Many-To-One */
#[ManyToOne(targetEntity: Comment::class)]
private Comment|null $firstComment = null;
}
#[Entity]
class Comment
{
#[Id, GeneratedValue, Column]
private string $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @var Collection
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
private Collection $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*/
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
private User|null $author = null;
}
This two entities generate the following MySQL Schema (Foreign Key
definitions omitted):
.. code-block:: sql
CREATE TABLE User (
id VARCHAR(255) NOT NULL,
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, comment_id)
) ENGINE = InnoDB;
Establishing Associations
-------------------------
Establishing an association between two entities is
straight-forward. Here are some examples for the unidirectional
relations of the ``User``:
.. code-block:: php
*/
public function getReadComments(): Collection {
return $this->commentsRead;
}
public function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
}
The interaction code would then look like in the following snippet
(``$em`` here is an instance of the EntityManager):
.. code-block:: php
find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
In the case of bi-directional associations you have to update the
fields on both sides:
.. code-block:: php
*/
public function getAuthoredComments(): Collection {
return $this->commentsAuthored;
}
/** @return Collection */
public function getFavoriteComments(): Collection {
return $this->favorites;
}
}
class Comment
{
// ...
/** @return Collection */
public function getUserFavorites(): Collection {
return $this->userFavorites;
}
public function setAuthor(User|null $author = null): void {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
Notice how always both sides of the bidirectional association are
updated. The previous unidirectional associations were simpler to
handle.
Removing Associations
---------------------
Removing an association between two entities is similarly
straight-forward. There are two strategies to do so, by key and by
element. Here are some examples:
.. code-block:: php
getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->remove($ithComment);
$comment->setAuthor(null);
You need to call ``$em->flush()`` to make persist these changes in
the database permanently.
Notice how both sides of the bidirectional association are always
updated. Unidirectional associations are consequently simpler to
handle.
Also note that if you use type-hinting in your methods, you will
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
otherwise ``setAddress(null)`` will fail to remove the association.
Another way to deal with this is to provide a special method, like
``removeAddress()``. This can also provide better encapsulation as
it hides the internal meaning of not having an address.
When working with collections, keep in mind that a Collection is
essentially an ordered map (just like a PHP array). That is why the
``remove`` operation accepts an index/key. ``removeElement`` is a
separate method that has O(n) complexity using ``array_search``,
where n is the size of the map.
.. note::
Since Doctrine always only looks at the owning side of a
bidirectional association for updates, it is not necessary for
write operations that an inverse collection of a bidirectional
one-to-many or many-to-many association is updated. This knowledge
can often be used to improve performance by avoiding the loading of
the inverse collection.
You can also clear the contents of a whole collection using the
``Collections::clear()`` method. You should be aware that using
this method can lead to a straight and optimized database delete or
update call during the flush operation that is not aware of
entities that have been re-added to the collection.
Say you clear a collection of tags by calling
``$post->getTags()->clear();`` and then call
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
calls.
Association Management Methods
------------------------------
It is generally a good idea to encapsulate proper association
management inside the entity classes. This makes it easier to use
the class correctly and can encapsulate details about how the
association is maintained.
The following code shows updates to the previous User and Comment
example that encapsulate much of the association management code:
.. code-block:: php
commentsRead[] = $comment;
}
public function addComment(Comment $comment): void {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment): void {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment): void {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user): void {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user): void {
$this->userFavorites->removeElement($user);
}
}
You will notice that ``addUserFavorite`` and ``removeUserFavorite``
do not call ``addFavorite`` and ``removeFavorite``, thus the
bidirectional association is strictly-speaking still incomplete.
However if you would naively add the ``addFavorite`` in
``addUserFavorite``, you end up with an infinite loop, so more work
is needed. As you can see, proper bidirectional association
management in plain OOP is a non-trivial task and encapsulating all
the details inside the classes can be challenging.
.. note::
If you want to make sure that your collections are perfectly
encapsulated you should not return them from a
``getCollectionName()`` method directly, but call
``$collection->toArray()``. This way a client programmer for the
entity cannot circumvent the logic you implement on your entity for
association management. For example:
.. code-block:: php
*/
public function getReadComments(): array {
return $this->commentsRead->toArray();
}
}
This will however always initialize the collection, with all the
performance penalties given the size. In some scenarios of large
collections it might even be a good idea to completely hide the
read access behind methods on the EntityRepository.
There is no single, best way for association management. It greatly
depends on the requirements of your concrete domain model as well
as your preferences.
Synchronizing Bidirectional Collections
---------------------------------------
In the case of Many-To-Many associations you as the developer have the
responsibility of keeping the collections on the owning and inverse side
in sync when you apply changes to them. Doctrine can only
guarantee a consistent state for the hydration, not for your client
code.
Using the User-Comment entities from above, a very simple example
can show the possible caveats you can encounter:
.. code-block:: php
getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
There are two approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next request Doctrine hydrates the consistent collection state
again.
2. Always keep the bidirectional collections in sync through
association management methods. Reads of the Collections directly
after changes are consistent then.
.. _transitive-persistence:
Transitive persistence / Cascade Operations
-------------------------------------------
Doctrine ORM provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``detach``, ``refresh`` or ``all``.
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
comment might look like in your controller (without ``cascade: persist``):
.. code-block:: php
addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
$em->flush();
Note that the Comment entity is instantiated right here in the controller.
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
only accessing it through the User entity:
.. code-block:: php
*/
private Collection $comments;
public function __construct()
{
$this->id = User::new();
$this->comments = new ArrayCollection();
}
public function comment(string $text, DateTimeInterface $time) : void
{
$newComment = Comment::create($text, $time);
$newComment->setUser($this);
$this->comments->add($newComment);
}
// ...
}
If you then set up the cascading to the ``User#commentsAuthored`` property...
.. code-block:: php
comment('Lorem ipsum', new DateTime());
$em->persist($user);
$em->flush();
.. note::
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
.. code-block:: php
find('User', $deleteUserId);
$em->remove($user);
$em->flush();
.. note::
Cascade operations are performed in memory. That means collections and related entities
are fetched into memory (even if they are marked as lazy) when
the cascade operation is about to be performed. This approach allows
entity lifecycle events to be performed for each of these operations.
However, pulling object graphs into memory on cascade can cause considerable performance
overhead, especially when the cascaded collections are large. Make sure
to weigh the benefits and downsides of each cascade operation that you define.
To rely on the database level cascade operations for the delete operation instead, you can
configure each join column with :doc:`the onDelete option `.
Even though automatic cascading is convenient, it should be used
with care. Do not blindly apply ``cascade=all`` to all associations as
it will unnecessarily degrade the performance of your application.
For each cascade operation that gets activated, Doctrine also
applies that operation to the association, be it single or
collection valued.
.. _persistence-by-reachability:
Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are additional semantics that apply to the Cascade Persist
operation. During each ``flush()`` operation Doctrine detects if there
are new entities in any collection and three possible cases can
happen:
1. New entities in a collection marked as ``cascade: persist`` will be
directly persisted by Doctrine.
2. New entities in a collection not marked as ``cascade: persist`` will
produce an Exception and rollback the ``flush()`` operation.
3. Collections without new entities are skipped.
This concept is called Persistence by Reachability: New entities
that are found on already managed entities are automatically
persisted as long as the association is defined as ``cascade: persist``.
Orphan Removal
--------------
There is another concept of cascading that is relevant only when removing entities
from collections. If an Entity of type ``A`` contains references to privately
owned Entities ``B`` then if the reference from ``A`` to ``B`` is removed the
entity ``B`` should also be removed, because it is not used anymore.
OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
.. note::
When using the ``orphanRemoval=true`` option Doctrine makes the assumption
that the entities are privately owned and will **NOT** be reused by other entities.
If you neglect this assumption your entities will get deleted by Doctrine even if
you assigned the orphaned entity to another one.
.. note::
``orphanRemoval=true`` option should be used in combination with ``cascade=["persist"]`` option
as the child entity, that is manually persisted, will not be deleted automatically by Doctrine
when a collection is still an instance of ArrayCollection (before first flush / hydration).
This is a Doctrine limitation since ArrayCollection does not have access to a UnitOfWork.
As a better example consider an Addressbook application where you have Contacts, Addresses
and StandingData:
.. code-block:: php
*/
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
private Collection $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd): void
{
$this->standingData = $sd;
}
public function removeAddress(int $pos): void
{
unset($this->addresses[$pos]);
}
}
Now two examples of what happens when you remove the references:
.. code-block:: php
find("Addressbook\Contact", $contactId);
$contact->newStandingData(new StandingData("Firstname", "Lastname", "Street"));
$contact->removeAddress(1);
$em->flush();
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
.. _filtering-collections:
Filtering Collections
---------------------
Collections have a filtering API that allows to slice parts of data from
a collection. If the collection has not been loaded from the database yet,
the filtering API can work on the SQL level to make optimized access to
large collections.
.. code-block:: php
find('Group', $groupId);
$userCollection = $group->getUsers();
$criteria = Criteria::create()
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
->orderBy(array("username" => Criteria::ASC))
->setFirstResult(0)
->setMaxResults(20)
;
$birthdayUsers = $userCollection->matching($criteria);
.. tip::
You can move the access of slices of collections into dedicated methods of
an entity. For example ``Group#getTodaysBirthdayUsers()``.
The Criteria has a limited matching language that works both on the
SQL and on the PHP collection level. This means you can use collection matching
interchangeably, independent of in-memory or sql-backed collections.
.. code-block:: php
`.
In this case, field names passed to expressions are being used to derive accessor
method names. Strict type comparisons are used for equal and not-equal checks,
and generally PHP language rules are being used for other comparison operators
or sorting.
As a general guidance, for consistent results use the Criteria API with scalar
values only. Note that ``DateTime`` and ``DateTimeImmutable`` are two predominant
examples of value objects that are *not* scalars.
Refrain from using special database-level column types or custom Doctrine Types
that may lead to database-specific comparison or sorting rules being applied, or
to database-level values being different from object field values.
Provide accessor methods for all entity fields used in criteria expressions,
and implement those methods in a way that their return value is the
same as the database-level value.
================================================
FILE: docs/en/reference/working-with-objects.rst
================================================
Working with Objects
====================
In this chapter we will help you understand the ``EntityManager``
and the ``UnitOfWork``. A Unit of Work is similar to an
object-level transaction. A new Unit of Work is implicitly started
when an EntityManager is initially created or after
``EntityManager#flush()`` has been invoked. A Unit of Work is
committed (and a new one started) by invoking
``EntityManager#flush()``.
A Unit of Work can be manually closed by calling
EntityManager#close(). Any changes to objects within this Unit of
Work that have not yet been persisted are lost.
.. note::
It is very important to understand that only
``EntityManager#flush()`` ever causes write operations against the
database to be executed. Any other methods such as
``EntityManager#persist($entity)`` or
``EntityManager#remove($entity)`` only notify the UnitOfWork to
perform these operations during flush.
Not calling ``EntityManager#flush()`` will lead to all changes
during that request being lost.
.. note::
Doctrine NEVER touches the public API of methods in your entity
classes (like getters and setters) nor the constructor method.
Instead, it uses reflection to get/set data from/to your entity objects.
When Doctrine fetches data from DB and saves it back,
any code put in your get/set methods won't be implicitly taken into account.
Entities and the Identity Map
-----------------------------
Entities are objects with identity. Their identity has a conceptual
meaning inside your domain. In a CMS application each article has a
unique id. You can uniquely identify each article by that id.
Take the following example, where you find an article with the
headline "Hello World" with the ID 1234:
.. code-block:: php
find('CMS\Article', 1234);
$article->setHeadline('Hello World dude!');
$article2 = $entityManager->find('CMS\Article', 1234);
echo $article2->getHeadline();
In this case the Article is accessed from the entity manager twice,
but modified in between. Doctrine ORM realizes this and will only
ever give you access to one instance of the Article with ID 1234,
no matter how often do you retrieve it from the EntityManager and
even no matter what kind of Query method you are using (find,
Repository Finder or DQL). This is called "Identity Map" pattern,
which means Doctrine keeps a map of each entity and ids that have
been retrieved per PHP request and keeps returning you the same
instances.
In the previous example the echo prints "Hello World dude!" to the
screen. You can even verify that ``$article`` and ``$article2`` are
indeed pointing to the same instance by running the following
code:
.. code-block:: php
*/
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
private Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getAuthor(): User|null { return $this->author; }
public function getComments(): Collection { return $this->comments; }
}
$article = $em->find('Article', 1);
This code only retrieves the ``Article`` instance with id 1 executing
a single SELECT statement against the articles table in the database.
You can still access the associated properties author and comments
and the associated objects they contain.
This works by utilizing the lazy loading pattern. Instead of
passing you back a real Author instance and a collection of
comments Doctrine will create proxy instances for you. Only if you
access these proxies for the first time they will go through the
EntityManager and load their state from the database.
This lazy-loading process happens behind the scenes, hidden from
your code. See the following code:
.. code-block:: php
find('Article', 1);
// accessing a method of the user instance triggers the lazy-load
echo "Author: " . $article->getAuthor()->getName() . "\n";
// Lazy Loading Proxies pass instanceof tests:
if ($article->getAuthor() instanceof User) {
// a User Proxy is a generated "UserProxy" class
}
// accessing the comments as an iterator triggers the lazy-load
// retrieving ALL the comments of this article from the database
// using a single SELECT statement
foreach ($article->getComments() as $comment) {
echo $comment->getText() . "\n\n";
}
// Article::$comments passes instanceof tests for the Collection interface
// But it will NOT pass for the ArrayCollection interface
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
echo "This will always be true!";
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
easily trigger lots of SQL queries and will perform badly if used
too heavily. Make sure to use DQL to fetch-join all the parts of the
object-graph that you need as efficiently as possible.
Persisting entities
-------------------
An entity can be made persistent by passing it to the
``EntityManager#persist($entity)`` method. By applying the persist
operation on some entity, that entity becomes MANAGED, which means
that its persistence is from now on managed by an EntityManager. As
a result the persistent state of such an entity will subsequently
be properly synchronized with the database when
``EntityManager#flush()`` is invoked.
.. note::
Invoking the ``persist`` method on an entity does NOT
cause an immediate SQL INSERT to be issued on the database.
Doctrine applies a strategy called "transactional write-behind",
which means that it will delay most SQL commands until
``EntityManager#flush()`` is invoked which will then issue all
necessary SQL statements to synchronize your objects with the
database in the most efficient way and a single, short transaction,
taking care of maintaining referential integrity.
.. note::
Do not make any assumptions in your code about the number of queries
it takes to flush changes, about the ordering of ``INSERT``, ``UPDATE``
and ``DELETE`` queries or the order in which entities will be processed.
Example:
.. code-block:: php
setName('Mr.Right');
$em->persist($user);
$em->flush();
.. note::
Generated entity identifiers / primary keys are
guaranteed to be available after the next successful flush
operation that involves the entity in question. You can not rely on
a generated identifier to be available directly after invoking
``persist``. The inverse is also true. You can not rely on a
generated identifier being not available after a failed flush
operation.
The semantics of the persist operation, applied on an entity X, are
as follows:
- If X is a new entity, it becomes managed. The entity X will be
entered into the database as a result of the flush operation.
- If X is a preexisting managed entity, it is ignored by the
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
":ref:`transitive-persistence`").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
.. caution::
Do not pass detached entities to the persist operation. The persist operation always
considers entities that are not yet known to the ``EntityManager`` as new entities
(refer to the ``STATE_NEW`` constant inside the ``UnitOfWork``).
Removing entities
-----------------
An entity can be removed from persistent storage by passing it to
the ``EntityManager#remove($entity)`` method. By applying the
``remove`` operation on some entity, that entity becomes REMOVED,
which means that its persistent state will be deleted once
``EntityManager#flush()`` is invoked.
.. note::
Just like ``persist``, invoking ``remove`` on an entity
does NOT cause an immediate SQL DELETE to be issued on the
database. The entity will be deleted on the next invocation of
``EntityManager#flush()`` that involves that entity. This
means that entities scheduled for removal can still be queried
for and appear in query and collection results. See
the section on :ref:`Database and UnitOfWork Out-Of-Sync `
for more information.
Example:
.. code-block:: php
remove($user);
$em->flush();
The semantics of the remove operation, applied to an entity X are
as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`transitive-persistence`").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
- A removed entity X will be removed from the database as a result
of the flush operation.
After an entity has been removed, its in-memory state is the same as
before the removal, except for generated identifiers.
During the ``EntityManager#flush()`` operation, the removed entity
will also be removed from all collections in entities currently
loaded into memory.
.. _remove_object_many_to_many_join_tables:
Join-table management when removing from many-to-many collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Regarding existing rows in many-to-many join tables that refer to
an entity being removed, the following applies.
When the entity being removed does not declare the many-to-many association
itself (that is, the many-to-many association is unidirectional and
the entity is on the inverse side), the ORM has no reasonable way to
detect associations targeting the entity's class. Thus, no ORM-level handling
of join-table rows is attempted and database-level constraints apply.
In case of database-level ``ON DELETE RESTRICT`` constraints, the
``EntityManager#flush()`` operation may abort and a ``ConstraintViolationException``
may be thrown. No in-memory collections will be modified in this case.
With ``ON DELETE CASCADE``, the RDBMS will take care of removing rows
from join tables.
When the entity being removed is part of bi-directional many-to-many
association, either as the owning or inverse side, the ORM will
delete rows from join tables before removing the entity itself. That means
database-level ``ON DELETE RESTRICT`` constraints on join tables are not
effective, since the join table rows are removed first. Removal of join table
rows happens through specialized methods in entity and collection persister
classes and take one query per entity and join table. In case the association
uses a ``@JoinColumn`` configuration with ``onDelete="CASCADE"``, instead
of using a dedicated ``DELETE`` query the database-level operation will be
relied upon.
.. note::
In case you rely on database-level ``ON DELETE RESTRICT`` constraints,
be aware that by making many-to-many associations bidirectional the
assumed protection may be lost.
Performance of different deletion strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM will
fetch this association. If it's a Single association it will pass
this entity to ``EntityManager#remove()``. If the association is a
collection, Doctrine will loop over all its elements and pass them to
``EntityManager#remove()``.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple
entities of a type with a single command and without hydrating
these entities. This can be very efficient to delete large object
graphs from the database.
3. Using foreign key semantics ``onDelete="CASCADE"`` can force the
database to remove all associated objects internally. This strategy
is a bit tricky to get right but can be very powerful and fast. You
should be aware however that using strategy 1 (``CASCADE=REMOVE``)
completely by-passes any foreign key ``onDelete=CASCADE`` option,
because Doctrine will fetch and remove all associated entities
explicitly nevertheless.
.. note::
Calling ``remove`` on an entity will remove the object from the identity
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
Detaching entities
------------------
An entity is detached from an EntityManager and thus no longer
managed by invoking the ``EntityManager#detach($entity)`` method on
it or by cascading the detach operation to it. Changes made to the
detached entity, if any (including removal of the entity), will not
be synchronized to the database after the entity has been
detached.
Doctrine will not hold on to any references to a detached entity.
Example:
.. code-block:: php
detach($entity);
The semantics of the detach operation, applied to an entity X are
as follows:
- If X is a managed entity, the detach operation causes it to
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
automatically without invoking the ``detach`` method:
- When ``EntityManager#clear()`` is invoked, all entities that are
currently managed by the EntityManager instance become detached.
- When serializing an entity. The entity retrieved upon subsequent
unserialization will be detached (This is the case for all entities
that are serialized and stored in some cache).
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.
Synchronization with the Database
---------------------------------
The state of persistent entities is synchronized with the database
on flush of an ``EntityManager`` which commits the underlying
``UnitOfWork``. The synchronization involves writing any updates to
persistent entities and their relationships to the database.
Thereby bidirectional relationships are persisted based on the
references held by the owning side of the relationship as explained
in the Association Mapping chapter.
When ``EntityManager#flush()`` is called, Doctrine inspects all
managed, new and removed entities and will perform the following
operations.
.. _workingobjects_database_uow_outofsync:
Effects of Database and UnitOfWork being Out-Of-Sync
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As soon as you begin to change the state of entities, call persist or remove the
contents of the UnitOfWork and the database will drive out of sync. They can
only be synchronized by calling ``EntityManager#flush()``. This section
describes the effects of database and UnitOfWork being out of sync.
- Entities that are scheduled for removal can still be queried from the database.
They are returned from DQL and Repository queries and are visible in collections.
- Entities that are passed to ``EntityManager#persist`` do not turn up in query
results.
- Entities that have changed will not be overwritten with the state from the database.
This is because the identity map will detect the construction of an already existing
entity and assumes its the most up to date version.
``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually.
Synchronizing New and Managed Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flush operation applies to a managed entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
UPDATE statement, only if at least one persistent field has
changed.
- No SQL updates are executed if the entity did not change.
The flush operation applies to a new entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
INSERT statement.
For all (initialized) relationships of the new or managed entity
the following semantics apply to each associated entity X:
- If X is new and persist operations are configured to cascade on
the relationship, X will be persisted.
- If X is new and no persist operations are configured to cascade
on the relationship, an exception will be thrown as this indicates
a programming error.
- If X is removed and persist operations are configured to cascade
on the relationship, an exception will be thrown as this indicates
a programming error (X would be re-persisted by the cascade).
- If X is detached and persist operations are configured to
cascade on the relationship, an exception will be thrown (This is
semantically the same as passing X to persist()).
Synchronizing Removed Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flush operation applies to a removed entity by deleting its
persistent state from the database. No cascade options are relevant
for removed entities on flush, the cascade remove option is already
executed during ``EntityManager#remove($entity)``.
The size of a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~
The size of a Unit of Work mainly refers to the number of managed
entities at a particular point in time.
The cost of flushing
~~~~~~~~~~~~~~~~~~~~
How costly a flush operation is, mainly depends on two factors:
- The size of the EntityManager's current UnitOfWork.
- The configured change tracking policies
You can get the size of a UnitOfWork as follows:
.. code-block:: php
getUnitOfWork()->size();
The size represents the number of managed entities in the Unit of
Work. This size affects the performance of flush() operations due
to change tracking (see "Change Tracking Policies") and, of course,
memory consumption, so you may want to check it from time to time
during development.
.. note::
Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
single HTTP request there should be usually no need for invoking
``flush`` more than 0-2 times.
Direct access to a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can get direct access to the Unit of Work by calling
``EntityManager#getUnitOfWork()``. This will return the UnitOfWork
instance the EntityManager is currently using.
.. code-block:: php
getUnitOfWork();
.. note::
Directly manipulating a UnitOfWork is not recommended.
When working directly with the UnitOfWork API, respect methods
marked as INTERNAL by not using them and carefully read the API
documentation.
Entity State
~~~~~~~~~~~~
As outlined in the architecture overview an entity can be in one of
four possible states: NEW, MANAGED, REMOVED, DETACHED. If you
explicitly need to find out what the current state of an entity is
in the context of a certain ``EntityManager`` you can ask the
underlying ``UnitOfWork``:
.. code-block:: php
getUnitOfWork()->getEntityState($entity)) {
case UnitOfWork::STATE_MANAGED:
...
case UnitOfWork::STATE_REMOVED:
...
case UnitOfWork::STATE_DETACHED:
...
case UnitOfWork::STATE_NEW:
...
}
An entity is in MANAGED state if it is associated with an
``EntityManager`` and it is not REMOVED.
An entity is in REMOVED state after it has been passed to
``EntityManager#remove()`` until the next flush operation of the
same EntityManager. A REMOVED entity is still associated with an
``EntityManager`` until the next flush operation.
An entity is in DETACHED state if it has persistent state and
identity but is currently not associated with an
``EntityManager``.
An entity is in NEW state if has no persistent state and identity
and is not associated with an ``EntityManager`` (for example those
just created via the "new" operator).
Querying
--------
Doctrine ORM provides the following ways, in increasing level of
power and flexibility, to query for persistent objects. You should
always start with the simplest one that suits your needs.
By Primary Key
~~~~~~~~~~~~~~
The most basic way to query for a persistent object is by its
identifier / primary key using the
``EntityManager#find($entityName, $id)`` method. Here is an
example:
.. code-block:: php
find('MyProject\Domain\User', $id);
The return value is either the found entity instance or null if no
instance could be found with the given identifier.
Essentially, ``EntityManager#find()`` is just a shortcut for the
following:
.. code-block:: php
getRepository('MyProject\Domain\User')->find($id);
``EntityManager#getRepository($entityName)`` returns a repository
object which provides many ways to retrieve entities of the
specified type. By default, the repository instance is of type
``Doctrine\ORM\EntityRepository``. You can also use custom
repository classes as shown later.
By Simple Conditions
~~~~~~~~~~~~~~~~~~~~
To query for one or more entities based on several conditions that
form a logical conjunction, use the ``findBy`` and ``findOneBy``
methods on a repository as follows:
.. code-block:: php
getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
You can also load by owning side associations through the repository:
.. code-block:: php
find('MyProject\Domain\Phonenumber', 1234);
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
.. code-block:: php
getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically:
.. code-block:: php
getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40)));
// translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
An EntityRepository also provides a mechanism for more concise
calls through its use of ``__call``. Thus, the following two
examples are equivalent:
.. code-block:: php
getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Additionally, you can just count the result of the provided conditions when you don't really need the data:
.. code-block:: php
getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
By Criteria
~~~~~~~~~~~
The Repository implement the ``Doctrine\Common\Collections\Selectable``
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
and pass them to the ``matching($criteria)`` method.
See section `Filtering collections` of chapter :doc:`Working with Associations `
By Eager Loading
~~~~~~~~~~~~~~~~
Whenever you query for an entity that has persistent associations
and these associations are mapped as EAGER, they will automatically
be loaded together with the entity being queried and is thus
immediately available to your application.
Eager Loading can also be configured at runtime through
``AbstractQuery::setFetchMode`` in DQL or Native Queries.
Eager loading for many-to-one and one-to-one associations is using either a
LEFT JOIN or a second query for fetching the related entity eagerly.
Eager loading for many-to-one associations uses a second query to load
the collections for several entities at the same time.
When many-to-many, one-to-one or one-to-many associations are eagerly loaded,
then the global batch size configuration is used to avoid IN(?) queries with
too many arguments. The default batch size is 100 and can be changed with
``Configuration::setEagerFetchBatchSize()``.
For eagerly loaded Many-To-Many associations one query has to be made for each
collection.
By Lazy Loading
~~~~~~~~~~~~~~~
Whenever you have a managed entity instance at hand, you can
traverse and use any associations of that entity that are
configured LAZY as if they were in-memory already. Doctrine will
automatically load the associated objects on demand through the
concept of lazy-loading.
By DQL
~~~~~~
The most powerful and flexible method to query for persistent
objects is the Doctrine Query Language, an object query language.
DQL enables you to query for persistent objects in the language of
objects. DQL understands classes, fields, inheritance and
associations. DQL is syntactically very similar to the familiar SQL
but *it is not SQL*.
A DQL query is represented by an instance of the
``Doctrine\ORM\Query`` class. You create a query using
``EntityManager#createQuery($dql)``. Here is a simple example:
.. code-block:: php
createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult();
Note that this query contains no knowledge about the relational
schema, only about the object model. DQL supports positional as
well as named parameters, many functions, (fetch) joins,
aggregates, subqueries and much more. Detailed information about
DQL and its syntax as well as the Doctrine class can be found in
:doc:`the dedicated chapter `.
For programmatically building up queries based on conditions that
are only known at runtime, Doctrine provides the special
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
it also brings more complexity to your code compared to plain DQL,
so you should only use it when you need it. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter `.
By Native Queries
~~~~~~~~~~~~~~~~~
As an alternative to DQL or as a fallback for special SQL
statements native queries can be used. Native queries are built by
using a hand-crafted SQL query and a ResultSetMapping that
describes how the SQL result set should be transformed by Doctrine.
More information about native queries can be found in
:doc:`the dedicated chapter `.
Custom Repositories
~~~~~~~~~~~~~~~~~~~
By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Attribute or XML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
.. code-block:: php
*/
public function getAllAdminUsers(): Collection
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
You can access your repository now by calling:
.. code-block:: php
getRepository('MyDomain\Model\User')->getAllAdminUsers();
================================================
FILE: docs/en/reference/xml-mapping.rst
================================================
XML Mapping
===========
The XML mapping driver enables you to provide the ORM metadata in
form of XML documents. It requires the ``dom`` extension in order to be
able to validate your mapping documents against its XML Schema.
The XML driver is backed by an XML Schema document that describes
the structure of a mapping document. The most recent version of the
XML Schema document is available online at
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd `_.
The most convenient way to work with
XML mapping files is to use an IDE/editor that can provide
code-completion based on such an XML Schema document. The following
is an outline of a XML mapping document with the proper xmlns/xsi
setup for the latest code in trunk.
.. code-block:: xml
...
The XML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated XML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.). For example an Entity with the fully
qualified class-name "MyProject" would require a mapping file
"MyProject.Entities.User.dcm.xml" unless the extension is changed.
- All mapping documents should get the extension ".dcm.xml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
.. code-block:: php
getLocator()->setFileExtension('.xml');
It is recommended to put all XML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the XmlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
.. code-block:: php
setMetadataDriverImpl($driver);
.. warning::
Note that Doctrine ORM does not modify any settings for ``libxml``,
therefore, external XML entities may or may not be enabled or
configured correctly.
XML mappings are not XXE/XEE attack vectors since they are not
related with user input, but it is recommended that you do not
use external XML entities in your mapping files to avoid running
into unexpected behaviour.
Simplified XML Driver
~~~~~~~~~~~~~~~~~~~~~
The Symfony project sponsored a driver that simplifies usage of the XML Driver.
The changes between the original driver are:
1. File Extension is .orm.xml
2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.xml
3. You can add a global file and add multiple entities in this file.
Configuration of this client works a little bit different:
.. code-block:: php
'MyProject\Entities',
'/path/to/files2' => 'OtherProject\Entities'
);
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces);
$driver->setGlobalBasename('global'); // global.orm.xml
Example
-------
As a quick start, here is a small example document that makes use
of several common elements:
.. code-block:: xml
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
Be aware that class-names specified in the XML files should be
fully qualified.
XML-Element Reference
---------------------
The XML-Element reference explains all the tags and attributes that
the Doctrine Mapping XSD Schema defines. You should read the
Basic-, Association- and Inheritance Mapping chapters to understand
what each of this definitions means in detail.
Defining an Entity
~~~~~~~~~~~~~~~~~~
Each XML Mapping File contains the definition of one entity,
specified as the ```` element as a direct child of the
```` element:
.. code-block:: xml
Required attributes:
- name - The fully qualified class-name of the entity.
Optional attributes:
- **table** - The Table-Name to be used for this entity. Otherwise the
Unqualified Class-Name is used by default.
- **repository-class** - The fully qualified class-name of an
alternative ``Doctrine\ORM\EntityRepository`` implementation to be
used with this entity.
- **inheritance-type** - The type of inheritance, defaults to none. A
more detailed description follows in the
*Defining Inheritance Mappings* section.
- **read-only** - Specifies that this entity is marked as read only and not
considered for change-tracking. Entities of this type can be persisted
and removed though.
- **schema** - The schema the table lies in, for platforms that support schemas
Defining Fields
~~~~~~~~~~~~~~~
Each entity class can contain zero to infinite fields that are
managed by Doctrine. You can define them using the ````
element as a children to the ```` element. The field
element is only used for primitive types that are not the ID of the
entity. For the ID mapping you have to use the ```` element.
.. code-block:: xml
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
Optional attributes:
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
"string"
- column - Name of the column in the database, defaults to the
field name.
- length - The length of the given type, for use with strings
only.
- unique - Should this field contain a unique value across the
table? Defaults to false.
- index - Should an index be created for this column? Defaults to
false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- insertable - Should this field be inserted? Defaults to true.
- updatable - Should this field be updated? Defaults to true.
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
generated value must be fetched from database after INSERT or UPDATE.
Defaults to "NEVER".
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
- precision - Precision of a decimal type.
- options - Array of additional options:
- default - The default value to set for the column if no value
is supplied.
- unsigned - Boolean value to determine if the column should
be capable of representing only non-negative integers
(applies only for integer column and might not be supported by
all vendors).
- fixed - Boolean value to determine if the specified length of
a string column should be fixed or varying (applies only for
string/binary column and might not be supported by all vendors).
- comment - The comment of the column in the schema (might not
be supported by all vendors).
- customSchemaOptions - Array of additional schema options
which are mostly vendor specific.
- column-definition - Optional alternative SQL representation for
this column. This definition begin after the field-name and has to
specify the complete column definition. Using this feature will
turn this field dirty for Schema-Tool update commands at all
times.
.. note::
For more detailed information on each attribute, please refer to
the DBAL ``Schema-Representation`` documentation.
Defining Identity and Generator Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An entity has to have at least one ```` element. For
composite keys you can specify more than one id-element, however
surrogate keys are recommended for use with Doctrine ORM. The Id
field allows to define properties of the identifier and allows a
subset of the ```` element attributes:
.. code-block:: xml
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
"string" or "integer".
Optional attributes:
- column - Name of the column in the database, defaults to the
field name.
Using the simplified definition above Doctrine will use no
identifier strategy for this entity. That means you have to
manually set the identifier before calling
``EntityManager#persist($entity)``. This is the so called
``NONE`` strategy.
If you want to switch the identifier generation strategy you have
to nest a ```` element inside the id-element. This of
course only works for surrogate keys. For composite keys you always
have to use the ``NONE`` strategy.
.. code-block:: xml
The following values are allowed for the ```` strategy
attribute:
- AUTO - Automatic detection of the identifier strategy based on
the preferred solution of the database vendor.
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
available to Doctrine AFTER the INSERT statement has been executed.
- SEQUENCE - Use of a database sequence to retrieve the
entity-ids. This is possible before the INSERT statement is
executed.
If you are using the SEQUENCE strategy you can define an additional
element to describe the sequence:
.. code-block:: xml
Required attributes for ````:
- sequence-name - The name of the sequence
Optional attributes for ````:
- allocation-size - By how much steps should the sequence be
incremented when a value is retrieved. Defaults to 1
- initial-value - What should the initial value of the sequence
be.
**NOTE**
If you want to implement a cross-vendor compatible application you
have to specify and additionally define the
element, if Doctrine chooses the sequence strategy for a
platform.
Defining a Mapped Superclass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you want to define a class that multiple entities inherit
from, which itself is not an entity however. The chapter on
*Inheritance Mapping* describes a Mapped Superclass in detail. You
can define it in XML using the ```` tag.
.. code-block:: xml
Required attributes:
- name - Class name of the mapped superclass.
You can nest any number of ```` and unidirectional
```` or ```` associations inside a
mapped superclass.
Defining Inheritance Mappings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are currently two inheritance persistence strategies that you
can choose from when defining entities that inherit from each
other. Single Table inheritance saves the fields of the complete
inheritance hierarchy in a single table, joined table inheritance
creates a table for each entity combining the fields using join
conditions.
You can specify the inheritance type in the ```` element
and then use the ```` and
```` attributes.
.. code-block:: xml
The allowed values for inheritance-type attribute are ``JOINED`` or
``SINGLE_TABLE``.
.. note::
All inheritance related definitions have to be defined on the root
entity of the hierarchy.
Defining Lifecycle Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can define the lifecycle callback methods on your entities
using the ```` element:
.. code-block:: xml
Defining One-To-One Relations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can define One-To-One Relations/Associations using the
```` element. The required and optional attributes
depend on the associations being on the inverse or owning side.
For the inverse side the mapping is as simple as:
.. code-block:: xml
Required attributes for inverse One-To-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
- mapped-by - Name of the field on the owning side (here Address
entity) that contains the owning side association.
For the owning side this mapping would look like:
.. code-block:: xml
Required attributes for owning One-to-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes for owning One-to-One:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- orphan-removal - If true, the inverse side entity is always
deleted when the owning side entity is. Defaults to false.
- fetch - Either LAZY or EAGER, defaults to LAZY. This attribute
makes only sense on the owning side, the inverse side *ALWAYS* has
to use the ``FETCH`` strategy.
The definition for the owning side relies on a bunch of mapping
defaults for the join column names. Without the nested
```` element Doctrine assumes to foreign key to be
called ``user_id`` on the Address Entities table. This is because
the ``MyProject\Address`` entity is the owning side of this
association, which means it contains the foreign key.
The completed explicitly defined mapping is:
.. code-block:: xml
Defining Many-To-One Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The many-to-one association is *ALWAYS* the owning side of any
bidirectional association. This simplifies the mapping compared to
the one-to-one case. The minimal mapping for this association looks
like:
.. code-block:: xml
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- orphan-removal - If true the entity on the inverse side is
always deleted when the owning side entity is and it is not
connected to any other owning side entity anymore. Defaults to
false.
- fetch - Either LAZY or EAGER, defaults to LAZY.
This definition relies on a bunch of mapping defaults with regards
to the naming of the join-column/foreign key. The explicitly
defined mapping includes a ```` tag nested inside
the many-to-one association tag:
.. code-block:: xml
The join-column attribute ``name`` specifies the column name of the
foreign key and the ``referenced-column-name`` attribute specifies
the name of the primary key column on the User entity.
Defining One-To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The one-to-many association is *ALWAYS* the inverse side of any
association. There exists no such thing as a uni-directional
one-to-many association, which means this association only ever
exists for bi-directional associations.
.. code-block:: xml
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
- mapped-by - Name of the field on the owning side (here
Phonenumber entity) that contains the owning side association.
Optional attributes:
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
- index-by: Index the collection by a field on the target entity.
Defining Many-To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From all the associations the many-to-many has the most complex
definition. When you rely on the mapping defaults you can omit many
definitions and rely on their implicit values.
.. code-block:: xml
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
- mapped-by - Name of the field on the owning side that contains
the owning side association if the defined many-to-many association
is on the inverse side.
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
- index-by: Index the collection by a field on the target entity.
The mapping defaults would lead to a join-table with the name
"User\_Group" being created that contains two columns "user\_id"
and "group\_id". The explicit definition of this mapping would be:
.. code-block:: xml
Here both the ```` and ````
tags are necessary to tell Doctrine for which side the specified
join-columns apply. These are nested inside a ````
attribute which allows to specify the table name of the
many-to-many join-table.
Cascade Element
~~~~~~~~~~~~~~~
Doctrine allows cascading of several UnitOfWork operations to
related entities. You can specify the cascade operations in the
```` element inside any of the association mapping
tags.
.. code-block:: xml
Besides ```` the following operations can be
specified by their respective tags:
- ````
- ````
- ````
- ````
Join Column Element
~~~~~~~~~~~~~~~~~~~
In any explicitly defined association mapping you will need the
```` tag. It defines how the foreign key and primary
key names are called that are used for joining two entities.
Required attributes:
- name - The column name of the foreign key.
- referenced-column-name - The column name of the associated
entities primary key
Optional attributes:
- unique - If the join column should contain a UNIQUE constraint.
This makes sense for Many-To-Many join-columns only to simulate a
one-to-many unidirectional using a join-table.
- nullable - should the join column be nullable, defaults to true.
- on-delete - Foreign Key Cascade action to perform when entity is
deleted, defaults to NO ACTION/RESTRICT but can be set to
"CASCADE".
Defining Order of To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can require one-to-many or many-to-many associations to be
retrieved using an additional ``ORDER BY``.
.. code-block:: xml
Defining Indexes or Unique Constraints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To define additional indexes or unique constraints on the entities
table you can use the ```` and
```` elements:
.. code-block:: xml
You have to specify the column and not the entity-class field names
in the index and unique-constraint definitions.
Derived Entities ID syntax
~~~~~~~~~~~~~~~~~~~~~~~~~~
If the primary key of an entity contains a foreign key to another entity we speak of a derived
entity relationship. You can define this in XML with the "association-key" attribute in the ```` tag.
.. code-block:: xml
================================================
FILE: docs/en/sidebar.rst
================================================
:orphan:
.. toctree::
:caption: Tutorials
:depth: 3
tutorials/getting-started
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
.. toctree::
:caption: Reference
:depth: 3
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-hydration
reference/partial-objects
reference/attributes-reference
reference/xml-mapping
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. toctree::
:caption: Cookbook
:depth: 3
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/generated-columns
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
================================================
FILE: docs/en/tutorials/composite-primary-keys.rst
================================================
Composite and Foreign Keys as Primary Key
=========================================
Doctrine ORM supports composite primary keys natively. Composite keys are a very powerful relational database concept
and we took good care to make sure Doctrine ORM supports as many of the composite primary key use-cases.
For Doctrine ORM composite keys of primitive data-types are supported, even foreign keys as
primary keys are supported.
This tutorial shows how the semantics of composite primary keys work and how they map to the database.
General Considerations
~~~~~~~~~~~~~~~~~~~~~~
Every entity with a composite key cannot use an id generator other than "NONE". That means
the ID fields have to have their values set before you call ``EntityManager#persist($entity)``.
Primitive Types only
~~~~~~~~~~~~~~~~~~~~
You can have composite keys as long as they only consist of the primitive types
``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name
and year of production as primary keys:
.. configuration-block::
.. code-block:: attribute
name;
}
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: xml
Now you can use this entity:
.. code-block:: php
persist($car);
$em->flush();
And for querying you can use arrays to both DQL and EntityRepositories:
.. code-block:: php
find("VehicleCatalogue\Model\Car", ["name" => "Audi A8", "year" => 2010]);
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.name = ?1 AND c.year = ?2";
$audi = $em->createQuery($dql)
->setParameter(1, "Audi A8")
->setParameter(2, 2010)
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
and to ``year`` to the related entities.
.. note::
This example shows how you can nicely solve the requirement for existing
values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor.
Identity through foreign Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are tons of use-cases where the identity of an Entity should be determined by the entity
of one or many parent entities.
- Dynamic Attributes of an Entity (for example Article). Each Article has many
attributes with primary key "article_id" and "attribute_name".
- Address object of a Person, the primary key of the address is "user_id". This is not a case of a composite primary
key, but the identity is derived through a foreign entity and a foreign key.
- Join Tables with metadata can be modelled as Entity, for example connections between two articles
with a little description and a score.
The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``#[Id]`` attribute onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
Use-Case 1: Dynamic Attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We keep up the example of an Article with arbitrary attributes, the mapping looks like this:
.. configuration-block::
.. code-block:: attribute
*/
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, string $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column]
private string $attribute;
#[Column]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
Use-Case 2: Simple Derived Identity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you have the requirement that two objects are related by a One-To-One association
and that the dependent class should re-use the primary key of the class it depends on.
One good example for this is a user-address relationship:
.. configuration-block::
.. code-block:: attribute
*/
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
#[Column]
private bool $paid = false;
#[Column]
private bool $shipped = false;
#[Column]
private DateTime $created;
public function __construct(
#[ManyToOne(targetEntity: Customer::class)]
private Customer $customer
) {
$this->items = new ArrayCollection();
$this->created = new DateTime("now");
}
}
#[Entity]
class Product
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column]
private string $name;
#[Column]
private int $currentPrice;
public function getCurrentPrice(): int
{
return $this->currentPrice;
}
}
#[Entity]
class OrderItem
{
#[Id, ManyToOne(targetEntity: Order::class)]
private Order|null $order = null;
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
#[Column]
private int $amount = 1;
#[Column]
private int $offeredPrice;
public function __construct(Order $order, Product $product, int $amount = 1)
{
$this->order = $order;
$this->product = $product;
$this->offeredPrice = $product->getCurrentPrice();
$this->amount = $amount;
}
}
Performance Considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
Using composite keys always comes with a performance hit compared to using entities with
a simple surrogate key. This performance impact is mostly due to additional PHP code that is
necessary to handle this kind of keys, most notably when using derived identifiers.
On the SQL side there is not much overhead as no additional or unexpected queries have to be
executed to manage entities with derived foreign keys.
================================================
FILE: docs/en/tutorials/embeddables.rst
================================================
Separating Concerns using Embeddables
=====================================
Embeddables are classes which are not entities themselves, but are embedded
in entities and can also be queried in DQL. You'll mostly want to use them
to reduce duplication or separating concerns. Value objects such as date range
or address are the primary use case for this feature.
.. note::
Embeddables can only contain properties with basic ``@Column`` mapping.
For the purposes of this tutorial, we will assume that you have a ``User``
class in your application and you would like to store an address in
the ``User`` class. We will model the ``Address`` class as an embeddable
instead of simply adding the respective columns to the ``User`` class.
.. configuration-block::
.. code-block:: attribute
In terms of your database schema, Doctrine will automatically inline all
columns from the ``Address`` class into the table of the ``User`` class,
just as if you had declared them directly there.
Initializing embeddables
------------------------
In case all fields in the embeddable are ``nullable``, you might want
to initialize the embeddable, to avoid getting a null value instead of
the embedded object.
.. code-block:: php
public function __construct()
{
$this->address = new Address();
}
Column Prefixing
----------------
By default, Doctrine names your columns by prefixing them, using the value
object name.
Following the example above, your columns would be named as ``address_street``,
``address_postalCode``...
You can change this behaviour to meet your needs by changing the
``columnPrefix`` attribute in the ``@Embedded`` notation.
The following example shows you how to set your prefix to ``myPrefix_``:
.. configuration-block::
.. code-block:: attribute
To have Doctrine drop the prefix and use the value object's property name
directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
.. configuration-block::
.. code-block:: attribute
DQL
---
You can also use mapped fields of embedded classes in DQL queries, just
as if they were declared in the ``User`` class:
.. code-block:: sql
SELECT u FROM User u WHERE u.address.city = :myCity
================================================
FILE: docs/en/tutorials/extra-lazy-associations.rst
================================================
Extra Lazy Associations
=======================
.. versionadded:: 2.1
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
where posts can be commented, you always have to assume that a post draws hundreds of comments.
In Doctrine ORM if you accessed an association it would always get loaded completely into memory. This
can lead to pretty serious performance problems, if your associations contain several hundreds or thousands
of entities.
Doctrine ORM includes a feature called **Extra Lazy** for associations. Associations
are marked as **Lazy** by default, which means the whole collection object for an association is populated
the first time its accessed. If you mark an association as extra lazy the following methods on collections
can be called without triggering a full load of the collection:
- ``Collection#contains($entity)``
- ``Collection#containsKey($key)``
- ``Collection#count()``
- ``Collection#first()``
- ``Collection#get($key)``
- ``Collection#isEmpty()``
- ``Collection#slice($offset, $length = null)``
For each of the above methods the following semantics apply:
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
Additionally even with Doctrine ORM the following methods do not trigger the collection load:
- ``Collection#add($entity)``
- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does
not work when setting specific keys like ``$coll[0] = $entity``.
With extra lazy collections you can now not only add entities to large collections but also paginate them
easily using a combination of ``count`` and ``slice``.
.. warning::
``removeElement`` directly issued DELETE queries to the database from
version 2.4.0 to 2.7.0. This circumvents the flush operation and might run
outside a transactional boundary if you don't create one yourself. We
consider this a critical bug in the assumption of how the ORM works and
reverted ``removeElement`` EXTRA_LAZY behavior in 2.7.1.
Enabling Extra-Lazy Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The mapping configuration is simple. Instead of using the default value of ``fetch="LAZY"`` you have to
switch to extra lazy as shown in these examples:
.. configuration-block::
.. code-block:: attribute
*/
#[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')]
public Collection $users;
}
.. code-block:: xml
================================================
FILE: docs/en/tutorials/getting-started.rst
================================================
Getting Started with Doctrine
=============================
This guide covers getting started with the Doctrine ORM. After working
through the guide you should know:
- How to install and configure Doctrine by connecting it to a database
- Mapping PHP objects to database tables
- Generating a database schema from PHP objects
- Using the ``EntityManager`` to insert, update, delete and find
objects in the database.
Guide Assumptions
-----------------
This guide is designed for beginners that haven't worked with Doctrine ORM
before. There are some prerequisites for the tutorial that have to be
installed:
- PHP (latest stable version)
- Composer Package Manager (\ `Install Composer
`_)
The code of this tutorial is `available on Github `_.
What is Doctrine?
-----------------
Doctrine ORM is an `object-relational mapper (ORM) `_
for PHP that provides transparent persistence for PHP objects. It uses the Data Mapper
pattern at the heart, aiming for a complete separation of your domain/business
logic from the persistence in a relational database management system.
The benefit of Doctrine for the programmer is the ability to focus
on the object-oriented business logic and worry about persistence only
as a secondary problem. This doesn't mean persistence is downplayed by Doctrine
2, however it is our belief that there are considerable benefits for
object-oriented programming if persistence and entities are kept
separated.
What are Entities?
~~~~~~~~~~~~~~~~~~
Entities are PHP Objects that can be identified over many requests
by a unique identifier or primary key. These classes don't need to extend any
abstract base class or interface.
An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
by Doctrine's data mapping capabilities.
An entity class can be final or read-only when you use
:ref:`native lazy objects `.
It may contain final methods or read-only properties too.
An Example Model: Bug Tracker
-----------------------------
For this Getting Started Guide for Doctrine we will implement the
Bug Tracker domain model from the
`Zend_Db_Table `_
documentation. Reading their documentation we can extract the
requirements:
- A Bug has a description, creation date, status, reporter and
engineer
- A Bug can occur on different Products (platforms)
- A Product has a name.
- Bug reporters and engineers are both Users of the system.
- A User can create new Bugs.
- The assigned engineer can close a Bug.
- A User can see all their reported or assigned Bugs.
- Bugs can be paginated through a list-view.
Project Setup
-------------
Create a new empty folder for this tutorial project, for example
``doctrine2-tutorial`` and create a new file ``composer.json`` inside
that directory with the following contents:
::
{
"require": {
"doctrine/orm": "^3",
"doctrine/dbal": "^4",
"symfony/cache": "^7"
},
"autoload": {
"psr-0": {"": "src/"}
}
}
Install Doctrine using the Composer Dependency Management tool, by calling:
::
$ composer install
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
into the ``vendor`` directory.
Add the following directories::
doctrine2-tutorial
|-- config
| `-- xml
`-- src
.. note::
It is strongly recommended that you require ``doctrine/dbal`` in your
``composer.json`` as well, because using the ORM means mapping objects
and their fields to database tables and their columns, and that
requires mentioning so-called types that are defined in ``doctrine/dbal``
in your application. Having an explicit requirement means you control
when the upgrade to the next major version happens, so that you can
do the necessary changes in your application beforehand.
Obtaining the EntityManager
---------------------------
Doctrine's public interface is through the ``EntityManager``. This class
provides access points to the complete lifecycle management for your entities,
and transforms entities from and back to persistence. You have to
configure and create it to use your entities with Doctrine ORM. I
will show the configuration steps and then discuss them step by
step:
.. code-block:: php
'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite',
], $config);
// obtaining the entity manager
$entityManager = new EntityManager($connection, $config);
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
The second block consists of the instantiation of the ORM
``Configuration`` object using the ``ORMSetup`` helper. It assumes a bunch
of defaults that you don't have to bother about for now. You can
read up on the configuration details in the
:doc:`reference chapter on configuration <../reference/configuration>`.
The third block shows the configuration options required to connect to
a database. In this case, we'll use a file-based SQLite database. All the
configuration options for all the shipped drivers are given in the
`DBAL Configuration section of the manual `_.
The last block shows how the ``EntityManager`` is obtained from a
factory method.
Generating the Database Schema
------------------------------
Doctrine has a command-line interface that allows you to access the SchemaTool,
a component that can generate a relational database schema based entirely on the
defined entity classes and their metadata. For this tool to work, you need to
create an executable console script as described in the
:doc:`tools chapter <../reference/tools>`.
If you created the ``bootstrap.php`` file as described in the previous section,
that script could look like this:
.. code-block:: php
#!/usr/bin/env php
`_)
where methods should represent real business operations and not simple
property change, And business invariants should be maintained both in the
application state (entities in this case) and in the database, with no
space for data corruption.
Here is an example of a simple **anemic entity**:
.. configuration-block::
.. code-block:: php
username;
}
public function setUsername(string $username): void
{
$this->username = $username;
}
public function getPasswordHash(): string
{
return $this->passwordHash;
}
public function setPasswordHash(string $passwordHash): void
{
$this->passwordHash = $passwordHash;
}
public function getBans(): array
{
return $this->bans;
}
public function addBan(Ban $ban): void
{
$this->bans[] = $ban;
}
}
In the example above, we avoid all possible logic in the entity and only care
about putting and retrieving data into it without validation (except the one
provided by type-hints) nor consideration about the object's state.
As Doctrine ORM is a persistence tool for your domain, the state of an object is
really important. This is why we strongly recommend using rich entities.
**Rich entities: Mutators and DTOs**
We recommend using a rich entity design and rely on more complex mutators,
and if needed based on DTOs.
In this design, you should **not** use getters nor setters, and instead,
implement methods that represent the **behavior** of your domain.
For example, when having a ``User`` entity, we could foresee
the following kind of optimization.
Example of a rich entity with proper accessors and mutators:
.. configuration-block::
.. code-block:: php
username;
}
public function authenticate(string $password, callable $checkHash): bool
{
return $checkHash($password, $this->passwordHash) && ! $this->hasActiveBans();
}
public function changePassword(string $password, callable $hash): void
{
$this->passwordHash = $hash($password);
}
public function ban(\DateInterval $duration): void
{
assert($duration->invert !== 1);
$this->bans[] = new Ban($this);
}
}
.. note::
Please note that this example is only a stub. When going further in the
documentation, we will update this object with more behavior and maybe
update some methods.
The entities should only mutate state after checking that all business logic
invariants are being respected.
Additionally, our entities should never see their state change without
validation. For example, creating a ``new Product()`` object without any data
makes it an **invalid object**.
Rich entities should represent **behavior**, not **data**, therefore
they should be valid even after a ``__construct()`` call.
To help creating such objects, we can rely on ``DTOs``, and/or make
our entities always up-to-date. This can be performed with static constructors,
or rich mutators that accept ``DTOs`` as parameters.
The role of the ``DTO`` is to maintain the entity's state and to help us rely
upon objects that correctly represent the data that is used to mutate the
entity.
.. note::
A `DTO `_ is an object
that only carries data without any logic. Its only goal is to be transferred
from one service to another.
A ``DTO`` often represents data sent by a client and that has to be validated,
but can also be used as simple data carrier for other cases.
By using ``DTOs``, if we take our previous ``User`` example, we could create
a ``ProfileEditingForm`` DTO that will be a plain model, totally unrelated to
our database, that will be populated via a form and validated.
Then we can add a new mutator to our ``User``:
.. configuration-block::
.. code-block:: php
The top-level ``entity`` definition specifies information about
the class and table name. The primitive type ``Product#name`` is
defined as a ``field`` attribute. The ``id`` property is defined with
the ``id`` tag. It has a ``generator`` tag nested inside, which
specifies that the primary key generation mechanism should automatically
use the database platform's native id generation strategy (for
example, AUTO INCREMENT in the case of MySql, or Sequences in the
case of PostgreSQL and Oracle).
Now that we have defined our first entity and its metadata,
let's update the database schema:
::
$ php bin/doctrine orm:schema-tool:update --force --dump-sql
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
statements to be executed and then printed to the screen.
Now, we'll create a new script to insert products into the database:
.. code-block:: php
require_once "bootstrap.php";
$newProductName = $argv[1];
$product = new Product();
$product->setName($newProductName);
$entityManager->persist($product);
$entityManager->flush();
echo "Created Product with ID " . $product->getId() . "\n";
Call this script from the command-line to see how new products are created:
::
$ php create_product.php ORM
$ php create_product.php DBAL
What is happening here? Using the ``Product`` class is pretty standard OOP.
The interesting bits are the use of the ``EntityManager`` service. To
notify the EntityManager that a new entity should be inserted into the database,
you have to call ``persist()``. To initiate a transaction to actually *perform*
the insertion, you have to explicitly call ``flush()`` on the ``EntityManager``.
This distinction between persist and flush is what allows the aggregation of
all database writes (INSERT, UPDATE, DELETE) into one single transaction, which
is executed when ``flush()`` is called. Using this approach, the write-performance
is significantly better than in a scenario in which writes are performed on
each entity in isolation.
Next, we'll fetch a list of all the Products in the database. Let's create a
new script for this:
.. code-block:: php
getRepository('Product');
$products = $productRepository->findAll();
foreach ($products as $product) {
echo sprintf("-%s\n", $product->getName());
}
The ``EntityManager#getRepository()`` method can create a finder object (called
a repository) for every type of entity. It is provided by Doctrine and contains
some finder methods like ``findAll()``.
Let's continue by creating a script to display the name of a product based on its ID:
.. code-block:: php
require_once "bootstrap.php";
$id = $argv[1];
$product = $entityManager->find('Product', $id);
if ($product === null) {
echo "No product found.\n";
exit(1);
}
echo sprintf("-%s\n", $product->getName());
Next we'll update a product's name, given its id. This simple example will
help demonstrate Doctrine's implementation of the :ref:`UnitOfWork pattern `. Doctrine
keeps track of all the entities that were retrieved from the Entity Manager,
and can detect when any of those entities' properties have been modified.
As a result, rather than needing to call ``persist($entity)`` for each individual
entity whose properties were changed, a single call to ``flush()`` at the end of a
request is sufficient to update the database for all of the modified entities.
.. code-block:: php
require_once "bootstrap.php";
$id = $argv[1];
$newName = $argv[2];
$product = $entityManager->find('Product', $id);
if ($product === null) {
echo "Product $id does not exist.\n";
exit(1);
}
$product->setName($newName);
$entityManager->flush();
After calling this script on one of the existing products, you can verify the
product name changed by calling the ``show_product.php`` script.
Adding Bug and User Entities
----------------------------
We continue with the bug tracker example by creating the ``Bug`` and ``User``
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
.. code-block:: php
id;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description): void
{
$this->description = $description;
}
public function setCreated(DateTime $created)
{
$this->created = $created;
}
public function getCreated(): DateTime
{
return $this->created;
}
public function setStatus($status): void
{
$this->status = $status;
}
public function getStatus():string
{
return $this->status;
}
}
.. code-block:: php
id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
}
All of the properties we've seen so far are of simple types (integer, string,
and datetime). But now, we'll add properties that will store objects of
specific *entity types* in order to model the relationships between different
entities.
At the database level, relationships between entities are represented by foreign
keys. But with Doctrine, you'll never have to (and never should) work with
the foreign keys directly. You should only work with objects that represent
foreign keys through their own identities.
For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have
OneToMany associations. Obviously you can have ManyToMany associations
that connect two tables with each other through a join table with
two foreign keys.
Now that you know the basics about references in Doctrine, we can extend the
domain model to match the requirements:
.. code-block:: php
*/
private Collection $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
}
.. code-block:: php
*/
private Collection $reportedBugs;
/** @var Collection */
private Collection $assignedBugs;
public function __construct()
{
$this->reportedBugs = new ArrayCollection();
$this->assignedBugs = new ArrayCollection();
}
}
.. note::
Whenever an entity is created from the database, a ``Collection``
implementation of the type ``PersistentCollection`` will be injected into
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
understand the changes that have happened to the collection that are
noteworthy for persistence.
Because we only work with collections for the references we must be
careful to implement a bidirectional reference in the domain model.
The concept of owning or inverse side of a relation is central to
this notion and should always be kept in mind. The following
assumptions are made about relations and have to be followed to be
able to work with Doctrine ORM. These assumptions are not unique to
Doctrine ORM but are best practices in handling database relations
and Object-Relational Mapping.
- In a one-to-one relation, the entity holding the foreign key of
the related entity on its own database table is *always* the owning
side of the relation.
- In a many-to-one relation, the Many-side is the owning side by
default because it holds the foreign key. Accordingly, the One-side
is the inverse side by default.
- In a many-to-one relation, the One-side can only be the owning side if
the relation is implemented as a ManyToMany with a join table, and the
One-side is restricted to allow only UNIQUE values per database constraint.
- In a many-to-many relation, both sides can be the owning side of
the relation. However, in a bi-directional many-to-many relation,
only one side is allowed to be the owning side.
- Changes to Collections are saved or updated, when the entity on
the *owning* side of the collection is saved or updated.
- Saving an Entity at the inverse side of a relation never
triggers a persist operation to changes to the collection.
.. note::
Consistency of bi-directional references on the inverse side of a
relation have to be managed in userland application code. Doctrine
cannot magically update your collections to be consistent.
In the case of Users and Bugs we have references back and forth to
the assigned and reported bugs from a user, making this relation
bi-directional. We have to change the code to ensure consistency of
the bi-directional reference:
.. code-block:: php
assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter(User $reporter): void
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
}
public function getEngineer(): User
{
return $this->engineer;
}
public function getReporter(): User
{
return $this->reporter;
}
}
.. code-block:: php
*/
private Collection $reportedBugs;
/** @var Collection */
private Collection $assignedBugs;
public function addReportedBug(Bug $bug): void
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug(Bug $bug): void
{
$this->assignedBugs[] = $bug;
}
}
I chose to name the inverse methods in past-tense, which should
indicate that the actual assigning has already taken place and the
methods are only used for ensuring consistency of the references.
This approach is my personal preference, you can choose whatever
method to make this work.
You can see from ``User#addReportedBug()`` and
``User#assignedToBug()`` that using this method in userland alone
would not add the Bug to the collection of the owning side in
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
calling Doctrine for persistence would not update the Collections'
representation in the database.
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
correctly saves the relation information.
The ``Bug#reporter`` and ``Bug#engineer`` properties are
Many-To-One relations, which point to a User. In a normalized
relational model, the foreign key is saved on the Bug's table, hence
in our object-relation model the Bug is at the owning side of the
relation. You should always make sure that the use-cases of your
domain model should drive which side is an inverse or owning one in
your Doctrine mapping. In our example, whenever a new bug is saved
or an engineer is assigned to the bug, we don't want to update the
User to persist the reference, but the Bug. This is the case with
the Bug being at the owning side of the relation.
Bugs reference Products by a uni-directional ManyToMany relation in
the database that points from Bugs to Products.
.. code-block:: php
*/
private Collection $products;
public function assignToProduct(Product $product): void
{
$this->products[] = $product;
}
/** @return Collection */
public function getProducts(): Collection
{
return $this->products;
}
}
We are now finished with the domain model given the requirements.
Lets add metadata mappings for the ``Bug`` entity, as we did for
the ``Product`` before:
.. configuration-block::
.. code-block:: attribute
*/
#[ORM\ManyToMany(targetEntity: Product::class)]
private Collection $products;
// ... (other code)
}
.. code-block:: xml
Here we have the entity, id and primitive type definitions.
For the "created" field we have used the ``datetime`` type,
which translates the YYYY-mm-dd HH:mm:ss database format
into a PHP DateTime instance and back.
After the field definitions, the two qualified references to the
user entity are defined. They are created by the ``ManyToOne``
attribute. The class name of the related entity has to be specified with
the ``targetEntity`` parameter, which is enough information for
the database mapper to access the foreign table. Since
``reporter`` and ``engineer`` are on the owning side of a
bi-directional relation, we also have to specify the ``inversedBy``
parameter. They have to point to the field names on the inverse
side of the relationship. We will see in the next example that the ``inversedBy``
parameter has a counterpart ``mappedBy`` which makes that
the inverse side.
The last definition is for the ``Bug#products`` collection. It
holds all products where the specific bug occurs. Again
you have to define the ``targetEntity`` and ``field`` parameters
on the ``ManyToMany`` attribute.
Finally, we'll add metadata mappings for the ``User`` entity.
.. configuration-block::
.. code-block:: attribute
An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'reporter')]
private Collection $reportedBugs;
/** @var Collection An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'engineer')]
private $assignedBugs;
// .. (other code)
}
.. code-block:: xml
Here are some new things to mention about the ``one-to-many`` tags.
Remember that we discussed about the inverse and owning side. Now
both reportedBugs and assignedBugs are inverse relations, which
means the join details have already been defined on the owning
side. Therefore we only have to specify the property on the Bug
class that holds the owning sides.
Update your database schema by running::
$ php bin/doctrine orm:schema-tool:update --force
Implementing more Requirements
------------------------------
So far, we've seen the most basic features of the metadata definition language.
To explore additional functionality, let's first create new ``User`` entities:
.. code-block:: php
setName($newUsername);
$entityManager->persist($user);
$entityManager->flush();
echo "Created User with ID " . $user->getId() . "\n";
Now call:
::
$ php create_user.php beberlei
We now have the necessary data to create a new Bug entity:
.. code-block:: php
require_once "bootstrap.php";
$reporterId = $argv[1];
$engineerId = $argv[2];
$productIds = explode(",", $argv[3]);
$reporter = $entityManager->find("User", $reporterId);
$engineer = $entityManager->find("User", $engineerId);
if (!$reporter || !$engineer) {
echo "No reporter and/or engineer found for the given id(s).\n";
exit(1);
}
$bug = new Bug();
$bug->setDescription("Something does not work!");
$bug->setCreated(new DateTime("now"));
$bug->setStatus("OPEN");
foreach ($productIds as $productId) {
$product = $entityManager->find("Product", $productId);
$bug->assignToProduct($product);
}
$bug->setReporter($reporter);
$bug->setEngineer($engineer);
$entityManager->persist($bug);
$entityManager->flush();
echo "Your new Bug Id: ".$bug->getId()."\n";
Since we only have one user and product, probably with the ID of 1, we can
call this script as follows:
::
php create_bug.php 1 1 1
See how simple it is to relate a Bug, Reporter, Engineer and Products?
Also recall that thanks to the :ref:`UnitOfWork pattern `, Doctrine will detect
these relations and update all of the modified entities in the database
automatically when ``flush()`` is called.
Queries for Application Use-Cases
---------------------------------
List of Bugs
~~~~~~~~~~~~
Using the previous examples we can fill up the database quite a
bit. However, we now need to discuss how to query the underlying
mapper for the required view representations. When opening the
application, bugs can be paginated through a list-view, which is
the first read-only use-case:
.. code-block:: php
createQuery($dql);
$query->setMaxResults(30);
$bugs = $query->getResult();
foreach ($bugs as $bug) {
echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
echo " Reported by: ".$bug->getReporter()->getName()."\n";
echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
foreach ($bug->getProducts() as $product) {
echo " Platform: ".$product->getName()."\n";
}
echo "\n";
}
The DQL Query in this example fetches the 30 most recent bugs with
their respective engineer and reporter in one single SQL statement.
The console output of this script is then:
::
Something does not work! - 02.04.2010
Reported by: beberlei
Assigned to: beberlei
Platform: My Product
.. note::
**DQL is not SQL**
You may wonder why we start writing SQL at the beginning of this
use-case. Don't we use an ORM to get rid of all the endless
hand-writing of SQL? Doctrine introduces DQL which is best
described as **object-query-language** and is a dialect of
`OQL `_ and
similar to `HQL `_ or
`JPQL `_.
It does not know the concept of columns and tables, but only those
of Entity-Class and property. Using the Metadata we defined before
it allows for very short distinctive and powerful queries.
An important reason why DQL is favourable to the Query API of most
ORMs is its similarity to SQL. The DQL language allows query
constructs that most ORMs don't: GROUP BY even with HAVING,
Sub-selects, Fetch-Joins of nested classes, mixed results with
entities and scalar data such as COUNT() results and much more.
Using DQL you should seldom come to the point where you want to
throw your ORM into the dumpster, because it doesn't support some
the more powerful SQL concepts.
If you need to build your query dynamically, you can use the ``QueryBuilder`` retrieved
by calling ``$entityManager->createQueryBuilder()``. There are more
details about this in the relevant part of the documentation.
As a last resort you can still use Native SQL and a description of the
result set to retrieve entities from the database. DQL boils down to a
Native SQL statement and a ``ResultSetMapping`` instance itself. Using
Native SQL you could even use stored procedures for data retrieval, or
make use of advanced non-portable database queries like PostgreSQL's
recursive queries.
Array Hydration of the Bug List
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the previous use-case we retrieved the results as their
respective object instances. We are not limited to retrieving
objects only from Doctrine however. For a simple list view like the
previous one we only need read access to our entities and can
switch the hydration from objects to simple PHP arrays instead.
Hydration can be an expensive process so only retrieving what you need can
yield considerable performance benefits for read-only requests.
Implementing the same list view as before using array hydration we
can rewrite our code:
.. code-block:: php
createQuery($dql);
$bugs = $query->getArrayResult();
foreach ($bugs as $bug) {
echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n";
echo " Reported by: ".$bug['reporter']['name']."\n";
echo " Assigned to: ".$bug['engineer']['name']."\n";
foreach ($bug['products'] as $product) {
echo " Platform: ".$product['name']."\n";
}
echo "\n";
}
There is one significant difference in the DQL query however, we
have to add an additional fetch-join for the products connected to
a bug. The resulting SQL query for this single select statement is
pretty large, however still more efficient to retrieve compared to
hydrating objects.
Find by Primary Key
~~~~~~~~~~~~~~~~~~~
The next Use-Case is displaying a Bug by primary key. This could be
done using DQL as in the previous example with a where clause,
however there is a convenience method on the ``EntityManager`` that
handles loading by primary key, which we have already seen in the
write scenarios:
.. code-block:: php
require_once "bootstrap.php";
$theBugId = $argv[1];
$bug = $entityManager->find("Bug", (int)$theBugId);
echo "Bug: ".$bug->getDescription()."\n";
echo "Engineer: ".$bug->getEngineer()->getName()."\n";
The output of the engineer’s name is fetched from the database! What is happening?
Since we only retrieved the bug by primary key both the engineer and reporter
are not immediately loaded from the database but are replaced by LazyLoading
proxies. These proxies will load behind the scenes, when attempting to access
any of their un-initialized state.
The call prints:
::
$ php show_bug.php 1
Bug: Something does not work!
Engineer: beberlei
.. warning::
Lazy loading additional data can be very convenient but the additional
queries create an overhead. If you know that certain fields will always
(or usually) be required by the query then you will get better performance
by explicitly retrieving them all in the first query.
Dashboard of the User
---------------------
For the next use-case we want to retrieve the dashboard view, a
list of all open bugs the user reported or was assigned to. This
will be achieved using DQL again, this time with some WHERE clauses
and usage of bound parameters:
.. code-block:: php
require_once "bootstrap.php";
$theUserId = $argv[1];
$dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
"WHERE b.status = 'OPEN' AND (e.id = ?1 OR r.id = ?1) ORDER BY b.created DESC";
$myBugs = $entityManager->createQuery($dql)
->setParameter(1, $theUserId)
->setMaxResults(15)
->getResult();
echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\n";
foreach ($myBugs as $bug) {
echo $bug->getId() . " - " . $bug->getDescription()."\n";
}
Number of Bugs
--------------
Until now we only retrieved entities or their array representation.
Doctrine also supports the retrieval of non-entities through DQL.
These values are called "scalar result values" and may even be
aggregate values using COUNT, SUM, MIN, MAX or AVG functions.
We will need this knowledge to retrieve the number of open bugs
grouped by product:
.. code-block:: php
createQuery($dql)->getScalarResult();
foreach ($productBugs as $productBug) {
echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n";
}
Updating Entities
-----------------
There is a single use-case missing from the requirements, Engineers
should be able to close a bug. This looks like:
.. code-block:: php
status = "CLOSE";
}
}
.. code-block:: php
require_once "bootstrap.php";
$theBugId = $argv[1];
$bug = $entityManager->find("Bug", (int)$theBugId);
$bug->close();
$entityManager->flush();
When retrieving the Bug from the database it is inserted into the
IdentityMap inside the UnitOfWork of Doctrine. This means your Bug
with exactly this id can only exist once during the whole request
no matter how often you call ``EntityManager#find()``. It even
detects entities that are hydrated using DQL and are already
present in the Identity Map.
When flush is called the EntityManager loops over all the entities
in the identity map and performs a comparison between the values
originally retrieved from the database and those values the entity
currently has. If at least one of these properties is different the
entity is scheduled for an UPDATE against the database. Only the
changed columns are updated, which offers a pretty good performance
improvement compared to updating all the properties.
Entity Repositories
-------------------
For now we have not discussed how to separate the Doctrine query logic from your model.
In Doctrine 1 there was the concept of ``Doctrine_Table`` instances for this
separation. The similar concept in Doctrine2 is called Entity Repositories, integrating
the `repository pattern `_ at the heart of Doctrine.
Every Entity uses a default repository by default and offers a bunch of convenience
methods that you can use to query for instances of that Entity. Take for example
our Product entity. If we wanted to Query by name, we can use:
.. code-block:: php
getRepository('Product')
->findOneBy(array('name' => $productName));
The method ``findOneBy()`` takes an array of fields or association keys and the values to match against.
If you want to find all entities matching a condition you can use ``findBy()``, for
example querying for all closed bugs:
.. code-block:: php
getRepository('Bug')
->findBy(array('status' => 'CLOSED'));
foreach ($bugs as $bug) {
// do stuff
}
Compared to DQL these query methods are falling short of functionality very fast.
Doctrine offers you a convenient way to extend the functionalities of the default ``EntityRepository``
and put all the specialized DQL query logic on it. For this you have to create a subclass
of ``Doctrine\ORM\EntityRepository``, in our case a ``BugRepository`` and group all
the previously discussed query functionality in it:
.. code-block:: php
getEntityManager()->createQuery($dql);
$query->setMaxResults($number);
return $query->getResult();
}
public function getRecentBugsArray($number = 30)
{
$dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
"JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
$query = $this->getEntityManager()->createQuery($dql);
$query->setMaxResults($number);
return $query->getArrayResult();
}
public function getUsersBugs($userId, $number = 15)
{
$dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
"WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC";
return $this->getEntityManager()->createQuery($dql)
->setParameter(1, $userId)
->setMaxResults($number)
->getResult();
}
public function getOpenBugsByProduct()
{
$dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
"JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
return $this->getEntityManager()->createQuery($dql)->getScalarResult();
}
}
To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')``
we have to adjust the metadata slightly.
.. configuration-block::
.. code-block:: attribute
Now we can remove our query logic in all the places and instead use them through the EntityRepository.
As an example here is the code of the first use case "List of Bugs":
.. code-block:: php
getRepository('Bug')->getRecentBugs();
foreach ($bugs as $bug) {
echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
echo " Reported by: ".$bug->getReporter()->getName()."\n";
echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
foreach ($bug->getProducts() as $product) {
echo " Platform: ".$product->getName()."\n";
}
echo "\n";
}
Using EntityRepositories you can avoid coupling your model with specific query logic.
You can also re-use query logic easily throughout your application.
The method ``count()`` takes an array of fields or association keys and the values to match against.
This provides you with a convenient and lightweight way to count a resultset when you don't need to
deal with it:
.. code-block:: php
getRepository(Product::class)
->count(['name' => $productName]);
Conclusion
----------
This tutorial is over here, I hope you had fun. Additional content
will be added to this tutorial incrementally, topics will include:
- More on Association Mappings
- Lifecycle Events triggered in the UnitOfWork
- Ordering of Collections
Additional details on all the topics discussed here can be found in
the respective manual chapters.
================================================
FILE: docs/en/tutorials/ordered-associations.rst
================================================
Ordering To-Many Associations
-----------------------------
There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``#[OrderBy]`` attribute with a collection that specifies
a DQL snippet that is appended to all queries with this
collection.
Additional to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute you
can specify the ``#[OrderBy]`` in the following way:
.. configuration-block::
.. code-block:: attribute
"ASC"])]
private Collection $groups;
}
.. code-block:: xml
The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``#[ManyToMany]`` or ``#[OneToMany]`` attribute.
The semantics of this feature can be described as follows:
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
fields, that is appended to all the explicitly given ORDER BY
items.
- All collections of the ordered type are always retrieved in an
ordered fashion.
- To keep the database impact low, these implicit ORDER BY items
are only added to a DQL Query if the collection is fetch joined in
the DQL query.
Given our previously defined example, the following would not add
ORDER BY, since g is not fetch joined:
.. code-block:: sql
SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10
However the following:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10
...would internally be rewritten to:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC
You can reverse the order with an explicit DQL ORDER BY:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC
...is internally rewritten to:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC
================================================
FILE: docs/en/tutorials/override-field-association-mappings-in-subclasses.rst
================================================
Override Field Association Mappings In Subclasses
-------------------------------------------------
Sometimes there is a need to persist entities but override all or part of the
mapping metadata. Sometimes also the mapping to override comes from entities
using traits where the traits have mapping metadata.
This tutorial explains how to override mapping metadata,
i.e. attributes and associations metadata in particular. The example here shows
the overriding of a class that uses a trait but is similar when extending a base
class as shown at the end of this tutorial.
Suppose we have a class ``ExampleEntityWithOverride``. This class uses trait ``ExampleTrait``:
.. code-block:: php
`).
================================================
FILE: docs/en/tutorials/pagination.rst
================================================
Pagination
==========
Doctrine ORM ships with a Paginator for DQL queries. It
has a very simple API and implements the SPL interfaces ``Countable`` and
``IteratorAggregate``.
.. code-block:: php
createQuery($dql)
->setFirstResult(0)
->setMaxResults(100);
$paginator = new Paginator($query, fetchJoinCollection: true);
$c = count($paginator);
foreach ($paginator as $post) {
echo $post->getHeadline() . "\n";
}
Paginating Doctrine queries is not as simple as you might think in the
beginning. If you have complex fetch-join scenarios with one-to-many or
many-to-many associations using the "default" LIMIT functionality of database
vendors is not sufficient to get the correct results.
By default the pagination extension does the following steps to compute the
correct result:
- Perform a Count query using `DISTINCT` keyword.
- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page.
- Perform a WHERE IN query to get all results for the current page.
This behavior is only necessary if you actually fetch join a to-many
collection. You can disable this behavior by setting the
``fetchJoinCollection`` argument to ``false``; in that case only 2 instead of the 3 queries
described are executed. We hope to automate the detection for this in
the future.
.. note::
``fetchJoinCollection`` argument set to ``true`` might affect results if you use aggregations in your query.
By using the ``Paginator::HINT_ENABLE_DISTINCT`` you can instruct doctrine that the query to be executed
will not produce "duplicate" rows (only to-one relations are joined), thus the SQL limit will work as expected.
In this way the `DISTINCT` keyword will be omitted and can bring important performance improvements.
.. code-block:: php
createQuery($dql)
->setHint(Paginator::HINT_ENABLE_DISTINCT, false)
->setFirstResult(0)
->setMaxResults(100);
================================================
FILE: docs/en/tutorials/working-with-indexed-associations/Market.php
================================================
*/
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
private Collection $stocks;
public function __construct(string $name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock(string $symbol): Stock
{
if (! isset($this->stocks[$symbol])) {
throw new InvalidArgumentException('Symbol is not traded on this market.');
}
return $this->stocks[$symbol];
}
/** @return array */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}
================================================
FILE: docs/en/tutorials/working-with-indexed-associations/market.xml
================================================
================================================
FILE: docs/en/tutorials/working-with-indexed-associations.rst
================================================
Working with Indexed Associations
=================================
Doctrine ORM collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY``
was used. You can index your collections by a value in the related entity.
This is a first step towards full ordered hashmap support through the Doctrine ORM.
The feature works like an implicit ``INDEX BY`` for the selected association but has several
downsides also:
- You have to manage both the key and field if you want to change the index by field value.
- On each request the keys are regenerated from the field value, and not from the previous collection key.
- Values of the Index-By keys are never considered during persistence. They only exist for accessing purposes.
- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities
with the same index-by field value is undefined.
As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock``
and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical
list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets.
Mapping Indexed Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can map indexed associations by adding:
* ``indexBy`` argument to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute.
* ``index-by`` attribute to any ```` or ```` xml element.
The code and mappings for the Market entity looks like this:
.. configuration-block::
.. literalinclude:: working-with-indexed-associations/Market.php
:language: attribute
.. literalinclude:: working-with-indexed-associations/market.xml
:language: xml
Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol,
so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)``
we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown.
The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness
here are the code and mappings for it:
.. configuration-block::
.. code-block:: attribute
symbol = $symbol;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol(): string
{
return $this->symbol;
}
}
.. code-block:: xml
Querying indexed associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now that we defined the stocks collection to be indexed by symbol, we can take a look at some code
that makes use of the indexing.
First we will populate our database with two example stocks traded on a single market:
.. code-block:: php
persist($market);
$em->persist($stock1);
$em->persist($stock2);
$em->flush();
This code is not particular interesting since the indexing feature is not yet used. In a new request we could
now query for the market:
.. code-block:: php
find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
// Access the stocks by symbol now:
$stock = $market->getStock($symbol);
echo $stock->getSymbol(); // will print "AAPL"
The implementation of ``Market::addStock()``, in combination with ``indexBy``, allows us to access the collection
consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not.
The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association.
.. code-block:: php
createQuery($dql)
->setParameter(1, $marketId)
->getSingleResult();
// Access the stocks by symbol now:
$stock = $market->getStock($symbol);
echo $stock->getSymbol(); // will print "AAPL"
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally,
indexed associations also work with the ``Collection::slice()`` functionality, even if the association's fetch mode is
LAZY or EXTRA_LAZY.
Outlook into the Future
~~~~~~~~~~~~~~~~~~~~~~~
For the inverse side of a many-to-many associations there will be a way to persist the keys and the order
as a third and fourth parameter into the join table. This feature is discussed in `#2817 `_
This feature cannot be implemented for one-to-many associations, because they are never the owning side.
================================================
FILE: doctrine-mapping.xsd
================================================
================================================
FILE: phpbench.json
================================================
{
"runner.bootstrap": "tests/Tests/TestInit.php",
"runner.path": "tests/Performance",
"runner.file_pattern": "*Bench.php",
"core.extensions": [
"PhpBench\\Extensions\\XDebug\\XDebugExtension"
]
}
================================================
FILE: phpcs.xml.dist
================================================
srctests*/tests/Tests/Proxies/__CG__**/tests/Tests/ORM/Tools/Export/export/*tests/Tests/Mocks/HydratorMockStatement.phptests/Tests/Models/Cache/ComplexAction.phptests/Tests/Models/DDC117/DDC117ArticleDetails.phptests/Tests/Models/DDC117/DDC117Translation.phptests/Tests/ORM/Functional/Ticket/DDC2579Test.phptests/Tests/ORM/Functional/ValueObjectsTest.phptests/*tests/*src/Mapping/Driver/LoadMappingFileImplementation.phpsrc/Mapping/GetReflectionClassImplementation.phptests/*src/Tools/Console/Helper/EntityManagerHelper.phptests/*src/Tools/Debug.phptests/Tests/ORM/Tools/DebugTest.phpsrc/Events.phpsrc/Tools/ToolEvents.phpsrc/Internal/Hydration/AbstractHydrator.phpsrc/Query/Parser.phpsrc/Mapping/AssociationOverride.phpsrc/Mapping/AssociationOverrides.phpsrc/Mapping/AttributeOverride.phpsrc/Mapping/AttributeOverrides.phpsrc/Mapping/Cache.phpsrc/Mapping/ChangeTrackingPolicy.phpsrc/Mapping/Column.phpsrc/Mapping/CustomIdGenerator.phpsrc/Mapping/DiscriminatorColumn.phpsrc/Mapping/DiscriminatorMap.phpsrc/Mapping/Embeddable.phpsrc/Mapping/Embedded.phpsrc/Mapping/Entity.phpsrc/Mapping/EntityListeners.phpsrc/Mapping/GeneratedValue.phpsrc/Mapping/HasLifecycleCallbacks.phpsrc/Mapping/Id.phpsrc/Mapping/Index.phpsrc/Mapping/InheritanceType.phpsrc/Mapping/JoinColumn.phpsrc/Mapping/JoinColumns.phpsrc/Mapping/JoinTable.phpsrc/Mapping/ManyToMany.phpsrc/Mapping/ManyToOne.phpsrc/Mapping/MappedSuperclass.phpsrc/Mapping/OneToMany.phpsrc/Mapping/OneToOne.phpsrc/Mapping/OrderBy.phpsrc/Mapping/PostLoad.phpsrc/Mapping/PostPersist.phpsrc/Mapping/PostRemove.phpsrc/Mapping/PostUpdate.phpsrc/Mapping/PreFlush.phpsrc/Mapping/PrePersist.phpsrc/Mapping/PreRemove.phpsrc/Mapping/PreUpdate.phpsrc/Mapping/SequenceGenerator.phpsrc/Mapping/Table.phpsrc/Mapping/UniqueConstraint.phpsrc/Mapping/Version.phpsrc/Cache/DefaultQueryCache.phpsrc/EntityManagerInterface.phptests/Tests/Models/DDC1872/DDC1872ExampleTrait.php*/tests/**/tests/**/tests/**/tests/*tests/Tests/Models/Global/GlobalNamespaceModel.phptests/Tests/Models/DDC3231/DDC3231User1NoNamespace.phptests/Tests/Models/DDC3231/DDC3231User2NoNamespace.phptests/Tests/ORM/Functional/Ticket/DDC2084Test.phptests/Tests/ORM/Functional/Ticket/DDC2084Test.phptests/Tests/ORM/Functional/Ticket/DDC1301Test.phptests/Tests/ORM/Functional/ExtraLazyCollectionTest.phptests/Tests/Models/DDC1590/DDC1590User.phptests/Tests/Proxy/DefaultProxyClassNameResolverTest.phptests/Tests/Mocks/DatabasePlatformMock.phptests/Tests/Mocks/DriverMock.phptests/Tests/ORM/UnitOfWorkTest.phptests/Tests/ORM/Query/DeleteSqlGenerationTest.phpsrc/Query/AST/Node.phptests/Tests/ORM/Mapping/php/Doctrine.Tests*src/AbstractQuery.phpsrc/Mapping/ClassMetadata.phpsrc/NativeQuery.phpsrc/Query.phpsrc/Query/TreeWalkerAdapter.phpsrc/Tools/Export/Driver/AbstractExporter.phpsrc/Tools/Export/Driver/PhpExporter.phptests/Tests/Mocks/DatabasePlatformMock.phptests/Tests/Mocks/SchemaManagerMock.phptests/Tests/ORM/AbstractQueryTest.phptests/Tests/ORM/Functional/Ticket/DDC3634Test.phptests/Tests/OrmFunctionalTestCase.phpsrc/Proxy/ProxyFactory.phptests/Tests/ORM/Functional/ProxiesLikeEntitiesTest.phptests/Tests/ORM/Functional/Ticket/DDC1885Test.phptests/Tests/ORM/Functional/Ticket/DDC1843Test.phptests/Tests/ORM/Mapping/ClassMetadataFactoryTest.phpsrc/Id/TableGenerator.phpsrc/Id/TableGenerator.phpsrc/QueryBuilder.php
================================================
FILE: phpstan-baseline.neon
================================================
parameters:
ignoreErrors:
-
message: '#^Method Doctrine\\ORM\\AbstractQuery\:\:getParameter\(\) should return Doctrine\\ORM\\Query\\Parameter\|null but returns Doctrine\\ORM\\Query\\Parameter\|false\|null\.$#'
identifier: return.type
count: 1
path: src/AbstractQuery.php
-
message: '#^Parameter \#1 \$stmt of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:toIterable\(\) expects Doctrine\\DBAL\\Result, Doctrine\\DBAL\\Result\|int given\.$#'
identifier: argument.type
count: 1
path: src/AbstractQuery.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CacheFactory\:\:buildCachedEntityPersister\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/CacheFactory.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CacheFactory\:\:buildEntityHydrator\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/CacheFactory.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:buildCacheEntry\(\) has parameter \$collection with generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:buildCacheEntry\(\) has parameter \$collection with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:buildCacheEntry\(\) has parameter \$collection with no value type specified in iterable type array\|\(Doctrine\\Common\\Collections\\Collection&iterable\)\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:buildCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:loadCacheEntry\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:loadCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCache\:\:buildCollectionCacheKey\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCache.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCache\:\:buildEntityCacheKey\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCache.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCache\:\:toIdentifierArray\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCache.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCacheFactory\:\:buildCachedEntityPersister\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCacheFactory.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCacheFactory\:\:buildEntityHydrator\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCacheFactory.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCollectionHydrator\:\:buildCacheEntry\(\) has parameter \$collection with generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCollectionHydrator\:\:buildCacheEntry\(\) has parameter \$collection with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/DefaultCollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCollectionHydrator\:\:buildCacheEntry\(\) has parameter \$collection with no value type specified in iterable type array\|\(Doctrine\\Common\\Collections\\Collection&iterable\)\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/DefaultCollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCollectionHydrator\:\:buildCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCollectionHydrator\:\:loadCacheEntry\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCollectionHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCollectionHydrator\:\:loadCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultCollectionHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumnFieldNames\.$#'
identifier: property.notFound
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:getCacheRegion\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultEntityHydrator\:\:buildCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultEntityHydrator\:\:loadCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Parameter \#2 \$data of class Doctrine\\ORM\\Cache\\EntityCacheEntry constructor expects array\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Unable to resolve the template type T in call to method Doctrine\\ORM\\EntityManagerInterface\:\:getReference\(\)$#'
identifier: argument.templateType
count: 1
path: src/Cache/DefaultEntityHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Cache\\CacheEntry\:\:\$class\.$#'
identifier: property.notFound
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Cache\\CacheEntry\:\:resolveAssociationEntries\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:getCacheRegion\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Cache/DefaultQueryCache.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:storeEntityCache\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultQueryCache\:\:storeAssociationCache\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#1 \$className of method Doctrine\\ORM\\EntityManagerInterface\:\:getClassMetadata\(\) expects string, class\-string\|false given\.$#'
identifier: argument.type
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#1 \$entityName of method Doctrine\\ORM\\UnitOfWork\:\:getEntityPersister\(\) expects class\-string, class\-string\|false given\.$#'
identifier: argument.type
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#1 \$entityName of static method Doctrine\\ORM\\Cache\\Exception\\NonCacheableEntity\:\:fromEntity\(\) expects string, class\-string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#2 \$key of method Doctrine\\ORM\\Cache\\Logging\\CacheLogger\:\:entityCacheHit\(\) expects Doctrine\\ORM\\Cache\\EntityCacheKey, Doctrine\\ORM\\Cache\\CacheKey given\.$#'
identifier: argument.type
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#2 \$key of method Doctrine\\ORM\\Cache\\Logging\\CacheLogger\:\:entityCacheMiss\(\) expects Doctrine\\ORM\\Cache\\EntityCacheKey, Doctrine\\ORM\\Cache\\CacheKey given\.$#'
identifier: argument.type
count: 2
path: src/Cache/DefaultQueryCache.php
-
message: '#^Method Doctrine\\ORM\\Cache\\EntityHydrator\:\:buildCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/EntityHydrator.php
-
message: '#^Method Doctrine\\ORM\\Cache\\EntityHydrator\:\:loadCacheEntry\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/EntityHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Cache\\CacheEntry\:\:\$identifiers\.$#'
identifier: property.notFound
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:contains\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:containsKey\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:count\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:get\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:getSourceEntityMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:getTargetEntityMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:loadCollectionCache\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:loadCriteria\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:slice\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:storeCollectionCache\(\) has parameter \$elements with generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:storeCollectionCache\(\) has parameter \$elements with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:storeCollectionCache\(\) has parameter \$elements with no value type specified in iterable type array\|\(Doctrine\\Common\\Collections\\Collection&iterable\)\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Parameter \#2 \$key of method Doctrine\\ORM\\Cache\\EntityHydrator\:\:buildCacheEntry\(\) expects Doctrine\\ORM\\Cache\\EntityCacheKey, Doctrine\\ORM\\Cache\\CacheKey given\.$#'
identifier: argument.type
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Parameter \#3 \$entry of method Doctrine\\ORM\\Cache\\CollectionHydrator\:\:loadCacheEntry\(\) expects Doctrine\\ORM\\Cache\\CollectionCacheEntry, Doctrine\\ORM\\Cache\\CacheEntry given\.$#'
identifier: argument.type
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Property Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:\$sourceEntity with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Property Doctrine\\ORM\\Cache\\Persister\\Collection\\AbstractCollectionPersister\:\:\$targetEntity with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/AbstractCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\CachedCollectionPersister\:\:getSourceEntityMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/CachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\CachedCollectionPersister\:\:getTargetEntityMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/CachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\CachedCollectionPersister\:\:loadCollectionCache\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/CachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\CachedCollectionPersister\:\:storeCollectionCache\(\) has parameter \$elements with generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/CachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\CachedCollectionPersister\:\:storeCollectionCache\(\) has parameter \$elements with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/Persister/Collection/CachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\CachedCollectionPersister\:\:storeCollectionCache\(\) has parameter \$elements with no value type specified in iterable type array\|\(Doctrine\\Common\\Collections\\Collection&iterable\)\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Cache/Persister/Collection/CachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister\:\:update\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/NonStrictReadWriteCachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister\:\:update\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/ReadOnlyCachedCollectionPersister.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Cache\\Region\:\:lock\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister\:\:update\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Collection/ReadWriteCachedCollectionPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Cache\\CacheEntry\:\:\$class\.$#'
identifier: property.notFound
count: 2
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Cache\\Persister\\CachedPersister&Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:loadCollectionCache\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Cache\\Persister\\CachedPersister&Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:storeCollectionCache\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:storeEntityCache\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\AbstractEntityPersister\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\AbstractEntityPersister\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\AbstractEntityPersister\:\:loadManyToManyCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\AbstractEntityPersister\:\:loadOneToManyCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Parameter \#3 \$entry of method Doctrine\\ORM\\Cache\\EntityHydrator\:\:loadCacheEntry\(\) expects Doctrine\\ORM\\Cache\\EntityCacheEntry, Doctrine\\ORM\\Cache\\CacheEntry given\.$#'
identifier: argument.type
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Cache\\Region\:\:lock\(\)\.$#'
identifier: method.notFound
count: 2
path: src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php
-
message: '#^If condition is always true\.$#'
identifier: if.alwaysTrue
count: 1
path: src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Cache/Persister/Entity/ReadWriteCachedEntityPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Cache\\CacheEntry\:\:\$time\.$#'
identifier: property.notFound
count: 1
path: src/Cache/TimestampQueryCacheValidator.php
-
message: '#^Call to function is_a\(\) with arguments class\-string\, ''Doctrine\\\\ORM\\\\EntityRepository'' and true will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Configuration.php
-
message: '#^Method Doctrine\\ORM\\Configuration\:\:getDefaultRepositoryClassName\(\) return type with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Configuration.php
-
message: '#^Method Doctrine\\ORM\\Configuration\:\:setDefaultRepositoryClassName\(\) has parameter \$className with generic class Doctrine\\ORM\\EntityRepository but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Configuration.php
-
message: '#^Parameter \#2 \$className of method Doctrine\\ORM\\Configuration\:\:addCustomNumericFunction\(\) expects \(callable\(string\)\: Doctrine\\ORM\\Query\\AST\\Functions\\FunctionNode\)\|class\-string\, class\-string given\.$#'
identifier: argument.type
count: 1
path: src/Configuration.php
-
message: '#^Method Doctrine\\ORM\\Decorator\\EntityManagerDecorator\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Decorator/EntityManagerDecorator.php
-
message: '#^Method Doctrine\\ORM\\Decorator\\EntityManagerDecorator\:\:getRepository\(\) return type with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Decorator/EntityManagerDecorator.php
-
message: '#^Return type \(Doctrine\\ORM\\Mapping\\ClassMetadataFactory\) of method Doctrine\\ORM\\Decorator\\EntityManagerDecorator\:\:getMetadataFactory\(\) should be compatible with return type \(Doctrine\\Persistence\\Mapping\\ClassMetadataFactory\\>\) of method Doctrine\\Persistence\\ObjectManager\:\:getMetadataFactory\(\)$#'
identifier: method.childReturnType
count: 1
path: src/Decorator/EntityManagerDecorator.php
-
message: '#^Return type \(Doctrine\\ORM\\Mapping\\ClassMetadataFactory\) of method Doctrine\\ORM\\Decorator\\EntityManagerDecorator\:\:getMetadataFactory\(\) should be compatible with return type \(Doctrine\\Persistence\\Mapping\\ClassMetadataFactory\\>\) of method Doctrine\\Persistence\\ObjectManagerDecorator\\:\:getMetadataFactory\(\)$#'
identifier: method.childReturnType
count: 1
path: src/Decorator/EntityManagerDecorator.php
-
message: '#^Method Doctrine\\ORM\\EntityManager\:\:checkLockRequirements\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/EntityManager.php
-
message: '#^Method Doctrine\\ORM\\EntityManager\:\:find\(\) should return \(T of object\)\|null but returns object\.$#'
identifier: return.type
count: 1
path: src/EntityManager.php
-
message: '#^Method Doctrine\\ORM\\EntityManager\:\:find\(\) should return \(T of object\)\|null but returns object\|null\.$#'
identifier: return.type
count: 3
path: src/EntityManager.php
-
message: '#^Method Doctrine\\ORM\\EntityManager\:\:getReference\(\) should return \(T of object\)\|null but returns object\.$#'
identifier: return.type
count: 1
path: src/EntityManager.php
-
message: '#^Method Doctrine\\ORM\\EntityManager\:\:getReference\(\) should return \(T of object\)\|null but returns object\|null\.$#'
identifier: return.type
count: 1
path: src/EntityManager.php
-
message: '#^Method Doctrine\\ORM\\EntityManager\:\:isUninitializedObject\(\) has parameter \$value with no type specified\.$#'
identifier: missingType.parameter
count: 1
path: src/EntityManager.php
-
message: '#^Parameter \#1 \$className of method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory\\:\:getMetadataFor\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/EntityManager.php
-
message: '#^Property Doctrine\\ORM\\EntityManager\:\:\$metadataFactory \(Doctrine\\ORM\\Mapping\\ClassMetadataFactory\) does not accept object\.$#'
identifier: assign.propertyType
count: 1
path: src/EntityManager.php
-
message: '#^Return type \(Doctrine\\ORM\\Mapping\\ClassMetadataFactory\) of method Doctrine\\ORM\\EntityManager\:\:getMetadataFactory\(\) should be compatible with return type \(Doctrine\\Persistence\\Mapping\\ClassMetadataFactory\\>\) of method Doctrine\\Persistence\\ObjectManager\:\:getMetadataFactory\(\)$#'
identifier: method.childReturnType
count: 1
path: src/EntityManager.php
-
message: '#^Return type \(Doctrine\\ORM\\Mapping\\ClassMetadataFactory\) of method Doctrine\\ORM\\EntityManagerInterface\:\:getMetadataFactory\(\) should be compatible with return type \(Doctrine\\Persistence\\Mapping\\ClassMetadataFactory\\>\) of method Doctrine\\Persistence\\ObjectManager\:\:getMetadataFactory\(\)$#'
identifier: method.childReturnType
count: 1
path: src/EntityManagerInterface.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:findBy\(\) should return list\ but returns array\\.$#'
identifier: return.type
count: 1
path: src/EntityRepository.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:findOneBy\(\) should return \(T of object\)\|null but returns object\|null\.$#'
identifier: return.type
count: 1
path: src/EntityRepository.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\ but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
identifier: return.type
count: 1
path: src/EntityRepository.php
-
message: '#^Method Doctrine\\ORM\\Event\\ListenersInvoker\:\:getSubscribedSystems\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Event/ListenersInvoker.php
-
message: '#^Method Doctrine\\ORM\\Event\\ListenersInvoker\:\:invoke\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Event/ListenersInvoker.php
-
message: '#^Method Doctrine\\ORM\\Event\\OnClassMetadataNotFoundEventArgs\:\:getFoundMetadata\(\) return type with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Event/OnClassMetadataNotFoundEventArgs.php
-
message: '#^Method Doctrine\\ORM\\Event\\OnClassMetadataNotFoundEventArgs\:\:setFoundMetadata\(\) has parameter \$classMetadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Event/OnClassMetadataNotFoundEventArgs.php
-
message: '#^Property Doctrine\\ORM\\Event\\OnClassMetadataNotFoundEventArgs\:\:\$foundMetadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Event/OnClassMetadataNotFoundEventArgs.php
-
message: '#^Method Doctrine\\ORM\\Event\\PreUpdateEventArgs\:\:__construct\(\) has parameter \$changeSet with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Event/PreUpdateEventArgs.php
-
message: '#^Method Doctrine\\ORM\\Event\\PreUpdateEventArgs\:\:getEntityChangeSet\(\) return type with generic class Doctrine\\ORM\\PersistentCollection does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Event/PreUpdateEventArgs.php
-
message: '#^Property Doctrine\\ORM\\Event\\PreUpdateEventArgs\:\:\$entityChangeSet with generic class Doctrine\\ORM\\PersistentCollection does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Event/PreUpdateEventArgs.php
-
message: '#^Method Doctrine\\ORM\\Id\\AssignedGenerator\:\:generateId\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Id/AssignedGenerator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 2
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 3
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) return type with generic class ReflectionClass does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:getDiscriminatorValues\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:registerManaged\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Parameter &\$id by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\, array\ given\.$#'
identifier: parameterByRef.type
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Parameter &\$nonemptyComponents by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\, array\ given\.$#'
identifier: parameterByRef.type
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\ArrayHydrator\:\:hydrateAllData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Internal/Hydration/ArrayHydrator.php
-
message: '#^Parameter &\$result by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\ArrayHydrator\:\:hydrateRowData\(\) expects array\, array\\|null given\.$#'
identifier: parameterByRef.type
count: 1
path: src/Internal/Hydration/ArrayHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$inversedBy\.$#'
identifier: property.notFound
count: 2
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 2
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 3
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator\:\:hydrateAllData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator\:\:initRelatedCollection\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator\:\:initRelatedCollection\(\) return type with generic class Doctrine\\ORM\\PersistentCollection does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Parameter \#2 \$assoc of method Doctrine\\ORM\\PersistentCollection\<\(int\|string\),mixed\>\:\:setOwner\(\) expects Doctrine\\ORM\\Mapping\\AssociationMapping&Doctrine\\ORM\\Mapping\\ToManyAssociationMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 1
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Property Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator\:\:\$uninitializedCollections with generic class Doctrine\\ORM\\PersistentCollection does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/ObjectHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\ScalarColumnHydrator\:\:hydrateAllData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Internal/Hydration/ScalarColumnHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\ScalarHydrator\:\:hydrateAllData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Internal/Hydration/ScalarHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\Hydration\\SimpleObjectHydrator\:\:hydrateAllData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Internal/Hydration/SimpleObjectHydrator.php
-
message: '#^Property Doctrine\\ORM\\Internal\\Hydration\\SimpleObjectHydrator\:\:\$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/Hydration/SimpleObjectHydrator.php
-
message: '#^Method Doctrine\\ORM\\Internal\\HydrationCompleteHandler\:\:deferPostLoadInvoking\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Internal/HydrationCompleteHandler.php
-
message: '#^Offset int\|null might not exist on array\\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Internal/StronglyConnectedComponents.php
-
message: '#^Property Doctrine\\ORM\\Internal\\StronglyConnectedComponents\:\:\$representingNodes \(array\\) does not accept array\\.$#'
identifier: assign.propertyType
count: 1
path: src/Internal/StronglyConnectedComponents.php
-
message: '#^Property Doctrine\\ORM\\Internal\\StronglyConnectedComponents\:\:\$states \(array\\) does not accept non\-empty\-array\<''''\|int, 1\|2\|3\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Internal/StronglyConnectedComponents.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getColumnAlias\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getIdentifierColumnNames\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getJoinColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getJoinTableName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getReferencedJoinColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getSequenceName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\AnsiQuoteStrategy\:\:getTableName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/AnsiQuoteStrategy.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\JoinTableMapping\:\:fromMappingArray\(\) expects array\{name\: string, quoted\?\: bool\|null, joinColumns\?\: array\, inverseJoinColumns\?\: array\, schema\?\: string\|null, options\?\: array\\}, non\-empty\-array\ given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/AssociationMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\AssociationMapping\:\:\$cache type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/AssociationMapping.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\AssociationOverride and Doctrine\\ORM\\Mapping\\AssociationOverride will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: src/Mapping/AssociationOverrides.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\AttributeOverride and Doctrine\\ORM\\Mapping\\AttributeOverride will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: src/Mapping/AttributeOverrides.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Builder\\ClassMetadataBuilder\:\:__construct\(\) has parameter \$cm with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Builder/ClassMetadataBuilder.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Builder\\ClassMetadataBuilder\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Builder/ClassMetadataBuilder.php
-
message: '#^Parameter \#1 \$repositoryClassName of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:setCustomRepositoryClass\(\) expects class\-string\\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Builder/ClassMetadataBuilder.php
-
message: '#^Parameter \#1 \$mapping of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:mapEmbedded\(\) expects array\{fieldName\: string, class\?\: class\-string, declaredField\?\: string, columnPrefix\?\: string\|false\|null, originalField\?\: string\}, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Builder/EmbeddedBuilder.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Builder\\EntityListenerBuilder\:\:bindEntityListener\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Builder/EntityListenerBuilder.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 4
path: src/Mapping/ClassMetadata.php
-
message: '#^If condition is always true\.$#'
identifier: if.alwaysTrue
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:inlineEmbeddable\(\) has parameter \$embeddable with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:setAssociationOverride\(\) has parameter \$overrideMapping with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 2
path: src/Mapping/ClassMetadata.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:setCustomRepositoryClass\(\) has parameter \$repositoryClassName with generic class Doctrine\\ORM\\EntityRepository but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$array \(list\\) of array_values is already a list, call has no effect\.$#'
identifier: arrayValues.list
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mapping of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:validateAndCompleteTypedAssociationMapping\(\) expects array\{type\: 1\|2\|4\|8, fieldName\: string, targetEntity\?\: class\-string\}, non\-empty\-array\ given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\AssociationMapping\:\:fromMappingArray\(\) expects array\{fieldName\: string, sourceEntity\: class\-string, targetEntity\: class\-string, cascade\?\: list\<''all''\|''detach''\|''persist''\|''refresh''\|''remove''\>, fetch\?\: 2\|3\|4\|null, inherited\?\: class\-string\|null, declared\?\: class\-string\|null, cache\?\: array\\|null, \.\.\.\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\EmbeddedClassMapping\:\:fromMappingArray\(\) expects array\{class\: class\-string, columnPrefix\?\: string\|false\|null, declaredField\?\: string\|null, originalField\?\: string\|null, inherited\?\: class\-string\|null, declared\?\: class\-string\|null\}, array\{class\: string, columnPrefix\: string\|false\|null, declaredField\: string\|null, originalField\: string\|null\} given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\:\:fromMappingArrayAndNamingStrategy\(\) expects array\{fieldName\: string, sourceEntity\: class\-string, targetEntity\: class\-string, cascade\?\: list\<''all''\|''detach''\|''persist''\|''refresh''\|''remove''\>, fetch\?\: 2\|3\|4\|null, inherited\?\: class\-string\|null, declared\?\: class\-string\|null, cache\?\: array\\|null, \.\.\.\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\:\:fromMappingArrayAndName\(\) expects array\{fieldName\: string, sourceEntity\: class\-string, targetEntity\: class\-string, cascade\?\: list\<''all''\|''detach''\|''persist''\|''refresh''\|''remove''\>, fetch\?\: 2\|3\|4\|null, inherited\?\: class\-string\|null, declared\?\: class\-string\|null, cache\?\: array\\|null, \.\.\.\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\ToOneInverseSideMapping\:\:fromMappingArrayAndName\(\) expects array\{fieldName\: string, sourceEntity\: class\-string, targetEntity\: class\-string, cascade\?\: list\<''all''\|''detach''\|''persist''\|''refresh''\|''remove''\>, fetch\?\: 2\|3\|4\|null, inherited\?\: class\-string\|null, declared\?\: class\-string\|null, cache\?\: array\\|null, \.\.\.\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Parameter \#1 \$mappingArray of static method Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\:\:fromMappingArrayAndName\(\) expects array\{fieldName\: string, sourceEntity\: class\-string, targetEntity\: class\-string, cascade\?\: list\<''all''\|''detach''\|''persist''\|''refresh''\|''remove''\>, fetch\?\: 2\|3\|4\|null, inherited\?\: class\-string\|null, declared\?\: class\-string\|null, cache\?\: array\\|null, \.\.\.\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 2
path: src/Mapping/ClassMetadata.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\:\:\$customRepositoryClassName with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\:\:\$table type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 2
path: src/Mapping/ClassMetadata.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:\$customRepositoryClassName \(class\-string\\|null\) does not accept class\-string\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:\$discriminatorMap \(array\\) does not accept array\\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:\$entityListeners \(array\\>\) does not accept non\-empty\-array\\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:\$subClasses \(list\\) does not accept non\-empty\-list\\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Strict comparison using \!\=\= between Doctrine\\ORM\\Mapping\\FieldMapping and false will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
count: 2
path: src/Mapping/ClassMetadata.php
-
message: '#^Template type T is declared as covariant, but occurs in invariant position in property Doctrine\\ORM\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: generics.variance
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Template type T is declared as covariant, but occurs in invariant position in property Doctrine\\ORM\\Mapping\\ClassMetadata\:\:\$reflClass\.$#'
identifier: generics.variance
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Template type T is declared as covariant, but occurs in invariant position in return type of method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:getReflectionClass\(\)\.$#'
identifier: generics.variance
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Unable to resolve the template type C in call to method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:fullyQualifiedClassName\(\)$#'
identifier: argument.templateType
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^If condition is always true\.$#'
identifier: if.alwaysTrue
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addDefaultDiscriminatorMap\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedEmbeddedClasses\(\) has parameter \$parentClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedEmbeddedClasses\(\) has parameter \$subClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedFields\(\) has parameter \$parentClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedFields\(\) has parameter \$subClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedIndexes\(\) has parameter \$parentClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedIndexes\(\) has parameter \$subClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedRelations\(\) has parameter \$parentClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addInheritedRelations\(\) has parameter \$subClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addMappingInheritanceInformation\(\) has parameter \$parentClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addNestedEmbeddedClasses\(\) has parameter \$parentClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:addNestedEmbeddedClasses\(\) has parameter \$subClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:completeIdGeneratorMapping\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:doLoadMetadata\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:doLoadMetadata\(\) has parameter \$parent with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:findAbstractEntityClassesNotListedInDiscriminatorMap\(\) has parameter \$rootEntityClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:inheritIdGeneratorMapping\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:inheritIdGeneratorMapping\(\) has parameter \$parent with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:initializeReflection\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:isEntity\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:newClassMetadataInstance\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:onNotFoundMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:validateRuntimeMetadata\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:validateRuntimeMetadata\(\) has parameter \$parent with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadataFactory\:\:wakeupReflection\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Parameter \#1 \$generator of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:setIdGenerator\(\) expects Doctrine\\ORM\\Id\\AbstractIdGenerator, object given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Parameter \#1 \$rootEntityClass of static method Doctrine\\ORM\\Mapping\\MappingException\:\:missingInheritanceTypeDeclaration\(\) expects class\-string, class\-string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\DefaultEntityListenerResolver\:\:\$instances \(array\\) does not accept array\\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/DefaultEntityListenerResolver.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getColumnAlias\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getIdentifierColumnNames\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getJoinColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getJoinTableName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getReferencedJoinColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getSequenceName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\DefaultQuoteStrategy\:\:getTableName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\AttributeDriver\:\:isRepeatedPropertyDeclaration\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Parameter \#1 \$mapping of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:mapEmbedded\(\) expects array\{fieldName\: string, class\?\: class\-string, declaredField\?\: string, columnPrefix\?\: string\|false\|null, originalField\?\: string\}, array\{fieldName\: string, cache\?\: array\{usage\: int, region\: string\|null\}, class\: string\|null, columnPrefix\: bool\|string\|null\} given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Unable to resolve the template type C in call to method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:fullyQualifiedClassName\(\)$#'
identifier: argument.templateType
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedColumn…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedTableN…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencingColum…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Table'' and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Index and ''getType'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\ClassMetadata\ and Doctrine\\ORM\\Mapping\\ClassMetadata will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:__construct\(\) has parameter \$sm with generic class Doctrine\\DBAL\\Schema\\AbstractSchemaManager but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildFieldMappings\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildIndexes\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildToOneAssociationMappings\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) has parameter \$asset with generic class Doctrine\\DBAL\\Schema\\AbstractAsset but does not specify its types\: N$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getClassNameForTable\(\) should return class\-string but returns string\.$#'
identifier: return.type
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 4
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$fileExtension with no type specified\.$#'
identifier: missingType.parameter
count: 1
path: src/Mapping/Driver/SimplifiedXmlDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$prefixes with no type specified\.$#'
identifier: missingType.parameter
count: 1
path: src/Mapping/Driver/SimplifiedXmlDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\XmlDriver\:\:cacheToArray\(\) should return array\{usage\: int\|null, region\?\: string\} but returns array\{usage\: ''''\|''0''\|int\|null, region\: string\|null\}\.$#'
identifier: return.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\XmlDriver\:\:columnToArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\\|null, options\?\: array\\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\\|bool\|object\|string\>\} given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Parameter \#1 \$data of function simplexml_load_string expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Parameter \#1 \$mapping of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:mapEmbedded\(\) expects array\{fieldName\: string, class\?\: class\-string, declaredField\?\: string, columnPrefix\?\: string\|false\|null, originalField\?\: string\}, array\{fieldName\: string, class\: string\|null, columnPrefix\: string\|false\|null\} given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Parameter \#1 \$repositoryClassName of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:setCustomRepositoryClass\(\) expects class\-string\\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Parameter \#1 \$repositoryClassName of method Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:setCustomRepositoryClass\(\) expects class\-string\\|null, string\|null given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\\|bool\|object\|string\>, quoted\?\: bool\}\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\FieldMapping\:\:\$declared \(class\-string\|null\) does not accept string\|null\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/FieldMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\FieldMapping\:\:\$enumType \(class\-string\\|null\) does not accept string\|null\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/FieldMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\FieldMapping\:\:\$inherited \(class\-string\|null\) does not accept string\|null\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/FieldMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\FieldMapping\:\:\$options type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/FieldMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\JoinTableMapping\:\:\$options \(array\\) does not accept array\\|bool\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/JoinTableMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\JoinTableMapping\:\:\$quoted \(bool\|null\) does not accept array\\|bool\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/JoinTableMapping.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\JoinTableMapping\:\:\$schema \(string\|null\) does not accept array\\|bool\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/JoinTableMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\LegacyReflectionFields\:\:__construct\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Parameter \#1 \$class of method Doctrine\\Persistence\\Mapping\\ReflectionService\:\:getAccessibleProperty\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Strict comparison using \!\=\= between array\ and null will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
count: 1
path: src/Mapping/ManyToManyOwningSideMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\MappedSuperclass\:\:__construct\(\) has parameter \$repositoryClass with generic class Doctrine\\ORM\\EntityRepository but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/MappedSuperclass.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\PropertyAccessors\\EnumPropertyAccessor\:\:toEnum\(\) should return array\\|BackedEnum but returns array\\.$#'
identifier: return.type
count: 1
path: src/Mapping/PropertyAccessors/EnumPropertyAccessor.php
-
message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(BackedEnum\|int\|string\)\: mixed\)\|null, array\{class\-string\, ''from''\} given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/PropertyAccessors/EnumPropertyAccessor.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getColumnAlias\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getIdentifierColumnNames\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinTableName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getReferencedJoinColumnName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getSequenceName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getTableName\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ReflectionEnumProperty\:\:getValue\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/ReflectionEnumProperty.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\:\:fromMappingArray\(\) should return static\(Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\) but returns Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\.$#'
identifier: return.type
count: 1
path: src/Mapping/ToOneOwningSideMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\:\:fromMappingArrayAndName\(\) has parameter \$table with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/ToOneOwningSideMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\:\:fromMappingArrayAndName\(\) should return static\(Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\) but returns Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\.$#'
identifier: return.type
count: 1
path: src/Mapping/ToOneOwningSideMapping.php
-
message: '#^Call to function is_int\(\) with string will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: src/NativeQuery.php
-
message: '#^Method Doctrine\\ORM\\NativeQuery\:\:_doExecute\(\) never returns int so it can be removed from the return type\.$#'
identifier: return.unusedType
count: 1
path: src/NativeQuery.php
-
message: '#^Result of && is always false\.$#'
identifier: booleanAnd.alwaysFalse
count: 1
path: src/NativeQuery.php
-
message: '#^Method Doctrine\\ORM\\ORMInvalidArgumentException\:\:invalidAssociation\(\) has parameter \$targetClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/ORMInvalidArgumentException.php
-
message: '#^Call to method Doctrine\\ORM\\Mapping\\AssociationMapping\:\:isToMany\(\) will always evaluate to true\.$#'
identifier: method.alreadyNarrowedType
count: 1
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:__construct\(\) has parameter \$typeClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:get\(\) should return T\|null but returns object\|null\.$#'
identifier: return.type
count: 1
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:getTypeClass\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:matching\(\) should return Doctrine\\Common\\Collections\\Collection\ but returns Doctrine\\Common\\Collections\\ArrayCollection\<\(int\|string\), mixed\>\|Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
identifier: return.type
count: 1
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:matching\(\) should return Doctrine\\Common\\Collections\\Collection\ but returns Doctrine\\Common\\Collections\\ReadableCollection\&Doctrine\\Common\\Collections\\Selectable\\.$#'
identifier: return.type
count: 1
path: src/PersistentCollection.php
-
message: '#^Parameter \#1 \$key of method Doctrine\\ORM\\PersistentCollection\\:\:set\(\) expects TKey of \(int\|string\), int\|string given\.$#'
identifier: argument.type
count: 1
path: src/PersistentCollection.php
-
message: '#^Parameter \#2 \$callback of function array_walk expects callable\(object, int\)\: mixed, array\{Doctrine\\Common\\Collections\\Collection\&Doctrine\\Common\\Collections\\Selectable\, ''add''\} given\.$#'
identifier: argument.type
count: 1
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:contains\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:containsKey\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:count\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:get\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:loadCriteria\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:slice\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\CollectionPersister\:\:update\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/CollectionPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTableColumns\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToTargetKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 2
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Call to function assert\(\) with true will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 2
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Call to method Doctrine\\ORM\\Mapping\\AssociationMapping\:\:isIndexed\(\) will always evaluate to true\.$#'
identifier: method.alreadyNarrowedType
count: 2
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\InverseSideMapping&Doctrine\\ORM\\Mapping\\ManyToManyAssociationMapping and Doctrine\\ORM\\Mapping\\InverseSideMapping will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 2
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:collectJoinTableColumnParameters\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:contains\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:containsKey\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:count\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:generateFilterConditionSQL\(\) has parameter \$targetEntity with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:get\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getDeleteRowSQL\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getDeleteRowSQLParameters\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getDeleteSQL\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getDeleteSQLParameters\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getInsertRowSQL\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getInsertRowSQLParameters\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getJoinTableRestrictions\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getJoinTableRestrictionsWithKey\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getMapping\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:getOrderingSql\(\) has parameter \$targetClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:loadCriteria\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:slice\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:update\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Parameter \#1 \$association of method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinTableName\(\) expects Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:contains\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:containsKey\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:count\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:deleteEntityCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:deleteJoinedEntityCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:get\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:getMapping\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:loadCriteria\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:slice\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\OneToManyPersister\:\:update\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Collection/OneToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\AbstractEntityInheritancePersister\:\:getSelectColumnSQL\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/AbstractEntityInheritancePersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 5
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 4
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:indexBy\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:__construct\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandToManyParameters\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:extractIdentifierTypes\(\) has parameter \$versionedClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:fetchVersionAndNotUpsertableValues\(\) has parameter \$versionedClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:generateFilterConditionSQL\(\) has parameter \$targetEntity with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getClassIdentifiersTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getSelectColumnAssociationSQL\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getSelectColumnSQL\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:loadCollectionFromStatement\(\) has parameter \$coll with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:loadManyToManyCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:loadOneToManyCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#1 \$association of method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinTableName\(\) expects Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping, Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#2 \$lockMode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:appendLockHint\(\) expects Doctrine\\DBAL\\LockMode, Doctrine\\DBAL\\LockMode\:\:NONE\|Doctrine\\DBAL\\LockMode\:\:OPTIMISTIC\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_READ\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_WRITE\|int given\.$#'
identifier: argument.type
count: 2
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#3 \$hints of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:hydrateAll\(\) expects array\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#3 \$hints of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:hydrateAll\(\) expects array\, array\ given\.$#'
identifier: argument.type
count: 4
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Property Doctrine\\ORM\\Persisters\\Entity\\CachedPersisterContext\:\:\$sqlTableAliases \(array\\) does not accept array\\.$#'
identifier: assign.propertyType
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Strict comparison using \!\=\= between mixed and null will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\CachedPersisterContext\:\:__construct\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/CachedPersisterContext.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/EntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:loadManyToManyCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/EntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:loadOneToManyCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/EntityPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 2
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\JoinedSubclassPersister\:\:fetchVersionAndNotUpsertableValues\(\) has parameter \$versionedClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\JoinedSubclassPersister\:\:getVersionedClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Parameter \#2 \$lockMode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:appendLockHint\(\) expects Doctrine\\DBAL\\LockMode, Doctrine\\DBAL\\LockMode\:\:NONE\|Doctrine\\DBAL\\LockMode\:\:OPTIMISTIC\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_READ\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_WRITE\|int given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Persisters/Entity/SingleTablePersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\SingleTablePersister\:\:generateFilterConditionSQL\(\) has parameter \$targetEntity with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/SingleTablePersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\SqlExpressionVisitor\:\:__construct\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/SqlExpressionVisitor.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\SqlValueVisitor\:\:getParamsAndTypes\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Persisters/SqlValueVisitor.php
-
message: '#^Parameter \#3 \$className of static method Doctrine\\ORM\\Proxy\\Autoloader\:\:resolveFile\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/Autoloader.php
-
message: '#^Parameter \#3 of closure expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/Autoloader.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\ but returns class\-string\\>\|class\-string\\.$#'
identifier: return.type
count: 1
path: src/Proxy/DefaultProxyClassNameResolver.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\ but returns string\.$#'
identifier: return.type
count: 1
path: src/Proxy/DefaultProxyClassNameResolver.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$isEmbeddedClass\.$#'
identifier: property.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$isMappedSuperclass\.$#'
identifier: property.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Call to an undefined static method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyGhost\(\)\.$#'
identifier: staticMethod.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Call to function is_bool\(\) with bool will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Comparison operation "\<" between 0\|1\|2\|3\|4 and 0 is always false\.$#'
identifier: smaller.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Comparison operation "\>" between 0\|1\|2\|3\|4 and 4 is always false\.$#'
identifier: greater.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) has Doctrine\\ORM\\EntityNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#'
identifier: throws.unusedType
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) has parameter \$classMetadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) return type with generic interface Doctrine\\ORM\\Proxy\\InternalProxy does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClass\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClasses\(\) has parameter \$classes with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateSerializeImpl\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateUseLazyGhostTrait\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:loadProxyClass\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:skipClass\(\) has parameter \$metadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#1 \$class of method Doctrine\\ORM\\Utility\\IdentifierFlattener\:\:flattenIdentifier\(\) expects Doctrine\\ORM\\Mapping\\ClassMetadata, Doctrine\\Persistence\\Mapping\\ClassMetadata given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#1 \$filename of function filemtime expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#3 \$newScope of static method Closure\:\:bind\(\) expects ''static''\|class\-string\|object\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Result of \|\| is always false\.$#'
identifier: booleanOr.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Query\:\:processParameterMappings\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Query.php
-
message: '#^Parameter \#1 \$parameters of method Doctrine\\ORM\\AbstractQuery\:\:toIterable\(\) expects array\\|Doctrine\\Common\\Collections\\ArrayCollection\, iterable\<\(int\|string\), mixed\> given\.$#'
identifier: argument.type
count: 1
path: src/Query.php
-
message: '#^Parameter \#2 \$sqlParams of method Doctrine\\ORM\\Query\:\:evictResultSetCache\(\) expects array\, list\ given\.$#'
identifier: argument.type
count: 1
path: src/Query.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\BitAndFunction\:\:\$firstArithmetic \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/BitAndFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\BitAndFunction\:\:\$secondArithmetic \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/BitAndFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\BitOrFunction\:\:\$firstArithmetic \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/BitOrFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\BitOrFunction\:\:\$secondArithmetic \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/BitOrFunction.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Query\\AST\\Node\:\:\$value\.$#'
identifier: property.notFound
count: 1
path: src/Query/AST/Functions/DateAddFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\DateAddFunction\:\:\$firstDateExpression \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/DateAddFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\DateAddFunction\:\:\$intervalExpression \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/DateAddFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\DateDiffFunction\:\:\$date1 \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/DateDiffFunction.php
-
message: '#^Property Doctrine\\ORM\\Query\\AST\\Functions\\DateDiffFunction\:\:\$date2 \(Doctrine\\ORM\\Query\\AST\\Node\) does not accept Doctrine\\ORM\\Query\\AST\\Node\|string\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/AST/Functions/DateDiffFunction.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Query\\AST\\Node\:\:\$value\.$#'
identifier: property.notFound
count: 1
path: src/Query/AST/Functions/DateSubFunction.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 2
path: src/Query/AST/Functions/IdentityFunction.php
-
message: '#^Parameter \#1 \$joinColumn of method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinColumnName\(\) expects Doctrine\\ORM\\Mapping\\JoinColumnMapping, Doctrine\\ORM\\Mapping\\JoinColumnMapping\|false given\.$#'
identifier: argument.type
count: 1
path: src/Query/AST/Functions/IdentityFunction.php
-
message: '#^Parameter \#1 \$simpleArithmeticExpr of method Doctrine\\ORM\\Query\\SqlWalker\:\:walkSimpleArithmeticExpression\(\) expects Doctrine\\ORM\\Query\\AST\\Node\|string, Doctrine\\ORM\\Query\\AST\\Node\|string\|true given\.$#'
identifier: argument.type
count: 1
path: src/Query/AST/Functions/LocateFunction.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 1
path: src/Query/AST/Functions/SizeFunction.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Query/AST/Functions/SizeFunction.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 1
path: src/Query/AST/Functions/SizeFunction.php
-
message: '#^Parameter \#1 \$association of method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinTableName\(\) expects Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping, Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 1
path: src/Query/AST/Functions/SizeFunction.php
-
message: '#^Parameter \#2 \$mode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getTrimExpression\(\) expects Doctrine\\DBAL\\Platforms\\TrimMode, Doctrine\\DBAL\\Platforms\\TrimMode\:\:BOTH\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:LEADING\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:TRAILING\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:UNSPECIFIED\|int given\.$#'
identifier: argument.type
count: 2
path: src/Query/AST/Functions/TrimFunction.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Query\\SqlWalker\:\:walkJoinPathExpression\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Query/AST/JoinClassPathExpression.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Query\\SqlWalker\:\:walkJoinVariableDeclaration\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Query/AST/JoinVariableDeclaration.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Query\\SqlWalker\:\:walkWhenClauseExpression\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Query/AST/SimpleWhenClause.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Query\\SqlWalker\:\:walkWhenClauseExpression\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Query/AST/WhenClause.php
-
message: '#^Argument of an invalid type list\\|string supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable
count: 1
path: src/Query/Exec/MultiTableDeleteExecutor.php
-
message: '#^Cannot assign new offset to list\\|string\.$#'
identifier: offsetAssign.dimType
count: 1
path: src/Query/Exec/MultiTableDeleteExecutor.php
-
message: '#^Method Doctrine\\ORM\\Query\\Exec\\MultiTableDeleteExecutor\:\:execute\(\) should return int but returns int\|string\.$#'
identifier: return.type
count: 1
path: src/Query/Exec/MultiTableDeleteExecutor.php
-
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/MultiTableDeleteExecutor.php
-
message: '#^Argument of an invalid type list\\|string supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Method Doctrine\\ORM\\Query\\Exec\\MultiTableUpdateExecutor\:\:execute\(\) should return int but returns int\|string\.$#'
identifier: return.type
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\ given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#1 \$sql of method Doctrine\\DBAL\\Connection\:\:executeQuery\(\) expects string, list\\|string given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/SingleSelectExecutor.php
-
message: '#^Method Doctrine\\ORM\\Query\\Exec\\SingleTableDeleteUpdateExecutor\:\:execute\(\) should return int but returns int\|string\.$#'
identifier: return.type
count: 1
path: src/Query/Exec/SingleTableDeleteUpdateExecutor.php
-
message: '#^Parameter \#1 \$sql of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects string, list\\|string given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/SingleTableDeleteUpdateExecutor.php
-
message: '#^Method Doctrine\\ORM\\Query\\Expr\\Func\:\:getArguments\(\) should return list\ but returns array\\.$#'
identifier: return.type
count: 1
path: src/Query/Expr/Func.php
-
message: '#^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns int so it can be removed from the return type\.$#'
identifier: return.unusedType
count: 1
path: src/Query/ParameterTypeInferer.php
-
message: '#^@readonly property cannot have a default value\.$#'
identifier: property.readOnlyByPhpDocDefaultValue
count: 3
path: src/Query/Parser.php
-
message: '#^Call to an undefined method object\:\:parse\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Query/Parser.php
-
message: '#^Method Doctrine\\ORM\\Query\\Parser\:\:CustomFunctionsReturningStrings\(\) should return Doctrine\\ORM\\Query\\AST\\Functions\\FunctionNode but returns object\.$#'
identifier: return.type
count: 1
path: src/Query/Parser.php
-
message: '#^Method Doctrine\\ORM\\Query\\Parser\:\:getMetadataForDqlAlias\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/Parser.php
-
message: '#^Parameter \#1 \$AST of method Doctrine\\ORM\\Query\\Parser\:\:processDeferredNewObjectExpressions\(\) expects Doctrine\\ORM\\Query\\AST\\SelectStatement, Doctrine\\ORM\\Query\\AST\\DeleteStatement\|Doctrine\\ORM\\Query\\AST\\SelectStatement\|Doctrine\\ORM\\Query\\AST\\UpdateStatement given\.$#'
identifier: argument.type
count: 1
path: src/Query/Parser.php
-
message: '#^Parameter \#1 \$expected of method Doctrine\\ORM\\Query\\Parser\:\:syntaxError\(\) expects string, int\|string given\.$#'
identifier: argument.type
count: 3
path: src/Query/Parser.php
-
message: '#^Parameter \#1 \$expression of class Doctrine\\ORM\\Query\\AST\\ParenthesisExpression constructor expects Doctrine\\ORM\\Query\\AST\\Node, Doctrine\\ORM\\Query\\AST\\Node\|string given\.$#'
identifier: argument.type
count: 1
path: src/Query/Parser.php
-
message: '#^Parameter \#2 \$stringPattern of class Doctrine\\ORM\\Query\\AST\\LikeExpression constructor expects Doctrine\\ORM\\Query\\AST\\Functions\\FunctionNode\|Doctrine\\ORM\\Query\\AST\\InputParameter\|Doctrine\\ORM\\Query\\AST\\Literal\|Doctrine\\ORM\\Query\\AST\\PathExpression, Doctrine\\ORM\\Query\\AST\\Node given\.$#'
identifier: argument.type
count: 1
path: src/Query/Parser.php
-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/Query/Parser.php
-
message: '#^Strict comparison using \=\=\= between 102 and 102 will always evaluate to true\.$#'
identifier: identical.alwaysTrue
count: 1
path: src/Query/Parser.php
-
message: '#^Unreachable statement \- code above always terminates\.$#'
identifier: deadCode.unreachable
count: 2
path: src/Query/Parser.php
-
message: '#^Using nullsafe property access "\?\-\>position" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: src/Query/Parser.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 2
path: src/Query/ResultSetMappingBuilder.php
-
message: '#^Method Doctrine\\ORM\\Query\\ResultSetMappingBuilder\:\:isInheritanceSupported\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/ResultSetMappingBuilder.php
-
message: '#^Instanceof between Doctrine\\ORM\\Query\\AST\\DeleteStatement and Doctrine\\ORM\\Query\\AST\\DeleteStatement will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: src/Query/SqlOutputWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 2
path: src/Query/SqlWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$sourceToTargetKeyColumns\.$#'
identifier: property.notFound
count: 2
path: src/Query/SqlWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 3
path: src/Query/SqlWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 6
path: src/Query/SqlWalker.php
-
message: '#^Cannot assign new offset to list\\|string\.$#'
identifier: offsetAssign.dimType
count: 2
path: src/Query/SqlWalker.php
-
message: '#^Match arm comparison between 3 and 3 is always true\.$#'
identifier: match.alwaysTrue
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Method Doctrine\\ORM\\Query\\SqlWalker\:\:generateClassTableInheritanceJoins\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Method Doctrine\\ORM\\Query\\SqlWalker\:\:generateFilterConditionSQL\(\) has parameter \$targetEntity with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Method Doctrine\\ORM\\Query\\SqlWalker\:\:getChildDiscriminatorsFromClassMetadata\(\) has parameter \$rootClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Method Doctrine\\ORM\\Query\\SqlWalker\:\:getMetadataForDqlAlias\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Parameter \#1 \$association of method Doctrine\\ORM\\Mapping\\QuoteStrategy\:\:getJoinTableName\(\) expects Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping, Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 2
path: src/Query/SqlWalker.php
-
message: '#^Parameter \#1 \$condTerm of method Doctrine\\ORM\\Query\\SqlWalker\:\:walkConditionalTerm\(\) expects Doctrine\\ORM\\Query\\AST\\ConditionalFactor\|Doctrine\\ORM\\Query\\AST\\ConditionalPrimary\|Doctrine\\ORM\\Query\\AST\\ConditionalTerm, Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional given\.$#'
identifier: argument.type
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Parameter \#1 \$identVariable of method Doctrine\\ORM\\Query\\SqlWalker\:\:walkEntityIdentificationVariable\(\) expects string, Doctrine\\ORM\\Query\\AST\\Node\|string given\.$#'
identifier: argument.type
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Parameter \#2 \$fieldName of method Doctrine\\ORM\\Query\\ResultSetMapping\:\:addIndexBy\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Property Doctrine\\ORM\\Query\\SqlWalker\:\:\$selectedClasses with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Method Doctrine\\ORM\\Query\\TreeWalkerAdapter\:\:getMetadataForDqlAlias\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Query/TreeWalkerAdapter.php
-
message: '#^Argument of an invalid type array\\|object\|string\>\|object\|string\|false supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable
count: 1
path: src/QueryBuilder.php
-
message: '#^Method Doctrine\\ORM\\QueryBuilder\:\:getParameter\(\) should return Doctrine\\ORM\\Query\\Parameter\|null but returns Doctrine\\ORM\\Query\\Parameter\|false\|null\.$#'
identifier: return.type
count: 1
path: src/QueryBuilder.php
-
message: '#^Parameter \#2 \$dqlPart of method Doctrine\\ORM\\QueryBuilder\:\:add\(\) expects array\<''join''\|int\<0, max\>, array\\|string\>\|object\|string, array\{Doctrine\\ORM\\Query\\Expr\\Andx\|Doctrine\\ORM\\Query\\Expr\\Orx\}\|Doctrine\\ORM\\Query\\Expr\\Andx given\.$#'
identifier: argument.type
count: 1
path: src/QueryBuilder.php
-
message: '#^Parameter \#2 \$dqlPart of method Doctrine\\ORM\\QueryBuilder\:\:add\(\) expects array\<''join''\|int\<0, max\>, array\\|string\>\|object\|string, array\{Doctrine\\ORM\\Query\\Expr\\Composite\}\|Doctrine\\ORM\\Query\\Expr\\Andx given\.$#'
identifier: argument.type
count: 1
path: src/QueryBuilder.php
-
message: '#^Parameter \#2 \$dqlPart of method Doctrine\\ORM\\QueryBuilder\:\:add\(\) expects array\<''join''\|int\<0, max\>, array\\|string\>\|object\|string, non\-empty\-array\ given\.$#'
identifier: argument.type
count: 2
path: src/QueryBuilder.php
-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 2
path: src/QueryBuilder.php
-
message: '#^Method Doctrine\\ORM\\Repository\\DefaultRepositoryFactory\:\:createRepository\(\) return type with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Repository/DefaultRepositoryFactory.php
-
message: '#^Property Doctrine\\ORM\\Repository\\DefaultRepositoryFactory\:\:\$repositoryList with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Repository/DefaultRepositoryFactory.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#1 \$filename of function file_exists expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#1 \$filename of function is_writable expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Console/Command/MappingDescribeCommand.php
-
message: '#^Parameter \#1 \$entityListeners of method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:formatEntityListeners\(\) expects list\, array\\> given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/MappingDescribeCommand.php
-
message: '#^Parameter \#2 \$callback of function array_filter expects \(callable\(class\-string\)\: bool\)\|null, Closure\(mixed\)\: \(0\|1\|false\) given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/MappingDescribeCommand.php
-
message: '#^Parameter \#1 \$classes of method Doctrine\\ORM\\Tools\\SchemaTool\:\:createSchema\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/SchemaTool/CreateCommand.php
-
message: '#^Parameter \#1 \$classes of method Doctrine\\ORM\\Tools\\SchemaTool\:\:getCreateSchemaSql\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/SchemaTool/CreateCommand.php
-
message: '#^Parameter \#1 \$classes of method Doctrine\\ORM\\Tools\\SchemaTool\:\:dropSchema\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/SchemaTool/DropCommand.php
-
message: '#^Parameter \#1 \$classes of method Doctrine\\ORM\\Tools\\SchemaTool\:\:getDropSchemaSQL\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 2
path: src/Tools/Console/Command/SchemaTool/DropCommand.php
-
message: '#^Parameter \#1 \$classes of method Doctrine\\ORM\\Tools\\SchemaTool\:\:getUpdateSchemaSql\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/SchemaTool/UpdateCommand.php
-
message: '#^Class Doctrine\\ORM\\Tools\\Console\\MetadataFilter extends generic class FilterIterator but does not specify its types\: TKey, TValue, TIterator$#'
identifier: missingType.generics
count: 1
path: src/Tools/Console/MetadataFilter.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\MetadataFilter\:\:__construct\(\) has parameter \$metadata with generic class ArrayIterator but does not specify its types\: TKey, TValue$#'
identifier: missingType.generics
count: 1
path: src/Tools/Console/MetadataFilter.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\MetadataFilter\:\:filter\(\) has parameter \$metadatas with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Console/MetadataFilter.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\MetadataFilter\:\:filter\(\) return type with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Console/MetadataFilter.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\MetadataFilter\:\:getInnerIterator\(\) return type with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Console/MetadataFilter.php
-
message: '#^Parameter \#1 \$string of function html_entity_decode expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Debug.php
-
message: '#^Parameter \#1 \$stream of function fclose expects resource, resource\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/DebugUnitOfWorkListener.php
-
message: '#^Parameter \#1 \$stream of function fwrite expects resource, resource\|false given\.$#'
identifier: argument.type
count: 14
path: src/Tools/DebugUnitOfWorkListener.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Event\\GenerateSchemaTableEventArgs\:\:__construct\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Event/GenerateSchemaTableEventArgs.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Event\\GenerateSchemaTableEventArgs\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Event/GenerateSchemaTableEventArgs.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Tools/Pagination/CountOutputWalker.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Tools/Pagination/LimitSubqueryOutputWalker.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Pagination\\LimitSubqueryOutputWalker\:\:walkSelectStatement\(\) should return string but returns list\\|string\.$#'
identifier: return.type
count: 1
path: src/Tools/Pagination/LimitSubqueryOutputWalker.php
-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Pagination/LimitSubqueryOutputWalker.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Pagination\\Paginator\:\:count\(\) should return int\<0, max\> but returns int\.$#'
identifier: return.type
count: 1
path: src/Tools/Pagination/Paginator.php
-
message: '#^PHPDoc tag @var for variable \$parameters contains generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/Tools/Pagination/Paginator.php
-
message: '#^Parameter \#1 \$parameters of method Doctrine\\ORM\\AbstractQuery\:\:setParameters\(\) expects array\\|Doctrine\\Common\\Collections\\ArrayCollection\, Doctrine\\Common\\Collections\\Collection&iterable\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Pagination/Paginator.php
-
message: '#^Method Doctrine\\ORM\\Tools\\ResolveTargetEntityListener\:\:remapAssociation\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/ResolveTargetEntityListener.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 3
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method getColumns\(\) of class Doctrine\\DBAL\\Schema\\Index\:
Use \{@see getIndexedColumns\(\)\} instead\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method getForeignColumns\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
Use \{@see getReferencedColumnNames\(\)\} instead\.
Returns the names of the referenced table columns
the foreign key constraint is associated with\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method getForeignTableName\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
Use \{@see getReferencedTableName\(\)\} instead\.
Returns the name of the referenced table
the foreign key constraint is associated with\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method getLocalColumns\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
Use \{@see getReferencingColumnNames\(\)\} instead\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method getPrimaryKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
Use \{@see getPrimaryKeyConstraint\(\)\} instead\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method removeForeignKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
Use \{@link dropForeignKey\(\)\} instead\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '''
#^Call to deprecated method setPrimaryKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
Use \{@see addPrimaryKeyConstraint\(\)\} instead\.$#
'''
identifier: method.deprecated
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to function is_numeric\(\) with int\<0, max\> will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:addDiscriminatorColumnDefinition\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:createSchema\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:dropSchema\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:gatherColumn\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:gatherColumns\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:gatherRelationJoinColumns\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:gatherRelationsSql\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getAssetName\(\) has parameter \$asset with generic class Doctrine\\DBAL\\Schema\\AbstractAsset but does not specify its types\: N$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getCreateSchemaSql\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getDefiningClass\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getDefiningClass\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getDropSchemaSQL\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getIndexColumns\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getSchemaFromMetadata\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getUpdateSchemaSql\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:processingNotRequired\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Negated boolean expression is always false\.$#'
identifier: booleanNot.alwaysFalse
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#1 \$classes of method Doctrine\\ORM\\Tools\\SchemaTool\:\:getUpdateSchemaSql\(\) expects list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addIndex\(\) expects non\-empty\-list\, list\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addUniqueIndex\(\) expects non\-empty\-list\, array\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#2 \$columns of class Doctrine\\DBAL\\Schema\\Index constructor expects non\-empty\-list\, list\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#2 \$localColumnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addForeignKeyConstraint\(\) expects non\-empty\-list\, list\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#2 \$primaryKeyColumns of method Doctrine\\ORM\\Tools\\SchemaTool\:\:addPrimaryKeyConstraint\(\) expects non\-empty\-array\, list\ given\.$#'
identifier: argument.type
count: 2
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#2 \$primaryKeyColumns of method Doctrine\\ORM\\Tools\\SchemaTool\:\:addPrimaryKeyConstraint\(\) expects non\-empty\-array\, non\-empty\-list\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \#3 \$foreignColumnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addForeignKeyConstraint\(\) expects non\-empty\-list\, list\ given\.$#'
identifier: argument.type
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Property Doctrine\\ORM\\Tools\\SchemaTool\:\:\$schemaManager with generic class Doctrine\\DBAL\\Schema\\AbstractSchemaManager does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$inversedBy\.$#'
identifier: property.notFound
count: 2
path: src/Tools/SchemaValidator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Tools/SchemaValidator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 6
path: src/Tools/SchemaValidator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 9
path: src/Tools/SchemaValidator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Tools/SchemaValidator.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToTargetKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Tools/SchemaValidator.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:orderBy\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Tools/SchemaValidator.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaValidator\:\:validateClass\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaValidator.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaValidator\:\:validatePropertiesTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Tools/SchemaValidator.php
-
message: '#^ Parameter \#3 \$changeSet of class Doctrine\\ORM\\Event\\PreUpdateEventArgs constructor is passed by reference, so it expects variables only$#'
identifier: argument.byRef
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\AssociationMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$inversedBy\.$#'
identifier: property.notFound
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 3
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 3
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$targetToSourceKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:convertSingleFieldIdentifierToPHPValue\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:eagerLoadCollections\(\) has parameter \$collections with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:getEntityChangeSet\(\) return type with generic class Doctrine\\ORM\\PersistentCollection does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:hasMissingIdsWhichAreForeignKeys\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:isCollectionScheduledForDeletion\(\) has parameter \$coll with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:loadCollection\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:normalizeIdentifier\(\) has parameter \$targetClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:scheduleCollectionDeletion\(\) has parameter \$coll with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:scheduleCollectionForBatchLoading\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Method Doctrine\\ORM\\UnitOfWork\:\:scheduleCollectionForBatchLoading\(\) has parameter \$sourceClass with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/UnitOfWork.php
-
message: '#^Parameter \#2 \$assoc of method Doctrine\\ORM\\PersistentCollection\<\(int\|string\),mixed\>\:\:setOwner\(\) expects Doctrine\\ORM\\Mapping\\AssociationMapping&Doctrine\\ORM\\Mapping\\ToManyAssociationMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 4
path: src/UnitOfWork.php
-
message: '#^Parameter \#2 \$assoc of method Doctrine\\ORM\\PersistentCollection\<\*NEVER\*,\*NEVER\*\>\:\:setOwner\(\) expects Doctrine\\ORM\\Mapping\\AssociationMapping&Doctrine\\ORM\\Mapping\\ToManyAssociationMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
identifier: argument.type
count: 1
path: src/UnitOfWork.php
-
message: '#^Parameter \#2 \$length of function array_chunk expects int\<1, max\>, int given\.$#'
identifier: argument.type
count: 2
path: src/UnitOfWork.php
-
message: '#^Parameter \#3 \$collection of class Doctrine\\ORM\\PersistentCollection constructor expects Doctrine\\Common\\Collections\\Collection\<\(int\|string\), mixed\>&Doctrine\\Common\\Collections\\Selectable\<\(int\|string\), mixed\>, Doctrine\\Common\\Collections\\Collection given\.$#'
identifier: argument.type
count: 1
path: src/UnitOfWork.php
-
message: '#^Parameter \#5 \$invoke of method Doctrine\\ORM\\Event\\ListenersInvoker\:\:invoke\(\) expects int\<0, 7\>, int\\|int\<1, max\> given\.$#'
identifier: argument.type
count: 1
path: src/UnitOfWork.php
-
message: '#^Parameter &\$visited by\-ref type of method Doctrine\\ORM\\UnitOfWork\:\:cascadeDetach\(\) expects array\, array\ given\.$#'
identifier: parameterByRef.type
count: 2
path: src/UnitOfWork.php
-
message: '#^Property Doctrine\\ORM\\UnitOfWork\:\:\$entityChangeSets \(array\\>\) does not accept non\-empty\-array\\>\.$#'
identifier: assign.propertyType
count: 1
path: src/UnitOfWork.php
-
message: '#^Unable to resolve the template type T in call to method static method Symfony\\Component\\VarExporter\\Hydrator\:\:hydrate\(\)$#'
identifier: argument.templateType
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
count: 1
path: src/Utility/HierarchyDiscriminatorResolver.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$subClasses\.$#'
identifier: property.notFound
count: 1
path: src/Utility/HierarchyDiscriminatorResolver.php
-
message: '#^Method Doctrine\\ORM\\Utility\\HierarchyDiscriminatorResolver\:\:resolveDiscriminatorsForClass\(\) has parameter \$rootClassMetadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/HierarchyDiscriminatorResolver.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Utility/IdentifierFlattener.php
-
message: '#^Method Doctrine\\ORM\\Utility\\IdentifierFlattener\:\:__construct\(\) has parameter \$metadataFactory with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadataFactory but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/IdentifierFlattener.php
-
message: '#^Method Doctrine\\ORM\\Utility\\IdentifierFlattener\:\:flattenIdentifier\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/IdentifierFlattener.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\JoinTableMapping\|Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinTable\.$#'
identifier: property.notFound
count: 2
path: src/Utility/PersisterHelper.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToTargetKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$sourceToTargetKeyColumns\.$#'
identifier: property.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getTypeOfColumn\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getTypeOfField\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) should return list\ but returns list\\.$#'
identifier: return.type
count: 1
path: src/Utility/PersisterHelper.php
================================================
FILE: phpstan-dbal3.neon
================================================
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
reportUnmatchedIgnoredErrors: false # Some errors in the baseline only apply to DBAL 4
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# We can be certain that those values are not matched.
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~.*getTrimExpression.*expects int.*~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to static method quoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getObjectName\(\)\.$~'
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::get.*\.$~'
identifier: method.notFound
-
message: '~createComparator~'
identifier: arguments.count
-
message: '~UnqualifiedName~'
identifier: class.notFound
-
message: '~IndexedColumn~'
identifier: class.notFound
-
message: '~PrimaryKeyConstraint~'
identifier: class.notFound
-
message: '~IndexType~'
identifier: class.notFound
-
message: '~dropForeignKey~'
identifier: method.notFound
-
message: '~getIndexedColumns~'
identifier: method.notFound
-
message: '~getPrimaryKeyConstraint~'
identifier: method.notFound
-
message: '~PrimaryKeyConstraint~'
identifier: class.notFound
path: src/Tools/SchemaTool.php
-
message: '~^Call to method toString.*UnqualifiedName\.$~'
path: src/Tools/SchemaTool.php
- '~^Call to method getObjectName\(\) on an unknown class Doctrine\\DBAL\\Schema\\NamedObject\.$~'
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
- '~^Class Doctrine\\DBAL\\Schema\\NamedObject not found\.$~'
-
message: '~sort~'
identifier: argument.unresolvableType
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#1 \$asset of static method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) expects Doctrine\\DBAL\\Schema\\AbstractAsset, Doctrine\\DBAL\\Schema\\Column\|false given\.$#'
identifier: argument.type
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instantiated class Doctrine\\DBAL\\Schema\\DefaultExpression\\\w+ not found\.$#'
identifier: class.notFound
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
-
message: '~^Call to deprecated method getEventManager\(\) of class Doctrine\\DBAL\\Connection\.$~'
path: src/EntityManager.php
-
message: '~deprecated class Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand\:~'
path: src/Tools/Console/ConsoleRunner.php
# Compatibility with Persistence 3
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php
-
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Utility/PersisterHelper.php
-
message: '~inferParameterTypes.*should return~'
path: src/Utility/PersisterHelper.php
-
message: '~.*appendLockHint.*expects.*LockMode given~'
paths:
- src/Persisters/Entity/BasicEntityPersister.php
- src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '~.*executeStatement.*expects~'
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '~method_exists.*getEventManager~'
path: src/EntityManager.php
-
message: '~method_exists.*getIdentitySequence~'
path: src/Mapping/ClassMetadataFactory.php
-
message: '~expand(Criteria)?Parameters.*should return array~'
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '~inferType.*never returns~'
path: src/Query/ParameterTypeInferer.php
================================================
FILE: phpstan-params.neon
================================================
parameters:
level: 7
paths:
- src
- tests/StaticAnalysis
excludePaths:
- src/Mapping/Driver/AttributeReader.php
earlyTerminatingMethodCalls:
Doctrine\ORM\Query\Parser:
- syntaxError
phpVersion: 80400
================================================
FILE: phpstan.neon
================================================
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# We can be certain that those values are not matched.
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# The return type is already narrow enough.
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns ''[a-z_]+'' so it can be removed from the return type\.$~'
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns Doctrine\\DBAL\\(?:Array)?ParameterType\:\:[A-Z_]+ so it can be removed from the return type\.$~'
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Utility/PersisterHelper.php
# Compatibility with DBAL 3
# See https://github.com/doctrine/dbal/pull/3480
-
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
path: src/UnitOfWork.php
-
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
path: src/UnitOfWork.php
-
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
path: src/UnitOfWork.php
-
message: '~^Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner::addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
path: src/Tools/Console/ConsoleRunner.php
-
message: '~Strict comparison using \=\=\= between callable\(\)\: mixed and null will always evaluate to false\.~'
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
# Compatibility with Persistence 3
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php
================================================
FILE: phpunit.xml.dist
================================================
./tests/Tests/ORMperformancelocking_functionalsrc
================================================
FILE: src/AbstractQuery.php
================================================
*/
protected ArrayCollection $parameters;
/**
* The user-specified ResultSetMapping to use.
*/
protected ResultSetMapping|null $resultSetMapping = null;
/**
* The map of query hints.
*
* @phpstan-var array
*/
protected array $hints = [];
/**
* The hydration mode.
*
* @phpstan-var string|AbstractQuery::HYDRATE_*
*/
protected string|int $hydrationMode = self::HYDRATE_OBJECT;
protected QueryCacheProfile|null $queryCacheProfile = null;
/**
* Whether or not expire the result cache.
*/
protected bool $expireResultCache = false;
protected QueryCacheProfile|null $hydrationCacheProfile = null;
/**
* Whether to use second level cache, if available.
*/
protected bool $cacheable = false;
protected bool $hasCache = false;
/**
* Second level cache region name.
*/
protected string|null $cacheRegion = null;
/**
* Second level query cache mode.
*
* @phpstan-var Cache::MODE_*|null
*/
protected int|null $cacheMode = null;
protected CacheLogger|null $cacheLogger = null;
protected int $lifetime = 0;
/**
* Initializes a new instance of a class derived from AbstractQuery.
*/
public function __construct(
/**
* The entity manager used by this query object.
*/
protected EntityManagerInterface $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.
*
* @return $this
*/
public function setCacheable(bool $cacheable): static
{
$this->cacheable = $cacheable;
return $this;
}
/** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */
public function isCacheable(): bool
{
return $this->cacheable;
}
/** @return $this */
public function setCacheRegion(string $cacheRegion): static
{
$this->cacheRegion = $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(): string|null
{
return $this->cacheRegion;
}
/** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */
protected function isCacheEnabled(): bool
{
return $this->cacheable && $this->hasCache;
}
public function getLifetime(): int
{
return $this->lifetime;
}
/**
* Sets the life-time for this query into second level cache.
*
* @return $this
*/
public function setLifetime(int $lifetime): static
{
$this->lifetime = $lifetime;
return $this;
}
/** @phpstan-return Cache::MODE_*|null */
public function getCacheMode(): int|null
{
return $this->cacheMode;
}
/**
* @phpstan-param Cache::MODE_* $cacheMode
*
* @return $this
*/
public function setCacheMode(int $cacheMode): static
{
$this->cacheMode = $cacheMode;
return $this;
}
/**
* Gets the SQL query that corresponds to this query object.
* The returned SQL syntax depends on the connection driver that is used
* by this query object at the time of this method call.
*
* @return list|string SQL query
*/
abstract public function getSQL(): string|array;
/**
* Retrieves the associated EntityManager of this Query instance.
*/
public function getEntityManager(): EntityManagerInterface
{
return $this->em;
}
/**
* Frees the resources used by the query object.
*
* Resets Parameters, Parameter Types and Query Hints.
*/
public function free(): void
{
$this->parameters = new ArrayCollection();
$this->hints = $this->em->getConfiguration()->getDefaultQueryHints();
}
/**
* Get all defined parameters.
*
* @phpstan-return ArrayCollection
*/
public function getParameters(): ArrayCollection
{
return $this->parameters;
}
/**
* Gets a query parameter.
*
* @param int|string $key The key (index or name) of the bound parameter.
*
* @return Parameter|null The value of the bound parameter, or NULL if not available.
*/
public function getParameter(int|string $key): Parameter|null
{
$key = Parameter::normalizeName($key);
$filteredParameters = $this->parameters->filter(
static fn (Parameter $parameter): bool => $parameter->getName() === $key,
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
}
/**
* Sets a collection of query parameters.
*
* @param ArrayCollection|mixed[] $parameters
* @phpstan-param ArrayCollection|mixed[] $parameters
*
* @return $this
*/
public function setParameters(ArrayCollection|array $parameters): static
{
if (is_array($parameters)) {
/** @phpstan-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 ParameterType|ArrayParameterType|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(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static
{
$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.
*
* @throws ORMInvalidArgumentException
*/
public function processParameterValue(mixed $value): mixed
{
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 ClassMetadata) {
return $value->name;
}
if ($value instanceof BackedEnum) {
return $value->value;
}
if (! is_object($value)) {
return $value;
}
try {
$class = DefaultProxyClassNameResolver::getClass($value);
$value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
}
} catch (MappingException | ORMMappingException) {
/* 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
*/
private function potentiallyProcessIterable(mixed $value): mixed
{
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(ResultSetMapping $rsm): static
{
$this->translateNamespaces($rsm);
$this->resultSetMapping = $rsm;
return $this;
}
/**
* Gets the ResultSetMapping used for hydration.
*/
protected function getResultSetMapping(): ResultSetMapping|null
{
return $this->resultSetMapping;
}
/**
* Allows to translate entity namespaces to full qualified names.
*/
private function translateNamespaces(ResultSetMapping $rsm): void
{
$translate = fn ($alias): string => $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|null $profile): static
{
if ($profile === null) {
$this->hydrationCacheProfile = null;
return $this;
}
if (! $profile->getResultCache()) {
$defaultHydrationCacheImpl = $this->em->getConfiguration()->getHydrationCache();
if ($defaultHydrationCacheImpl) {
$profile = $profile->setResultCache($defaultHydrationCacheImpl);
}
}
$this->hydrationCacheProfile = $profile;
return $this;
}
public function getHydrationCacheProfile(): QueryCacheProfile|null
{
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|null $profile): static
{
if ($profile === null) {
$this->queryCacheProfile = null;
return $this;
}
if (! $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.
*/
public function setResultCache(CacheItemPoolInterface|null $resultCache): static
{
if ($resultCache === null) {
if ($this->queryCacheProfile) {
$this->queryCacheProfile = new QueryCacheProfile($this->queryCacheProfile->getLifetime(), $this->queryCacheProfile->getCacheKey());
}
return $this;
}
$this->queryCacheProfile = $this->queryCacheProfile
? $this->queryCacheProfile->setResultCache($resultCache)
: new QueryCacheProfile(0, null, $resultCache);
return $this;
}
/**
* 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|null $lifetime = null, string|null $resultCacheId = null): static
{
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
return $this;
}
/**
* Disables caching of the results of this query.
*
* @return $this
*/
public function disableResultCache(): static
{
$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(int|null $lifetime): static
{
$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;
}
$this->queryCacheProfile = $this->queryCacheProfile->setResultCache($cache);
return $this;
}
/**
* 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(bool $expire = true): static
{
$this->expireResultCache = $expire;
return $this;
}
/**
* Retrieves if the resultset cache is active or not.
*/
public function getExpireResultCache(): bool
{
return $this->expireResultCache;
}
public function getQueryCacheProfile(): QueryCacheProfile|null
{
return $this->queryCacheProfile;
}
/**
* Change the default fetch mode of an association for this query.
*
* @param class-string $class
* @phpstan-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode
*/
public function setFetchMode(string $class, string $assocName, int $fetchMode): static
{
$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.
* @phpstan-param string|AbstractQuery::HYDRATE_* $hydrationMode
*
* @return $this
*/
public function setHydrationMode(string|int $hydrationMode): static
{
$this->hydrationMode = $hydrationMode;
return $this;
}
/**
* Gets the hydration mode currently used by the query.
*
* @phpstan-return string|AbstractQuery::HYDRATE_*
*/
public function getHydrationMode(): string|int
{
return $this->hydrationMode;
}
/**
* Gets the list of results for the query.
*
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
*
* @phpstan-param string|AbstractQuery::HYDRATE_* $hydrationMode
*/
public function getResult(string|int $hydrationMode = self::HYDRATE_OBJECT): mixed
{
return $this->execute(null, $hydrationMode);
}
/**
* Gets the array of results for the query.
*
* Alias for execute(null, HYDRATE_ARRAY).
*
* @return mixed[]
*/
public function getArrayResult(): array
{
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(): array
{
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(): array
{
return $this->execute(null, self::HYDRATE_SCALAR);
}
/**
* Get exactly one result or null.
*
* @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @throws NonUniqueResultException
*/
public function getOneOrNullResult(string|int|null $hydrationMode = null): mixed
{
try {
$result = $this->execute(null, $hydrationMode);
} catch (NoResultException) {
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.
*
* @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult(string|int|null $hydrationMode = null): mixed
{
$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 bool|float|int|string|null The scalar result.
*
* @throws NoResultException If the query returned no result.
* @throws NonUniqueResultException If the query result is not unique.
*/
public function getSingleScalarResult(): mixed
{
return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
}
/**
* Sets a query hint. If the hint name is not recognized, it is silently ignored.
*
* @return $this
*/
public function setHint(string $name, mixed $value): static
{
$this->hints[$name] = $value;
return $this;
}
/**
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
*
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
*/
public function getHint(string $name): mixed
{
return $this->hints[$name] ?? false;
}
public function hasHint(string $name): bool
{
return isset($this->hints[$name]);
}
/**
* Return the key value map of query hints that are currently set.
*
* @return array
*/
public function getHints(): array
{
return $this->hints;
}
/**
* Executes the query and returns an iterable that can be used to incrementally
* iterate over the result.
*
* @phpstan-param ArrayCollection|mixed[] $parameters
* @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return iterable
*/
public function toIterable(
ArrayCollection|array $parameters = [],
string|int|null $hydrationMode = null,
): iterable {
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
}
if (count($parameters) !== 0) {
$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)->toIterable($stmt, $rsm, $this->hints);
}
/**
* Executes the query.
*
* @phpstan-param ArrayCollection|mixed[]|null $parameters
* @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*/
public function execute(
ArrayCollection|array|null $parameters = null,
string|int|null $hydrationMode = null,
): mixed {
if ($this->cacheable && $this->isCacheEnabled()) {
return $this->executeUsingQueryCache($parameters, $hydrationMode);
}
return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
}
/**
* Execute query ignoring second level cache.
*
* @phpstan-param ArrayCollection|mixed[]|null $parameters
* @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*/
private function executeIgnoreQueryCache(
ArrayCollection|array|null $parameters = null,
string|int|null $hydrationMode = null,
): mixed {
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);
$cache = $this->hydrationCacheProfile->getResultCache();
assert($cache !== null);
return $cache;
}
/**
* Load from second level cache or executes the query and put into cache.
*
* @phpstan-param ArrayCollection|mixed[]|null $parameters
* @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*/
private function executeUsingQueryCache(
ArrayCollection|array|null $parameters = null,
string|int|null $hydrationMode = null,
): mixed {
$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|null
{
assert($this->resultSetMapping !== null);
$entityName = reset($this->resultSetMapping->aliasMap);
if (empty($entityName)) {
return null;
}
$metadata = $this->em->getClassMetadata($entityName);
return new 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)
* @phpstan-return array{string, string} ($key, $hash)
*/
protected function getHydrationCacheId(): array
{
$parameters = [];
$types = [];
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
$types[$parameter->getName()] = $parameter->getType();
}
$sql = $this->getSQL();
assert(is_string($sql));
$queryCacheProfile = $this->getHydrationCacheProfile();
$hints = $this->getHints();
$hints['hydrationMode'] = $this->getHydrationMode();
ksort($hints);
assert($queryCacheProfile !== null);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints);
}
/**
* Set the result cache id to use to store the result set cache entry.
* If this is not explicitly set by the developer then a hash is automatically
* generated for you.
*/
public function setResultCacheId(string|null $id): static
{
if (! $this->queryCacheProfile) {
return $this->setResultCacheProfile(new QueryCacheProfile(0, $id));
}
$this->queryCacheProfile = $this->queryCacheProfile->setCacheKey($id);
return $this;
}
/**
* Executes the query and returns 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(): Result|int;
/**
* Cleanup Query resource when clone is called.
*/
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.
*/
protected function getHash(): string
{
$query = $this->getSQL();
assert(is_string($query));
$hints = $this->getHints();
$params = array_map(function (Parameter $parameter) {
$value = $parameter->getValue();
// Small optimization
// Does not invoke processParameterValue for scalar value
if (is_scalar($value)) {
return $value;
}
return $this->processParameterValue($value);
}, $this->parameters->getValues());
ksort($hints);
return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
}
}
================================================
FILE: src/Cache/AssociationCacheEntry.php
================================================
$identifier The entity identifier.
* @param class-string $class The entity class name
*/
public function __construct(
public readonly string $class,
public readonly array $identifier,
) {
}
/**
* Creates a new AssociationCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*/
public static function __set_state(array $values): self
{
return new self($values['class'], $values['identifier']);
}
}
================================================
FILE: src/Cache/CacheConfiguration.php
================================================
cacheFactory;
}
public function setCacheFactory(CacheFactory $factory): void
{
$this->cacheFactory = $factory;
}
public function getCacheLogger(): CacheLogger|null
{
return $this->cacheLogger;
}
public function setCacheLogger(CacheLogger $logger): void
{
$this->cacheLogger = $logger;
}
public function getRegionsConfiguration(): RegionsConfiguration
{
return $this->regionsConfig ??= new RegionsConfiguration();
}
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig): void
{
$this->regionsConfig = $regionsConfig;
}
public function getQueryValidator(): QueryCacheValidator
{
return $this->queryValidator ??= new TimestampQueryCacheValidator(
$this->cacheFactory->getTimestampRegion(),
);
}
public function setQueryValidator(QueryCacheValidator $validator): void
{
$this->queryValidator = $validator;
}
}
================================================
FILE: src/Cache/CacheEntry.php
================================================
IMPORTANT NOTE:
*
* Fields of classes that implement CacheEntry are public for performance reason.
*/
interface CacheEntry
{
}
================================================
FILE: src/Cache/CacheException.php
================================================
$cache The cache configuration.
*/
public function getRegion(array $cache): Region;
/**
* Build timestamp cache region
*/
public function getTimestampRegion(): TimestampRegion;
/**
* Build \Doctrine\ORM\Cache
*/
public function createCache(EntityManagerInterface $entityManager): Cache;
}
================================================
FILE: src/Cache/CacheKey.php
================================================
$values array containing property values
*/
public static function __set_state(array $values): CollectionCacheEntry
{
return new self($values['identifiers']);
}
}
================================================
FILE: src/Cache/CollectionCacheKey.php
================================================
*/
public readonly array $ownerIdentifier;
/**
* @param class-string $entityClass The owner entity class.
* @param array $ownerIdentifier The identifier of the owning entity.
*/
public function __construct(
public readonly string $entityClass,
public readonly string $association,
array $ownerIdentifier,
string $filterHash = '',
) {
ksort($ownerIdentifier);
$this->ownerIdentifier = $ownerIdentifier;
$filterHash = $filterHash === '' ? '' : '_' . $filterHash;
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association . $filterHash);
}
}
================================================
FILE: src/Cache/CollectionHydrator.php
================================================
*/
private array $queryCaches = [];
private QueryCache|null $defaultQueryCache = null;
public function __construct(
private readonly EntityManagerInterface $em,
) {
$this->uow = $em->getUnitOfWork();
$this->cacheFactory = $em->getConfiguration()
->getSecondLevelCacheConfiguration()
->getCacheFactory();
}
public function getEntityCacheRegion(string $className): Region|null
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if (! ($persister instanceof CachedPersister)) {
return null;
}
return $persister->getCacheRegion();
}
public function getCollectionCacheRegion(string $className, string $association): Region|null
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if (! ($persister instanceof CachedPersister)) {
return null;
}
return $persister->getCacheRegion();
}
public function containsEntity(string $className, mixed $identifier): bool
{
$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));
}
public function evictEntity(string $className, mixed $identifier): void
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if (! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
}
public function evictEntityRegion(string $className): void
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if (! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evictAll();
}
public function evictEntityRegions(): void
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if (! ($persister instanceof CachedPersister)) {
continue;
}
$persister->getCacheRegion()->evictAll();
}
}
public function containsCollection(string $className, string $association, mixed $ownerIdentifier): bool
{
$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));
}
public function evictCollection(string $className, string $association, mixed $ownerIdentifier): void
{
$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));
}
public function evictCollectionRegion(string $className, string $association): void
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if (! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evictAll();
}
public function evictCollectionRegions(): void
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
foreach ($metadata->associationMappings as $association) {
if (! $association->isToMany()) {
continue;
}
$persister = $this->uow->getCollectionPersister($association);
if (! ($persister instanceof CachedPersister)) {
continue;
}
$persister->getCacheRegion()->evictAll();
}
}
}
public function containsQuery(string $regionName): bool
{
return isset($this->queryCaches[$regionName]);
}
public function evictQueryRegion(string|null $regionName = null): void
{
if ($regionName === null && $this->defaultQueryCache !== null) {
$this->defaultQueryCache->clear();
return;
}
if (isset($this->queryCaches[$regionName])) {
$this->queryCaches[$regionName]->clear();
}
}
public function evictQueryRegions(): void
{
$this->getQueryCache()->clear();
foreach ($this->queryCaches as $queryCache) {
$queryCache->clear();
}
}
public function getQueryCache(string|null $regionName = null): QueryCache
{
if ($regionName === null) {
return $this->defaultQueryCache ??= $this->cacheFactory->buildQueryCache($this->em);
}
return $this->queryCaches[$regionName] ??= $this->cacheFactory->buildQueryCache($this->em, $regionName);
}
private function buildEntityCacheKey(ClassMetadata $metadata, mixed $identifier): EntityCacheKey
{
if (! is_array($identifier)) {
$identifier = $this->toIdentifierArray($metadata, $identifier);
}
return new EntityCacheKey($metadata->rootEntityName, $identifier);
}
private function buildCollectionCacheKey(
ClassMetadata $metadata,
string $association,
mixed $ownerIdentifier,
): CollectionCacheKey {
if (! is_array($ownerIdentifier)) {
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
}
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
}
/** @return array