ahmed-bhs/doctrine-doctor
Doctrine Doctor is a runtime analysis tool for Doctrine ORM integrated into the Symfony Web Profiler. It detects real-world issues like N+1 queries, slow queries, missing indexes, hydration overhead, and injection risks, with actionable backtraces and suggestions.
OrderByNullableLeadingColumnAnalyzer (Performance/Integrity): flags ORDER BY on a nullable leading column combined with LIMIT. NULL placement (first or last) is platform- and configuration-dependent, so a query like ORDER BY requested_at LIMIT 1 can silently skip rows whose sort column is NULL depending on the database engine. Info severity, flag-only, never auto-fixed since NULL-first/last is sometimes intentional. Configurable via doctrine_doctor.analyzers.order_by_nullable_leading_column.enabled.FlushInLoopAnalyzer / FlushInLoopAnalyzerModern: both were tagged doctrine_doctor.analyzer via the same glob registration, producing duplicate/conflicting findings for the same flush-in-loop pattern. FlushInLoopAnalyzerModern is now excluded from the tag glob while staying registered and autowireable.GetReferenceAnalyzer: no longer flags Doctrine's own optimistic-lock version-check re-reads (SELECT version FROM table WHERE id = ?) or PHP 8.4 native lazy-ghost object initialization as find()-instead-of-getReference() candidates. Added ProxyFactory::createLazyInitializer / EntityPersister::loadById to the lazy-loading backtrace markers alongside the legacy Proxy::__load / __CG__:: ones.DoctrineDoctorDataCollector: expensive runAnalysis() work now runs in lateCollect() (after the HTTP response is sent) instead of collect() (before), on runtimes where this is safe. Auto-detected via function_exists('fastcgi_finish_request') so it works correctly on both php-fpm and persistent/worker-mode runtimes (FrankenPHP/RoadRunner/Swoole). No config flag needed.CollectionJoinDetector::isForeignKeyInJoinedTable(): compared SQL column names against getIdentifierFieldNames() (PHP field paths) instead of getIdentifierColumnNames() (actual DB column names). Broke for entities with embedded value-object identifiers, causing ManyToOne joins to fall through to an overly-broad table-wide fallback and get misclassified as collection joins.UnusedEagerLoadAnalyzer: carried its own copy of the now-fixed CollectionJoinDetector join-classification logic instead of reusing the shared helper, so it suffered the same value-object-identifier misclassification independently. Deduplicated to delegate to CollectionJoinDetector.DeepOffsetPaginationAnalyzer (Performance): detects deep OFFSET pagination in executed SQL — OFFSET >= 1000 is flagged as a warning, OFFSET >= 10000 as critical. Cost grows linearly with page depth because the database must read and discard every skipped row. Suggests keyset (seek) pagination via WHERE id > :lastId ORDER BY id LIMIT N or an application-level cap on the maximum offset. Handles both LIMIT n OFFSET m and MySQL LIMIT m, n forms. Thresholds are configurable via doctrine_doctor.analyzers.deep_offset_pagination.offset_warning_threshold and offset_critical_threshold.PaginationWithoutOrderByAnalyzer (Performance): detects LIMIT/OFFSET pagination without an ORDER BY clause. SQL does not guarantee row order without an explicit ORDER BY, so identical executions can return different rows and consecutive pages can return duplicates or skip rows. Suggests adding a deterministic ORDER BY on a stable, indexed column (typically the primary key) and a PK tiebreaker when sorting by a non-unique column. Skips LIMIT 1 single-row fetches where ordering is irrelevant.FunctionOnPredicateColumnAnalyzer (Performance): detects non-sargable functions wrapping a column in WHERE (LOWER, UPPER, COALESCE, IFNULL, ISNULL, NULLIF, CAST, CONVERT, TRIM, LTRIM, RTRIM, SUBSTRING, SUBSTR, CONCAT, ABS, ROUND, FLOOR, CEIL, CEILING). Wrapping a filtered column in a function defeats any standard index on that column and forces a full scan with per-row evaluation. Suggests rewriting the predicate so the column appears bare on one side, normalizing values at write time, or creating a functional/expression index. Date functions remain handled by YearFunctionOptimizationAnalyzer. Threshold configurable via doctrine_doctor.analyzers.function_on_predicate_column.min_execution_time_ms (default 10ms).NotInSubqueryAnalyzer (Performance): detects <column> NOT IN (SELECT ...) patterns that silently return zero rows whenever the subquery yields any NULL value, due to SQL three-valued logic (x NOT IN (a, b, NULL) evaluates to UNKNOWN, never TRUE). A frequent source of bugs that pass in tests but break in production. Suggests NOT EXISTS, LEFT JOIN ... IS NULL, or an explicit IS NOT NULL filter inside the subquery.ImplicitTypeConversionAnalyzer (Performance): detects predicates likely to trigger implicit type conversion in the engine — numeric columns compared to quoted string literals (e.g. user_id = '42') or date/time columns compared to bare integer literals. Implicit conversion typically disables index usage on the column. Heuristics rely on column-name suffixes (_id, _count, _amount, _at, _date, _time, ...) and skip placeholders (?, :param) since the bound type is invisible from SQL text alone. Suggests binding parameters with the correct PHP type or explicit DBAL Types::*.SQLInjectionInRawQueriesAnalyzer: extended to detect tautology variants (OR 1=1, OR '1'='1', OR TRUE) and UNION SELECT injection patterns; the LIMIT/OFFSET vector is now also analyzed for concatenated injection attempts that previously slipped past the WHERE-only scan.doctrine/orm installed. A new RemoveOrmServicesPass compiler pass removes ORM-dependent services (the EntityManager decorator, EntityMetadataProvider, and every analyzer with an EntityManagerInterface dependency) when doctrine.orm.entity_manager is absent, so the container compiles cleanly and only DBAL-native analyzers stay active.NPlusOneSqlAnalyzer: pure-SQL N+1 detection that groups identical SELECT patterns from QueryDataCollection and flags any group above the threshold (default 3), without needing ORM metadata. Required so DBAL-only applications get N+1 coverage equivalent to the existing ORM-aware analyzer.MissingTransactionOnBatchAnalyzer: walks the query timeline tracking transaction state via START/BEGIN/SAVEPOINT and COMMIT/ROLLBACK/RELEASE SAVEPOINT markers, and flags N >= threshold (default 10) INSERT/UPDATE/DELETE statements executed outside any transaction. Wrapping them in a single transaction collapses N fsyncs into one for a 10-100x speedup on durable storage.YearFunctionOptimizationAnalyzer: now also detects SQLite strftime('%Y'|'%m'|'%d'|'%H'|'%M'|'%S', col) and standard SQL EXTRACT(part FROM col) patterns in WHERE clauses, mapping them back to the existing YEAR/MONTH/... reasoning. Previously the analyzer was MySQL-only.doctrine_doctor: added a profiler collector formatter, an MCP tool (doctrine-doctor-issues), and a sanitization layer for issue hints, traces, and SQL snippets so profiler findings can be consumed safely by AI agents when symfony/ai-symfony-mate-extension is installed in the host Symfony application.QueryCachingOpportunityAnalyzer: added Doctrine 2LC Opportunity detection for repeated fast SELECT entity-load patterns with varying parameter sets, plus a dedicated suggestion template and configuration thresholds for second-level cache candidates.doctrine/orm moved from require to require-dev + suggest. Installing ahmed-bhs/doctrine-doctor no longer pulls doctrine/orm transitively. Projects that depended on this transitive install must add doctrine/orm to their own composer.json. ORM-specific analyzers (N+1 via metadata, eager-loading mapping, partial objects, etc.) are silently disabled when doctrine/orm is absent.RemoveOrmServicesPass: when the host application does not configure doctrine.orm.entity_manager, also removes PartialObjectAnalyzer, whose SELECT PARTIAL u.{...} recommendation is unusable in pure DBAL and surfaced as a false positive on SELECT * queries.
TransactionBoundaryAnalyzer: now recognizes Doctrine-quoted "START TRANSACTION" / "COMMIT" markers and treats SAVEPOINT/RELEASE SAVEPOINT as begin/commit so nested- and unclosed-transaction detection works in DBAL applications that call $conn->beginTransaction() directly.
EntityManagerClearAnalyzer: now inspects query backtraces and only flags sequential INSERT/UPDATE/DELETE operations when at least one frame originates from the Doctrine ORM (EntityManager, UnitOfWork, EntityRepository, ServiceEntityRepository). Pure-DBAL batches no longer get a misleading EntityManager::clear() recommendation.
JoinTypeConsistencyAnalyzer / UnusedEagerLoadAnalyzer: only fire on aggregations / many-JOIN queries when the FROM table is mapped in the ORM metadata. This silences JOIN type may cause incorrect results and Unused eager loading alerts on pure-DBAL queries over tables that are not ORM-managed.
SQLInjectionInRawQueriesAnalyzer / InjectionPatternDetector / QueryBuilderPatternDetector: whitelist short clean LIKE / WHERE literal values (<=64 chars, no SQL meta-tokens) so hardcoded filter values like WHERE country = 'FR' or LIKE '%abc%' are no longer reported as SQL injection. Real concatenation attacks (literals containing OR/AND/UNION/SELECT/DROP/--/;, or suspiciously long values) still trigger the issue.
FindAllAnalyzer: skips queries with GROUP BY/HAVING clauses and aggregate functions (COUNT, SUM, AVG, MIN, MAX) in the SELECT list - these are analytic queries by design, not unrestricted findAll() patterns.
DoctrineCacheAnalyzer: the YAML-based scan now detects missing metadata_cache_driver, query_cache_driver, and result_cache_driver keys inside an existing when@prod section, not only the explicit type: array case. Previously, a when@prod block that omitted these keys entirely was silently ignored, causing the critical performance issue (entity metadata reparsed on every request, -50 to -80%) to go unreported in dev. The absence of when@prod altogether is still not flagged to avoid false positives on projects using split config/packages/prod/ files.
Added missing_cache_production.php suggestion template used for the new "not configured" issues, distinct from array_cache_production.php which covers the explicit array cache case.
OrderByWithoutLimitAnalyzer: fast queries (below min_execution_time_ms) with a FK equality predicate in the WHERE clause (e.g. WHERE deposit_request_id = ?) are now silently skipped. These are aggregate-child collections whose size is bounded by the parent entity lifecycle, not by data volume. If the query ever degrades (execution time exceeds the threshold), the alert re-enables automatically.
FinalEntityAnalyzer: added early-return guard when enable_native_lazy_objects (PHP 8.4 ghost objects) is active. Ghost objects decorate rather than subclass the entity, so final classes are safe — the previous behavior produced false-positive CRITICAL issues on PHP 8.4 projects.
QueryData serialization: bound parameters flagged as sensitive (passwords, tokens, API keys, secrets, etc.) are now redacted before the QueryData DTO is serialized into the profiler payload, preventing credential leakage through the Symfony Web Profiler cache and any downstream MCP/AI integrations that consume profiler data.PhpTemplateRenderer: template names are now restricted to a strict allowlist regex, and the resolved template path is confined to the bundle's Template/Suggestions/ directory via realpath() comparison. Closes a path-traversal vector where a malicious template name (e.g. ../../etc/passwd) could escape the suggestions directory.DeepOffsetPaginationAnalyzer (Performance): detects deep OFFSET pagination in executed SQL — OFFSET >= 1000 is flagged as a warning, OFFSET >= 10000 as critical. Cost grows linearly with page depth because the database must read and discard every skipped row. Suggests keyset (seek) pagination via WHERE id > :lastId ORDER BY id LIMIT N or an application-level cap on the maximum offset. Handles both LIMIT n OFFSET m and MySQL LIMIT m, n forms. Thresholds are configurable via doctrine_doctor.analyzers.deep_offset_pagination.offset_warning_threshold and offset_critical_threshold.PaginationWithoutOrderByAnalyzer (Performance): detects LIMIT/OFFSET pagination without an ORDER BY clause. SQL does not guarantee row order without an explicit ORDER BY, so identical executions can return different rows and consecutive pages can return duplicates or skip rows. Suggests adding a deterministic ORDER BY on a stable, indexed column (typically the primary key) and a PK tiebreaker when sorting by a non-unique column. Skips LIMIT 1 single-row fetches where ordering is irrelevant.FunctionOnPredicateColumnAnalyzer (Performance): detects non-sargable functions wrapping a column in WHERE (LOWER, UPPER, COALESCE, IFNULL, ISNULL, NULLIF, CAST, CONVERT, TRIM, LTRIM, RTRIM, SUBSTRING, SUBSTR, CONCAT, ABS, ROUND, FLOOR, CEIL, CEILING). Wrapping a filtered column in a function defeats any standard index on that column and forces a full scan with per-row evaluation. Suggests rewriting the predicate so the column appears bare on one side, normalizing values at write time, or creating a functional/expression index. Date functions remain handled by YearFunctionOptimizationAnalyzer. Threshold configurable via doctrine_doctor.analyzers.function_on_predicate_column.min_execution_time_ms (default 10ms).NotInSubqueryAnalyzer (Performance): detects <column> NOT IN (SELECT ...) patterns that silently return zero rows whenever the subquery yields any NULL value, due to SQL three-valued logic (x NOT IN (a, b, NULL) evaluates to UNKNOWN, never TRUE). A frequent source of bugs that pass in tests but break in production. Suggests NOT EXISTS, LEFT JOIN ... IS NULL, or an explicit IS NOT NULL filter inside the subquery.ImplicitTypeConversionAnalyzer (Performance): detects predicates likely to trigger implicit type conversion in the engine — numeric columns compared to quoted string literals (e.g. user_id = '42') or date/time columns compared to bare integer literals. Implicit conversion typically disables index usage on the column. Heuristics rely on column-name suffixes (_id, _count, _amount, _at, _date, _time, ...) and skip placeholders (?, :param) since the bound type is invisible from SQL text alone. Suggests binding parameters with the correct PHP type or explicit DBAL Types::*.SQLInjectionInRawQueriesAnalyzer: extended to detect tautology variants (OR 1=1, OR '1'='1', OR TRUE) and UNION SELECT injection patterns; the LIMIT/OFFSET vector is now also analyzed for concatenated injection attempts that previously slipped past the WHERE-only scan.doctrine/orm installed. A new RemoveOrmServicesPass compiler pass removes ORM-dependent services (the EntityManager decorator, EntityMetadataProvider, and every analyzer with an EntityManagerInterface dependency) when doctrine.orm.entity_manager is absent, so the container compiles cleanly and only DBAL-native analyzers stay active.NPlusOneSqlAnalyzer: pure-SQL N+1 detection that groups identical SELECT patterns from QueryDataCollection and flags any group above the threshold (default 3), without needing ORM metadata. Required so DBAL-only applications get N+1 coverage equivalent to the existing ORM-aware analyzer.MissingTransactionOnBatchAnalyzer: walks the query timeline tracking transaction state via START/BEGIN/SAVEPOINT and COMMIT/ROLLBACK/RELEASE SAVEPOINT markers, and flags N >= threshold (default 10) INSERT/UPDATE/DELETE statements executed outside any transaction. Wrapping them in a single transaction collapses N fsyncs into one for a 10-100x speedup on durable storage.YearFunctionOptimizationAnalyzer: now also detects SQLite strftime('%Y'|'%m'|'%d'|'%H'|'%M'|'%S', col) and standard SQL EXTRACT(part FROM col) patterns in WHERE clauses, mapping them back to the existing YEAR/MONTH/... reasoning. Previously the analyzer was MySQL-only.doctrine_doctor: added a profiler collector formatter, an MCP tool (doctrine-doctor-issues), and a sanitization layer for issue hints, traces, and SQL snippets so profiler findings can be consumed safely by AI agents when symfony/ai-symfony-mate-extension is installed in the host Symfony application.QueryCachingOpportunityAnalyzer: added Doctrine 2LC Opportunity detection for repeated fast SELECT entity-load patterns with varying parameter sets, plus a dedicated suggestion template and configuration thresholds for second-level cache candidates.doctrine/orm moved from require to require-dev + suggest. Installing ahmed-bhs/doctrine-doctor no longer pulls doctrine/orm transitively. Projects that depended on this transitive install must add doctrine/orm to their own composer.json. ORM-specific analyzers (N+1 via metadata, eager-loading mapping, partial objects, etc.) are silently disabled when doctrine/orm is absent.RemoveOrmServicesPass: when the host application does not configure doctrine.orm.entity_manager, also removes PartialObjectAnalyzer, whose SELECT PARTIAL u.{...} recommendation is unusable in pure DBAL and surfaced as a false positive on SELECT * queries.
TransactionBoundaryAnalyzer: now recognizes Doctrine-quoted "START TRANSACTION" / "COMMIT" markers and treats SAVEPOINT/RELEASE SAVEPOINT as begin/commit so nested- and unclosed-transaction detection works in DBAL applications that call $conn->beginTransaction() directly.
EntityManagerClearAnalyzer: now inspects query backtraces and only flags sequential INSERT/UPDATE/DELETE operations when at least one frame originates from the Doctrine ORM (EntityManager, UnitOfWork, EntityRepository, ServiceEntityRepository). Pure-DBAL batches no longer get a misleading EntityManager::clear() recommendation.
JoinTypeConsistencyAnalyzer / UnusedEagerLoadAnalyzer: only fire on aggregations / many-JOIN queries when the FROM table is mapped in the ORM metadata. This silences JOIN type may cause incorrect results and Unused eager loading alerts on pure-DBAL queries over tables that are not ORM-managed.
SQLInjectionInRawQueriesAnalyzer / InjectionPatternDetector / QueryBuilderPatternDetector: whitelist short clean LIKE / WHERE literal values (<=64 chars, no SQL meta-tokens) so hardcoded filter values like WHERE country = 'FR' or LIKE '%abc%' are no longer reported as SQL injection. Real concatenation attacks (literals containing OR/AND/UNION/SELECT/DROP/--/;, or suspiciously long values) still trigger the issue.
FindAllAnalyzer: skips queries with GROUP BY/HAVING clauses and aggregate functions (COUNT, SUM, AVG, MIN, MAX) in the SELECT list - these are analytic queries by design, not unrestricted findAll() patterns.
DoctrineCacheAnalyzer: the YAML-based scan now detects missing metadata_cache_driver, query_cache_driver, and result_cache_driver keys inside an existing when@prod section, not only the explicit type: array case. Previously, a when@prod block that omitted these keys entirely was silently ignored, causing the critical performance issue (entity metadata reparsed on every request, -50 to -80%) to go unreported in dev. The absence of when@prod altogether is still not flagged to avoid false positives on projects using split config/packages/prod/ files.
Added missing_cache_production.php suggestion template used for the new "not configured" issues, distinct from array_cache_production.php which covers the explicit array cache case.
OrderByWithoutLimitAnalyzer: fast queries (below min_execution_time_ms) with a FK equality predicate in the WHERE clause (e.g. WHERE deposit_request_id = ?) are now silently skipped. These are aggregate-child collections whose size is bounded by the parent entity lifecycle, not by data volume. If the query ever degrades (execution time exceeds the threshold), the alert re-enables automatically.
FinalEntityAnalyzer: added early-return guard when enable_native_lazy_objects (PHP 8.4 ghost objects) is active. Ghost objects decorate rather than subclass the entity, so final classes are safe — the previous behavior produced false-positive CRITICAL issues on PHP 8.4 projects.
QueryData serialization: bound parameters flagged as sensitive (passwords, tokens, API keys, secrets, etc.) are now redacted before the QueryData DTO is serialized into the profiler payload, preventing credential leakage through the Symfony Web Profiler cache and any downstream MCP/AI integrations that consume profiler data.PhpTemplateRenderer: template names are now restricted to a strict allowlist regex, and the resolved template path is confined to the bundle's Template/Suggestions/ directory via realpath() comparison. Closes a path-traversal vector where a malicious template name (e.g. ../../etc/passwd) could escape the suggestions directory.ColumnTypeAnalyzer: added configurable excluded_fields list (default: mimeType, contentType, mediaType, fileType) to prevent false enum opportunity alerts on MIME-type fields that match enum patterns but are not enums.OrderByWithoutLimitAnalyzer: added configurable min_execution_time_ms threshold (default: 10ms); array-result queries below the threshold are now flagged as info instead of warning, with a description warning that production data growth will degrade performance.OrderByWithoutLimitAnalyzer: improved suggestion template for bounded array-result queries (WHERE clause present) — now recommends adding an index on the ORDER BY column, adding setMaxResults, or suppressing the alert via config when the collection is guaranteed small.GetReferenceAnalyzer: removed wildcard *_id column patterns that caused false positives on FK columns (e.g. deposit_request_id). Detection is now restricted to strict id primary key columns only, since FK columns return collections and are not candidates for getReference().DoctrineDoctorDataCollector: bootstrap entry points (index.php, autoload_runtime.php, autoload.php) are now excluded when searching for the first application frame in exclude_paths filtering. Previously these files appeared at the bottom of every backtrace and short-circuited vendor exclusion for framework-internal queries (e.g. EasyAdmin entity loading).InheritanceStrategyAnalyzer family: detects invalid or risky inheritance mappings, including missing discriminator maps in STI, sparse STI tables, unsupported OneToMany associations on mapped superclasses, non-root #[InheritanceType] declarations, non-nullable subclass columns in STI, deep CTI hierarchies, and thin CTI subclasses.UniqueEntityWithoutDatabaseIndexAnalyzer: detects #[UniqueEntity] constraints that are not backed by a database UNIQUE index, including Symfony validation metadata declared with attributes, YAML, and XML.DenormalizedAggregateWithoutLockingAnalyzer: detects denormalized aggregate fields updated alongside collections without optimistic or pessimistic locking.ColumnTypeAnalyzer: flags mutable Doctrine date/time column types and suggests immutable equivalents to avoid silent state corruption.EagerLoadingMappingAnalyzer: detects associations declared with fetch: 'EAGER' in entity mapping and suggests deferring fetch strategy decisions to queries.GedmoExtensionPerformanceAnalyzer: detects entities using Gedmo Loggable or Translatable patterns that implicitly generate extra database queries.LazyGhostObjectsDisabledAnalyzer: detects Doctrine ORM configurations where enable_lazy_ghost_objects is not enabled on supported Symfony versions.ManyToManyWithExtraColumnsAnalyzer: detects ManyToMany join tables containing extra columns and recommends promoting them to an explicit join entity.MissingVersionFieldForConcurrencyAnalyzer: detects entities involved in concurrent write patterns without an #[ORM\Version] field for optimistic locking.FlushInEventListenerAnalyzer: detects flush() calls inside Doctrine lifecycle callbacks that can trigger re-entrant UnitOfWork computation or infinite loops.UniqueEntityWithoutDatabaseIndexAnalyzer now supports Symfony validation metadata declared in YAML and XML in addition to PHP attributes.HardcodedDatabaseCredentialsAnalyzer and UniqueEntityWithoutDatabaseIndexAnalyzer.MissingIndexAnalyzer: no longer reports a false positive when the relevant index is already used.LazyGhostObjectsDisabledAnalyzer.MetadataAnalyzerTrait and the split analyzer interface contract.OverprivilegedDatabaseUserAnalyzer: detects privileged, empty, or passwordless database users and suggests switching to a least-privilege account.HardcodedDatabaseCredentialsAnalyzer: detects database credentials embedded directly in DBAL configuration and suggests moving them to environment variables.NPlusOneAnalyzer: identifies repeated findBy()/findOneBy()-style lookups on non-key columns and suggests batching with IN queries or request-level caching.SensitiveDataExposureAnalyzer: now also flags public getters that expose sensitive entity fields without explicit protection.PropertyTypeMismatchAnalyzer: now attaches concrete fix suggestions for PHP/Doctrine type mismatches, including nullability mismatches.CollectionInitializationAnalyzer suggestion template now uses the actual mappedBy value when available.CollectionInitializationAnalyzer: now supports PHP constructor promotion when detecting collection initialization, fixing the false positive reported in issue #67.CollectionEmptyAccessAnalyzer in favor of the AST-based collection initialization analysis path.SQLInjectionInRawQueriesAnalyzer: now detects unparameterized literals in WHERE clauses of raw SQL queries as an injection risk, instead of only flagging active attack patterns.DQLInjectionAnalyzer: now detects Doctrine-generated SQL with concatenated literals and empty bound parameters, indicating unsafe DQL string concatenation.OneToOneInverseSideAnalyzer: detects bidirectional OneToOne mappedBy sides that silently force Doctrine to execute N+1 queries on every load, even when the relation is never accessed. Suggests flipping the owning side, going unidirectional, or using a fetch join.one_to_one_inverse_side analyzer.CompositeKeyComplexityAnalyzer: use ShortClassNameTrait, proper return types, and MappingHelper for Doctrine 2/3/4 compatibility.CompositeKeyComplexityAnalyzer: detects entities using composite primary keys that limit Doctrine ORM features (no getReference(), slower identity map, complex FK mappings). Severity: WARNING for 2 columns, CRITICAL for 3+ or when referenced by other entities.composite_key_complexity analyzer.OnDeleteCascadeMismatchAnalyzer now assigns CRITICAL severity for orm_cascade_db_setnull and orm_orphan_db_setnull mismatches (previously WARNING).on_delete_cascade_mismatch now render context-aware code snippets per mismatch type instead of a generic template.JoinColumnNonPrimaryKeyAnalyzer: detects associations where referencedColumnName points to a non-primary-key column, which causes incorrect lazy-loading proxy behavior.DuplicatePrivateFieldInHierarchyAnalyzer: detects private fields with the same name in an entity and its mapped parent classes, which triggers MappingException or unpredictable Collection filtering.join_column_non_primary_key, duplicate_private_field_in_hierarchy, overprivileged_database_user, and hardcoded_database_credentials analyzers.ORM\BatchFetch, fixed isVendorCode detection.Suggested Fix, Hide suggestion).issue-body background color to #fffefc for better visual consistency.doctrine_doctor.enabled now supports Symfony parameter placeholders (e.g. %kernel.debug%) by resolving the root enabled config before strict tree validation.%kernel.debug% placeholder handling in DI extension tests.isset.initializedProperty error: use ReflectionProperty::isInitialized() for readonly property check after unserialization.rel="noopener noreferrer" on external Doctrine documentation link (target="_blank")..alert-warning, .alert-danger, and .dd-suggestion-meta-intro blocks (less aggressive text contrast).IssueInterface and DeduplicatableIssueInterface.IssueReconstructor (#32)<pre><code> to prevent entity encodingwebmozart/assert constraint to support v2.xbitbag/coding-standard dependencydoctrine/doctrine-bundle ^3.0 (drop ^2.x)doctrine/orm ^3.0|^4.0 (drop ^2.x)webmozart/assert ^1.12PhpTemplateRenderer into IssueReconstructor so template rendering works after Symfony profiler deserializationSafeContext::offsetGet() now returns null for missing keys instead of throwing, enabling safe array destructuring in templates with optional context variablestrigger_location in eager_loading templateleft_join_with_not_null template (table_name -> entity)#[\Override], typed constants, array_find())ini_set('memory_limit') runtime manipulationdoctrine/doctrine-bundle ^2.x supportdoctrine/orm ^2.x supportHow can I help you explore Laravel packages today?