fix(DynamicContentSubscriber): port Mautic 5 migration to Mautic 6#2
fix(DynamicContentSubscriber): port Mautic 5 migration to Mautic 6#2edouard-mangel wants to merge 17 commits into6.xfrom
Conversation
…evaluation The 5.x branch left DynamicContentSubscriber in the old Mautic 4 state: traits (MatchFilterForLeadTrait, DbalQueryTrait) that no longer exist in mautic/core-lib ^5.0, and a QueryBuilder-based filter evaluation path. Changes: - Remove MatchFilterForLeadTrait and DbalQueryTrait (removed from core in 5.x) - Remove QueryFilterHelper and LoggerInterface from constructor - Wire ContactFilterMatcher as the delegate in evaluateFilters() - Simplify hasCustomObjectFilters() — returns true on first non-throwing filter - Rewrite DynamicContentSubscriberTest: correct collaborators, 6 tests / 21 assertions - Add docs/adr/0001-mautic5-migration-strategy.md Verified on Mautic 5.2.9 / Symfony 5.4 / PHP 8.3 via DDEV: - cache:clear succeeds - mautic:plugins:reload installs the plugin (13 DB tables created) - Custom Objects UI fully functional at /s/custom/object/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ill trait directly The class_alias trick (selecting between Mautic core MatchFilterForLeadTrait and the polyfill at runtime) is not statically analysable by PHPStan, causing 8 false-positive errors. Since this branch targets Mautic 5 where the core trait no longer has transformFilterDataForLead(), the polyfill branch always executes — making the class_alias logic dead code. - Remove class_alias block and Mautic\EmailBundle\EventListener\MatchFilterForLeadTrait import - Use MatchFilterForLeadTraitPolyfill directly with a named alias - Add 2 legitimate PHPStan baseline entries (MAUTIC_TABLE_PREFIX global constant and transformFilterDataForLead trait-override detection limitation) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DynamicContentSubscriber and ContactFilterMatcher are already discovered by Config/services.php via ->load(). Add ->bind() for the scalar $leadCustomItemFetchLimit parameter so autowiring can resolve it without explicit config.php entries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers: no CO filters, NotFoundException/InvalidCustomObjectFormatListException handling, no linked items, name match/mismatch, multi-item match, custom item caching per object+lead, company enrichment, tag enrichment.
…ems linked When a contact has no linked custom items, the lead value array is empty and the foreach never executes, leaving the group result as false. For the 'empty' operator this is semantically wrong — no items means the field IS empty, so the condition should match.
…ing in services.php The #[Autowire] attribute is not reliably processed in Mautic 5.1 environments, causing a container compilation failure. Use a specific service definition with ->arg() in services.php instead — this is scoped to ContactFilterMatcher only and works across all supported Mautic/Symfony versions.
… non-capturing catches)
…ataForLead methods
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Ports the Dynamic Content subscriber/filter-matching integration from Mautic 5 to Mautic 6 by switching to the new evaluation event, refactoring ContactFilterMatcher to rely on the polyfill trait, and updating CI/static-analysis/test coverage accordingly.
Changes:
- Migrate dynamic content filter evaluation to Mautic 6 APIs (
ContactFiltersEvaluateEvent) and adjust exception handling. - Refactor
ContactFilterMatcherto removeclass_alias, use constructor promotion, and update DBAL calls (executeQuery()). - Add new unit tests, update service wiring for autowiring/binding, and extend PHPStan baseline + CI matrix for Mautic 6 / PHP 8.2.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
EventListener/DynamicContentSubscriber.php |
Update to Mautic 6 event API and modernize catch blocks. |
Helper/ContactFilterMatcher.php |
Refactor trait usage/constructor promotion; update DBAL query execution; custom-object filter integration. |
Polyfill/EventListener/MatchFilterForLeadTrait.php |
Add empty-linked-items handling and modern string helpers in filter matching. |
Config/services.php |
Rely on autowiring; bind the fetch-limit constructor arg. |
Tests/Unit/Helper/ContactFilterMatcherTest.php |
Add unit coverage for matcher behavior, caching, company/tags data fetching. |
.github/workflows/tests.yml |
Move CI to PHP 8.2 and Mautic 6.x. |
phpstan-baseline.neon |
Add new suppressions for the refactor/polyfill changes. |
Config/config.php |
Minor formatting-only change. |
Comments suppressed due to low confidence (1)
Polyfill/EventListener/MatchFilterForLeadTrait.php:201
transformFilterDataForLead()currently returnsnullunconditionally while its signature advertises?array. This mismatch is also reflected by the new PHPStan baseline suppressions. If this method is no longer needed, remove it; otherwise, implement the actual transformation (e.g., by aliasing/delegating to Mautic’sMatchFilterForLeadTrait::transformFilterDataForLead()) and add a precise phpdoc like@return array<mixed>|nullto avoid baseline growth.
/**
* @param mixed[] $data
* @param mixed[] $lead
*/
private function transformFilterDataForLead(array $data, array $lead): ?array
{
return null;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| /** | ||
| * @param mixed[] $data | ||
| * @param mixed[] $lead | ||
| * | ||
| * @return ?mixed[] | ||
| */ | ||
| private function transformFilterDataForLead(array $data, array $lead): ?array | ||
| { | ||
| if ('custom_object' === $data['object']) { | ||
| return $lead[$data['field']]; | ||
| } |
There was a problem hiding this comment.
transformFilterDataForLead() is private and (per the added PHPStan baseline) currently considered unused. If matchFilterForLead() no longer calls into this hook, consider removing this method to reduce dead code and avoid needing a baseline entry for it.
|
|
||
| return $this->transformFilterDataForLeadAlias($data, $lead); | ||
| return $this->transformFilterDataForLeadPolyfill($data, $lead); | ||
| } |
There was a problem hiding this comment.
transformFilterDataForLead() delegates to the aliased polyfill method, but the polyfill transformFilterDataForLead() currently returns null unconditionally. Since this method is also marked unused by PHPStan, consider removing the alias + this wrapper entirely, or implementing a real delegation to Mautic’s trait so the method is correct if it becomes used again.
Config/services.php
Outdated
| $services->set(ContactFilterMatcher::class) | ||
| ->arg('$leadCustomItemFetchLimit', '%mautic.custom_item_fetch_limit_per_lead%'); |
There was a problem hiding this comment.
The default bind('int $leadCustomItemFetchLimit', ...) already supplies the constructor argument for autowired services, and the explicit set(ContactFilterMatcher::class)->arg(...) repeats the same configuration. Consider keeping just one of these (preferably the default bind) to avoid config drift if the parameter name/type changes later.
| $services->set(ContactFilterMatcher::class) | |
| ->arg('$leadCustomItemFetchLimit', '%mautic.custom_item_fetch_limit_per_lead%'); |
| $this->buildLeadFilter('companycountry', '=', 'US'), | ||
| ]; | ||
|
|
||
| $this->matcher->match($filters, ['id' => 42]); |
There was a problem hiding this comment.
testMatchFetchesCompanyDataWhenFilterFieldStartsWithCompany() only asserts that the repository call happens, but it doesn’t assert the overall match result for a company-prefixed filter. Adding an assertion that the company filter actually influences the return value would better protect against regressions (especially since company filter evaluation is easy to accidentally skip when relying on flattened vs companies data).
| $this->matcher->match($filters, ['id' => 42]); | |
| $result = $this->matcher->match($filters, ['id' => 42]); | |
| $this->assertFalse($result); |
| use MatchFilterForLeadTraitPolyfill { | ||
| transformFilterDataForLead as transformFilterDataForLeadPolyfill; | ||
| } |
There was a problem hiding this comment.
ContactFilterMatcher now always uses the polyfill MatchFilterForLeadTrait. That trait’s matchFilterForLead() currently reads $lead[$data['field']] directly and does not appear to use the fetched $lead['companies'] data, so segment filters like companycountry may be silently skipped even though match() eagerly loads companies. Consider delegating to Mautic’s native matchFilterForLead() / transformFilterDataForLead() (or implementing equivalent company-field extraction) so company-prefixed filters are actually evaluated.
| if (is_array($leadValues)) { | ||
| if ('custom_object' === $data['object'] && [] === $leadValues) { | ||
| // No custom items linked to this contact: only 'empty' is true. | ||
| $groups[$groupNum] = 'empty' === $data['operator']; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
In matchFilterForLead(), $leadValues is taken directly from $lead[$data['field']], so filters that rely on derived data (notably company-prefixed fields that typically come from $lead['companies']) won’t match unless the flattened field keys already exist on $lead. Since ContactFilterMatcher fetches companies into $lead['companies'], consider using/adding a transformFilterDataForLead() step (or delegating to Mautic’s original trait implementation) so company filters are evaluated correctly.
Symfony 6.4 is strict about unused _defaults bindings. Replace the ->bind()/_defaults approach with #[Autowire(param:...)] on the ContactFilterMatcher constructor, which is fully supported on Symfony 6.4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bulk fix of trailing_comma_in_multiline, new_with_parentheses, fully_qualified_strict_types, ordered_imports, and phpdoc_separation violations across the codebase. These are pre-existing issues exposed when testing against Mautic 6's php-cs-fixer ruleset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… in PHPStan 2.0) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Port of PR fix/dynamic-content-subscriber-mautic5 to Mautic 6.
DynamicContentSubscribermigration for Mautic 6 (usesContactFiltersEvaluateEvent,ContactFilterMatcher, and the polyfill trait)ContactFilterMatcherto removeclass_aliasand use the polyfill trait directlyconfig.php, relying on autowiring viaservices.phpexecute()withexecuteQuery()MatchFilterForLeadTraitwhen no custom items are linkedContactFilterMatcherandDynamicContentSubscriberstr_starts_with, non-capturing catches)Conflict resolution
One cherry-pick conflict in
DynamicContentSubscriberTest.php: the 5.x branch used->will($this->onConsecutiveCalls(...))(older PHPUnit API) while 6.x already used->willReturnOnConsecutiveCalls(...)(PHPUnit 10+ API). Kept the 6.x version.CI
Updated workflow to test against PHP 8.2 and Mautic
6.xbranch (previously targeting PHP 8.0 / Mautic 5.1).Test plan
🤖 Generated with Claude Code