A Sylius 2.x plugin that integrates Mailchimp into the Sylius e-commerce platform. Built as a standard Symfony bundle following Sylius plugin conventions.
- Plugin namespace:
Webgriffe\SyliusMailchimpPlugin - Test namespace:
Tests\Webgriffe\SyliusMailchimpPlugin - Requires PHP 8.2+, Sylius ^2.0, Symfony ^7.4
vendor/bin/ecs check # coding standard (fix: add --fix)
vendor/bin/phpstan analyse -c phpstan.neon -l max src/
vendor/bin/psalmvendor/bin/phpunit # all PHPUnit tests
vendor/bin/phpunit --testsuite=unit # unit only
vendor/bin/phpunit --testsuite=integration
vendor/bin/phpunit --testsuite=non-unit # functional + integration
vendor/bin/phpunit --filter TestClassName # single test class
vendor/bin/phpunit tests/path/to/TestFile.php # single test file
vendor/bin/phpspec run
vendor/bin/behat --strict --tags="~@javascript&&~@mink:chromedriver" # non-JS Behat
vendor/bin/behat --strict --tags="@javascript,@mink:chromedriver" # JS Behatvendor/bin/console is a symlink created automatically by bin/create_console_symlink.php (runs post-install/update).
src/ Plugin source (loaded via WebgriffeSyliusMailchimpExtension)
DependencyInjection/ Bundle extension + configuration tree
Migrations/ Doctrine migrations (run after Sylius core migrations)
config/
services.php Auto-imports all files in config/services/*.php
config.yaml Imports all config/twig_hooks/**/*.yaml
services/ Individual service definition files (PHP)
twig_hooks/ Twig hooks YAML configs
tests/
TestApplication/ Full Symfony app used by PHPUnit and Behat
config/bundles.php Registers only this plugin bundle
config/config.yaml Imports @WebgriffeSyliusMailchimpPlugin/config/config.yaml
Unit/ PHPUnit unit tests
Integration/ PHPUnit integration tests
Functional/ PHPUnit functional tests
Behat/ Behat contexts, pages, and feature files
The WebgriffeSyliusMailchimpExtension extends AbstractResourceExtension (Sylius pattern) and uses PrependDoctrineMigrationsTrait to ensure plugin migrations run after Sylius core migrations.
Migrations are namespaced as DoctrineMigrations in src/Migrations/.
All code must be in English — class names, method names, variable names, comments, log messages, and exception messages. Italian is only acceptable in user-facing content (Twig templates, translation files, AI prompts intended to produce Italian output).
- All PHP files:
declare(strict_types=1);— no exceptions. - Classes default to
finalunless extension is explicitly needed. - Do not align assignment operators with extra spaces — ECS enforces this and will revert any vertical alignment automatically.
- Only add inline comments when the implementation logic is genuinely complex and requires explanation. Keep code self-documenting through clear naming.
- PHPStan at max level + Psalm at error level 1.
src/DependencyInjection/Configuration.phpis excluded from PHPStan (causes a crash). - Coding standard:
sylius-labs/coding-standardECS ruleset applied tosrc/,tests/Behat/,tests/Integration/, andecs.php.
Full testing guide:
docs/ai/testing-guide.md— read this before writing or reviewing tests.
- Unit (
tests/Unit/,PHPUnit\Framework\TestCase): class has ≤2 external dependencies, purely in-memory logic (mappers, value objects, simple event subscribers, remove-only handlers). - Integration (
tests/Integration/,KernelTestCase): use when the class is a message handler, command, or event listener with 3+ constructor deps; or when it interacts with the database (EntityManager, repositories); or when it calls an external API (useStubMailchimpClientfrom the container). - Mirror the source namespace structure in the test directory (e.g.,
src/Foo/Bar.php→tests/Unit/Foo/BarTest.php).
All test methods use snake_case with the test_ prefix: test_it_does_something_when_condition(): void.
Use Tests\Webgriffe\SyliusMailchimpPlugin\Unit\ReflectionIdTrait for setting auto-generated $id fields via reflection — do not duplicate setId() in every test class.
- Mock only services — i.e., collaborators of the class under test that are injected as dependencies (repositories, message bus, API clients, mapper interfaces, etc.).
- Never mock entities or models — always instantiate real objects and populate them via setters/adders. Use
ReflectionPropertyto set protected fields that have no public setter (e.g., auto-generated$id,$quantity). - The only acceptable exception is when a test specifically needs an object that does not implement a certain interface (e.g., testing a guard that skips non-
MailchimpAwareInterfacecustomers) — in that case a targeted mock is justified. - Concrete entity classes available for tests:
Tests\Webgriffe\SyliusMailchimpPlugin\Entity\Customer\CustomerTests\Webgriffe\SyliusMailchimpPlugin\Entity\Order\OrderTests\Webgriffe\SyliusMailchimpPlugin\Entity\Channel\ChannelSylius\Component\Core\Model\{Address, OrderItem, Product, ProductVariant, ProductTranslation, ChannelPricing}Sylius\Component\Order\Model\AdjustmentSylius\Component\Locale\Model\Locale
- Extend
Symfony\Bundle\FrameworkBundle\Test\KernelTestCase, callself::bootKernel()insetUp(). - Resolve services via
self::getContainer()->get(ServiceClass::class). - Use
Tests\Webgriffe\SyliusMailchimpPlugin\Stub\Mailchimp\StubMailchimpClient(already registered in the test container asMailchimpClientInterface) to assert Mailchimp API calls. - Create DB fixtures programmatically in PHP (no Alice — not installed); clean up in
tearDown().
tests/Stub/Mailchimp/StubMailchimpClient.php — tracks all calls to the Mailchimp client in a temp file (cross-process safe for Behat). Use $stub->reset() in setUp() and $stub->getUpsertCartCalls() / $stub->getUpsertOrderCalls() / etc. to assert calls.
Long-running Symfony commands use LockableTrait with an environment toggle:
if ($this->commandLockEnable && !$this->lock()) {
return Command::FAILURE;
}The $commandLockEnable flag is controlled via the app_command_lock_enable parameter defined in config/services.yaml.
- New services go in a dedicated PHP file under
config/services/. - New Twig hooks go in a dedicated PHP file under
config/twig_hooks/.
- CI also runs
roave/backward-compatibility-check— avoid breaking public API without a major version bump.