# Lukasz Bajsarowicz — Full Content for LLMs > Full markdown of all published posts plus profile data, intended for retrieval and citation. ## About Łukasz Bajsarowicz, CTO at FastWhiteCat S.A. (Warsaw, Poland). Magento / Adobe Commerce expert, Top 50 Magento contributor 2019, Magento Contribution Leader 2020, Adobe Commerce Architect since 2020. 9+ years in e-commerce development. ### Specialties - Magento, Adobe Commerce - WooCommerce, PrestaShop, Shopware, Sylius - Continuous Integration, Code Craftsmanship - Technical Leadership, E-commerce Architecture ### Certifications - Adobe Certified Master: Adobe Commerce Architect - Adobe Certified Expert: Business Practitioner, Developer, Front End Developer - Adobe Certified Professional: Adobe Commerce Developer ## Social - LinkedIn: https://pl.linkedin.com/in/lbajsarowicz - Twitter: https://x.com/LBajsarowicz - GitHub: https://github.com/lbajsarowicz ## Site Map - Home: https://lbajsarowicz.me/ - Blog: https://lbajsarowicz.me/blog/ - Book a Meeting: https://lbajsarowicz.me/calendar/ - Contact: https://lbajsarowicz.me/#contact --- # Articles ## The Atlassian Blind Spot: How a Compliance Checklist Led to an Open Source Provider - URL: https://lbajsarowicz.me/blog/the-atlassian-blind-spot/ - Published: 2026-04-04 - Category: DevOps - Tags: terraform, atlassian, jira, confluence, infrastructure-as-code, open-source, compliance - Description: We were preparing for SOC-2 and ISO-27001. The checklist asked simple questions. We didn't have answers for all of them. We started preparing for SOC-2 and ISO-27001 earlier this year. If you've been through this, you know the drill: you hire a consultant, they hand you a checklist, and you spend the next few weeks answering questions about your processes. When I joined the organization as a Consultant in September 2025 — and got promoted to CTO in March 2026 — the security posture was... rough. Shared accounts, no enforced 2FA, passwords that hadn't been rotated in years. The kind of setup where everyone knows the one Gmail password and nobody asks why. We spent months getting the basics right — individual accounts, MFA everywhere, secrets into a vault, infrastructure managed through code. By the time the auditor arrived, we'd addressed most of the obvious gaps. The checklist was going well. Then we hit the access management section. > "Can you demonstrate that user access to business-critical systems is reviewed periodically?" Sure. We had access reviews for AWS, for GitHub, for Google Workspace. All managed as code, all auditable. > "Including Jira and Confluence?" Pause. Jira held our project roadmaps, customer-facing delivery timelines, and internal HR boards. Confluence had architecture decision records, incident post-mortems, and financial planning documents. These weren't side tools — they were where the actual work happened. And nobody was managing their configuration as code. Nobody was reviewing who had access to what. Nobody had a process that would catch a stale account, an overly broad permission scheme, or a Confluence space quietly opened to "anyone with a link." We didn't have a breach. We didn't have an incident. We had something arguably worse for a compliance audit: **we had no process at all.** ## The spreadsheet that was outdated by Monday The first instinct was to audit manually. I opened a spreadsheet and started documenting our Jira setup. Projects, permission schemes, groups, roles, who can see what, who can do what. Four hours in, I had 200 rows. Atlassian's permission model is layers upon layers. A project has a permission scheme. That scheme has grants. The grants reference groups or roles. The roles have actors. Each layer can override the one above it. The only way to see the full picture is to click through every screen in the admin panel. I did it anyway. Three days later, I had 600+ rows documenting the state of our Jira instance as of that exact moment. By Monday, it was already outdated. Someone had created a new project with a custom permission scheme over the weekend. The spreadsheet wasn't a process — it was a snapshot. And snapshots don't pass SOC-2 audits. What I actually needed was the same thing we already had for infrastructure: a declared desired state, continuous comparison against reality, and alerts when the two diverge. I needed **Terraform for Atlassian**. ## The search that came up empty Terraform has providers for AWS, GCP, Azure, GitHub, Datadog, PagerDuty — basically every service with an API. Surely someone had built one for Atlassian. I searched. Here's what I found: | Provider | Status | Coverage | |----------|--------|----------| | `fourplusone/jira` | ★183 — Abandoned since 2023 | Issues, comments, groups — no permission schemes, no governance | | `surajrajput1024/atlassian` | ★4 — Single developer, no tests | Basic permission schemes only | | `atlassian/atlassian-operations` | Official — Active | JSM Operations and Compass — **not Jira, not Confluence** | The most mature provider was abandoned. The alternatives were incomplete. The official one was scoped to a different product entirely. The governance layer — the part that controls who can access what, the exact thing the auditor was asking about — was a blind spot across the entire ecosystem. ## Building what was missing Building a Terraform provider from scratch is not a weekend project. But the alternative was that spreadsheet. And the spreadsheet wasn't going to satisfy an auditor, let alone actually protect us. I made the repository public from day one. Not out of idealism — because knowing the code is visible forces you to write proper tests and documentation from the start. Same reason some people work out at the gym instead of at home. Witnesses keep you honest. The scope started small: three resource types to prove the full lifecycle — create, read, update, delete, import, drift detection — before scaling further. Those first three surfaced a dozen edge cases that would have compounded across a larger surface area. The decision that mattered most for compliance: treating the relationship between projects and their permission schemes as a first-class concept. In Jira, a project doesn't simply "have permissions." It's associated with a permission scheme, which contains grants, which reference groups and roles. If you want to answer "who has access to what?", you need to traverse the entire chain. Existing providers treated projects and schemes as separate islands. I connected them. ## What the Atlassian API is really like Atlassian's documentation and actual API behavior don't always agree. Some discoveries along the way: - **Incomplete responses** — creating a project returns only the ID and key. Nothing else from what you sent. - **Inconsistent types** — the same field comes back as a number in one endpoint and a string in another. Same resource, different API versions under the hood. - **Different pagination models** — Confluence and Jira use completely different approaches. Same company, same domain. - **Silent permission changes** — deleting a permission scheme that's assigned to a project makes Jira silently reassign it to the default scheme. No error, no warning. The project just loses its custom permissions. That last one is exactly the kind of silent drift that makes compliance people nervous — and exactly what Terraform's state comparison catches. ## What it covers today The provider reached **v0.1.0** with **23 resources** and **13 data sources**. | Area | What you can manage | |------|-------------------| | **Projects** | Projects, project roles, role actors | | **Permissions** | Permission schemes, grants, project-to-scheme associations | | **Issue configuration** | Issue types, issue type schemes, custom fields | | **Workflows** | Statuses, workflows, workflow schemes | | **Screens** | Screens, tabs, fields, screen schemes, issue type screen schemes | | **Confluence** | Spaces, space permissions | Every resource supports full CRUD, import, and drift detection. Tested daily against a real Jira instance. Signed releases published to the Terraform Registry. ## How we use it A dedicated repository contains the Terraform configuration for our entire Jira and Confluence setup. A pipeline runs daily: 1. **Plan** — compare the declared state against what's actually configured 2. **Alert** — if something changed outside of Terraform, the team gets notified 3. **Review** — a human either enforces the declared state, or updates the config to acknowledge an intentional change This gives us three things the spreadsheet never could: > **Continuous monitoring** — not a point-in-time audit, but a daily comparison. If a permission scheme changes at 2 AM, we see it at 8 AM. > **Change history** — every modification goes through a pull request. The answer to "who changed that?" is in git, not in an admin audit log nobody reads. > **Reproducibility** — the Jira configuration is code. New projects get the same setup, because it's defined once and applied consistently. ## The provider is open source **GitHub:** [github.com/lbajsarowicz/terraform-provider-atlassian](https://github.com/lbajsarowicz/terraform-provider-atlassian) **Terraform Registry:** `lbajsarowicz/atlassian` If any of these sound familiar, it was built for this: **You're going through SOC-2 or ISO-27001** and need to prove that access to Jira and Confluence is managed and reviewed. Define your permission schemes, groups, and project associations as code. Git history becomes your audit trail. **Your offboarding has gaps.** Manage Jira group membership through Terraform. When someone leaves, remove them from the config and apply. No checkboxes to forget. **You're scaling and projects are inconsistent.** Teams create projects with ad-hoc setups. Define standard configurations and apply them uniformly. **You want Confluence space governance.** Manage who can access which spaces as code — something no other Terraform provider supports. Getting started: ```hcl terraform { required_providers { atlassian = { source = "lbajsarowicz/atlassian" version = "~> 0.1" } } } provider "atlassian" { url = "https://yoursite.atlassian.net" user = "admin@yourcompany.com" token = var.atlassian_api_token } ``` Import what you already have: ```bash terraform import atlassian_jira_project.my_project PROJ terraform import atlassian_jira_permission_scheme.default 10001 terraform import atlassian_jira_group.developers developers ``` The provider tracks state, detects drift, and gives you visibility into a part of your stack that's probably been running on trust and good intentions. That worked fine for us too. Until the auditor asked a simple question we couldn't answer. --- ## Magento Cloud: Fastly dashboard in NewRelic - URL: https://lbajsarowicz.me/blog/magento-cloud-fastly-dashboard-in-newrelic/ - Published: 2025-03-26 - Category: Magento / Adobe Commerce - Tags: magento, adobe-commerce, fastly, newrelic, monitoring - Description: Monitoring your e-commerce infrastructure plays a key role in improving performance and service availability. While investigating excessive usage of CDN traffic. Learn step-by-step how to set up Fastly dashboard in your NewRelic. Information from Fastly presented in the NewRelic. Monitoring your e-commerce infrastructure plays a key role in improving performance and service availability. While investigating excessive usage of CDN traffic, I missed detailed information that is offered by competition _(such as Cloudflare)_. I followed the Fastly documentation on setting up a **Log Streaming from Fastly to NewRelic**. That did not work. I have submitted Support Ticket to Adobe. > _Log Streaming is a service offered by New Relic on their own. Supporting any 3rd party service falls out of scope of Adobe Support. Although, I would recommend you to generate new keys, and try again. If it still doesn't work, please get in touch with your development team, or with NewRelic's support team._ – Technical Support Engineer, Adobe Commerce **Adobe Commerce** gives you very limited access to **Fastly** and **NewRelic**. There is no access to Fastly dashboard, and even if they create a NewRelic account for you – it does not have enough permissions to create Ingress License Key (API Key required for log streaming). > _In the Adobe Commerce Cloud environment, direct access to the Fastly dashboard is not provided. Changes to the Fastly configuration will need to be done through the Adobe Commerce admin panel, or users can secure the API credentials and use the Fastly API for changes: _ – Technical Support Engineer, Adobe Commerce ## Recycling Adobe's data Magento Cloud employees claim they can't configure Log Streaming or give you sufficient permissions for you to configure it on your own. The truth is: Adobe Support already use log streaming for their internal use. You just need to listen to their data, instead of following Fastly documentation. 1. Log in to your Adobe Commerce admin panel, proceed to `Stores > Settings > Configuration` in the menu, then expand `Advanced` section to access `System` configuration. 2. In the `Full Page Cache` group, you'll find sub-group `Fastly Configuration`. Expand the `Tools` group to gain access to `Real-Time Log Streaming` sub-group. 3. Look closer at the **Endpoints** section. You should notice `mc_nr` is there. ![Fastly Endpoints Configuration](/images/blog/fastly-endpoints.png) Click on the ⚙️ cog next to this endpoint and review the `Log Format`: ![Fastly Log Format Configuration](/images/blog/fastly-log-format.png) Comparing that to the previously installed **Fastly Dashboard** in **NewRelic**, you should notice similarities: | Fastly Dashboard | mc_nr log format | | ------------------ | ---------------- | | fastly_datacenter | geo_datacenter | | fastly_region | geo_region | | resp_status | status | This way, you can map the expected attributes with their corresponding data keys. The problem starts with numeric values – for example `client_resp_ttfb` or `client_resp_body_size_write`. Adobe employees stream these as… `string`. Which makes it unable to aggregate data correctly. Fortunately, NewRelic predicted that beginners could make such a mistake in data mapping and they have introduced `numeric()` function, that converts strings into numbers (`float`). ## Download Fastly Dashboard for NewRelic You don't need to reinvent the wheel. You can [download Fastly dashboard](https://gist.github.com/lbajsarowicz/14b01bc7ed4de39bd942b77480cf6233) and install to your NewRelic. If you're not sure how to import JSON file as a Dashboard, follow the NewRelic documentation. --- ## Lowering TCO & Skyrocketing Conversions through Green Optimization - URL: https://lbajsarowicz.me/blog/eco-merce-economy-lowering-tco-skyrocketing-conversions-through-green-optimization/ - Published: 2024-02-25 - Category: Magento / Adobe Commerce - Tags: video, youtube, magento, sustainability, optimization, e-commerce - Description: Discover how green optimization strategies can significantly lower the TCO while dramatically improving conversion rates. This presentation from Meet Magento Florida explores sustainable practices that benefit both the environment and your bottom line. In today's competitive e-commerce landscape, sustainability isn't just about being environmentally responsible—it's a strategic advantage that can drive real business results. This presentation explores how green optimization practices can simultaneously reduce your Total Cost of Ownership (TCO) and boost conversion rates. Watch the full video below to learn about: - **Cost Reduction Strategies**: How sustainable practices can lower operational expenses - **Conversion Optimization**: Techniques that improve user experience and drive sales - **Real-World Examples**: Case studies demonstrating the impact of green optimization - **Implementation Roadmap**: Practical steps to get started with eco-friendly e-commerce https://www.youtube.com/watch?v=xn_bxpTJgjQ --- ## (Don't) Leave Me Alone! - Facing depression in the remote times - URL: https://lbajsarowicz.me/blog/don-t-leave-me-alone-facing-depression-in-the-remote-times/ - Published: 2023-11-09 - Category: Personal - Tags: video, youtube, mental-health, conference, speaking, personal - Description: A personal presentation about mental health challenges in remote work environments. This talk, delivered at Meet Magento România, addresses the importance of mental health awareness in the IT industry and beyond. This 20-minute presentation focuses on **#MentalHealth** in remote work environments. Although based on experiences in the #IT industry, the insights and strategies shared can be applied to any intellectual work. In this talk, I discuss: - **The challenges of remote work** and isolation - **Recognizing signs of depression** in professional settings - **Practical strategies** for maintaining mental health while working remotely - **Creating supportive environments** in teams and companies - **Breaking the silence** around mental health in tech Watch the full presentation below: https://www.youtube.com/watch?v=-K5OE4Oxxm8 --- ## Et voila! Conference Season is over! - URL: https://lbajsarowicz.me/blog/et-voila-conference-season-is-over/ - Published: 2023-11-09 - Category: Personal - Tags: conference, mental-health, speaking, personal - Description: This year, my 20-minute presentation "(Don't) Leave Me Alone! — Facing depression in the remote times." was focused on #MentalHealth. Although based on #IT, the presentation could have been applied to any intellectual work. This year, my 20-minute presentation "(Don't) Leave Me Alone! — Facing depression in the remote times." was focused on #MentalHealth. Although based on #IT, the presentation could have been applied to any intellectual work. * 5 conferences (4 x speaker: Mumbai, Miami, Romania, Amsterdam) * 4 meetups (3 x speaker: Gdansk, Poznan, Frankfurt) * Over $4200 for flights, trains, hostels, food to reach conference * Over a month (total) in travel * One true mission: Stop that madness! Was it worth the hustle? Yes. I'm more than sure – Yes! I believe I was that little domino brick that inspired others not only to take care of themselves but also to… talk about mental health in their homes and companies. I've met many wonderful people all around the world visited the most beautiful places I always wanted to visit (Taj Mahal, Romania, Florida – rocket launch, pouring Amsterdam). Conference Photo 1 Conference Photo 2 Conference Photo 3 Conference Photo 4 Conference Photo 5 Conference Photo 6 ## What's next? I feel the Mental Health mission was way too challenging. For the next few months, I'll stay in one place and focus on my health (mental, too). In 2024, I changed the direction completely. With a technical background, I'll focus on social responsibility and answer the question: "How can e-commerce be more environmentally friendly?" --- ## Ecommerce Ninja Series: Utilizing Magento Integration Tests Framework - URL: https://lbajsarowicz.me/blog/ecommerce-ninja-series-utilizing-magento-integration-tests-framework/ - Published: 2022-05-04 - Category: Magento / Adobe Commerce - Tags: video, youtube, magento, mftf, integration-tests, testing, adobe-commerce - Description: Learn how to effectively use the Magento Integration Tests Framework (MFTF) in your development workflow. This technical presentation covers practical approaches to writing and maintaining integration tests for Magento projects. The Magento Integration Tests Framework (MFTF) is a powerful tool for ensuring your Magento store functions correctly. This presentation from the Ecommerce Ninja Series provides practical guidance on utilizing MFTF effectively. In this talk, you'll learn about: - **MFTF fundamentals** and how it fits into your testing strategy - **Writing effective integration tests** for Magento modules and customizations - **Best practices** for maintaining test suites - **Common patterns** and workflows for development teams - **Real-world examples** and implementation strategies Watch the full presentation below: https://www.youtube.com/watch?v=xZC697klWaA --- ## MFTF: Acceptance functional testing for agencies and extension developers - URL: https://lbajsarowicz.me/blog/mftf-acceptance-functional-testing-for-agencies-and-extension-developers/ - Published: 2020-09-28 - Category: Magento / Adobe Commerce - Tags: video, youtube, magento, mftf, testing, functional-testing, adobe-commerce - Description: Learn how to implement Magento Functional Testing Framework (MFTF) for acceptance testing in your agency projects and custom extensions. Practical approaches to functional testing that improve code quality and reduce regression issues. Magento Functional Testing Framework (MFTF) is a powerful tool for creating acceptance tests that validate your Magento store's functionality. This presentation focuses on practical implementation strategies for agencies and extension developers. In this talk, you'll learn about: - **MFTF fundamentals** and how it fits into the testing strategy - **Best practices** for writing effective functional tests - **Implementation approaches** for agencies working with multiple clients - **Extension development** testing patterns and workflows - **Real-world examples** and common pitfalls to avoid Watch the full presentation below: https://www.youtube.com/watch?v=t8hjae0L3Uo --- ## Migrating PHPUnit 6 to PHPUnit 9.1 in Magento 2.4 - URL: https://lbajsarowicz.me/blog/migrating-phpunit-6-to-phpunit-9-1-in-magento-2-4/ - Published: 2020-06-16 - Category: Magento / Adobe Commerce - Tags: magento, phpunit, testing, migration, php - Description: Magento 2.4.0 comes with a major upgrade of the PHPUnit framework, which is used for all types of tests: Static, Unit, Integration, API Functional, and the Magento Functional Testing Framework. > **📋 Archival Copy:** This is an archival copy of a blog post originally published on [Adobe Magento DevBlog](https://community.magento.com/t5/Magento-DevBlog/Migrating-PHPUnit-6-to-PHPUnit-9-1-in-Magento-2-4/ba-p/449879). The original publication date has been preserved. Magento 2.4.0 comes with a major upgrade of the PHPUnit framework, which is used for all types of tests: Static, Unit, Integration, API Functional, and the Magento Functional Testing Framework. Magento Architects decided to make an upgrade to the latest PHPUnit version, which is 9.1. Please be aware that the upgrade of PHPUnit is backward incompatible. In this post, you'll learn about the migration process, the challenges we faced, the tools and scripts we used, and how you can apply similar techniques to migrate your own PHPUnit tests. ## Migration process The migration began during Magento Commerce Global Contribution Day on April 4th. Together with the community, we started working on [issue #27500](https://github.com/magento/magento2/issues/27500). One week later, not even half the work was complete. The clock was ticking, as the `2.4` feature freeze was approaching. I tried to encourage Magento contributors to undertake the challenge. > **"Use Rector"** appeared quite often instead. Out of curiosity, I tried to use existing _Rectors_. The first issue I noticed was the fact that **Rector** expects correct PHP syntax (including annotations) and additionally, expects classes to exist. Otherwise, code refactoring fails. ### Migration Challenges The migration presented several significant challenges that needed to be addressed: We had to contend with: 1. Over **30.000 Unit Tests** to migrate in very limited time 2. Invalid PHP code syntax, missing or incorrect annotations 3. Backward compatibility requirements 4. Required to pass Static Analysis **Note:** The first approach assumed that we need to migrate PHPUnit 6 to PHPUnit 8 first, as it was backward-compatible, and then in a further release, upgrade to PHPUnit 9\. Finally, the Core Team said that we are allowed to migrate all the way to PHPUnit 9. ### Step 1: Fix failing Unit Tests The Magento codebase includes over 30.000 unit tests (including Magento 2 Commerce, B2B and projects such as MSI). Some of them were just flaky (eg. environment-dependent) or invalid. The first step was to fix them and make sure these tests pass on all supported PHP versions. ### Step 2: Migration to PHPUnit 8 All contributions to Magento 2 must follow the Definition of Done: * Pass Integrity Tests which include Static Code Analysis Every single file modified in the Magento codebase has to follow coding standards. Most of the issues were fixed automatically by PHP Code Beautifier and PHP CS Fixer * Pass Unit Tests Migration of unit tests included not only compatibility with PHPUnit 8, but also introduction of Strict Typing. This step revealed many, many hidden issues caused by Type Juggling. One common issue was with assertions that appeared correct but were actually tautologies. For example: `$this->assertTrue($validator->isValid($data))` is actually a tautology, because both `true` and an `array` with an error was returning a true value: ```php return [ 'status' => 'error', 'message' => __('Error message') ]; ``` There was also an issue with handling `Phrase` objects with `\PHPUnit\Framework\Assert::assertEquals` or `\PHPUnit\Framework\MockObject\InvocationMocker::with`. Declaration of `strict_types=1` "turns off" the Type Juggling, though `->__toString` has to be called explicitly on a Phrase object, otherwise `assertEquals('Expected Error Message', $phraseObject)` failed. There are two ways to fix this: `assertEquals('Expected Error Message', $phraseObject->__toString())` or the preferred method: `assertEquals('Expected Error Message', (string)$phraseObject)` The most time-consuming manual job was related to line length: the limit of 120 characters could not be fixed automatically. We had to edit the exception messages manually, wrapping them into multiple lines. No one is capable of reviewing 30,000 files submitted in scope of a single PR. The decision was made to split the changes by module. Every single module was separately reviewed and stabilized. At this stage, PHPUnit Warnings and Notices were allowed. ### Step 3: Migration to PHPUnit 9 Contributors who took on the challenge were given a separate Slack channel for synchronizing with the core team, as well as a separate PHP 7.4-based branch. The goals were: 1. Unit Tests passing with PHP 7.3 and PHP 7.4 2. Compliance with PHPUnit 9.1 3. Drop deprecated methods and structures At the end of that stage, PHPUnit Warnings and Notices **were not** allowed. PHPUnit 9 comes with very strict rules for Mocking objects. Invalid usage of `\PHPUnit\Framework\TestCase::createPartialMock` for Interfaces and Abstract classes had to be replaced with `getMockBuilder` and then configured. The `\PHPUnit\Framework\MockObject\MockBuilder::setMethods` method was replaced with `onlyMethods` and `addMethods`. What is the difference? You need to explicitly specify whether you're mocking an existing method or a non-existent method of a class or interface. ### Step 4: Upgrade & Merge Magento internal teams were working on the API functional tests and integration tests, while the Community worked on unit tests. The final step was to synchronize the results and stabilize the outcome. Having `magento/2.4-develop74` stabilized with PHP 7.4, these changes were merged to the mainline `magento/2.4-develop` branch. Our journey was complete! ## Tooling Such a large undertaking could not be accomplished manually. Given the scale of the migration, automation was essential. That is why, step by step, we used automated tools and scripts to make the upgrade possible. This code was not intended for publication, which is why it may not follow best practices. ### Script: Per-module automation The natural way of delivering units of work is splitting the results by Module. That is why the shell scripts were focused on these scopes: `./migration.sh {Module name} [Custom directory]` | Command | $TESTS\_PATH | | -------------------------- | ------------------------------------------------ | | ./migration.sh Catalog | /var/www/html/app/code/Magento/Catalog/Test/Unit | | ./migration.sh Staging .ee | /var/www/html/.ee/app/code/Staging/Test/Unit | Where `[Custom directory]` was optional and was used to handle different products: * Magento Open Source * Magento Commerce * Magento B2B * PageBuilder. ```bash #!/bin/bash set -e BASE_DIR="/var/www/html/${2:-}" TESTS_PATH="$BASE_DIR/app/code/Magento/$1/Test/Unit" ``` Working with different projects, we had to handle multiple Git repositories: ```bash LocalGit () { git --work-tree="$BASE_DIR" --git-dir="$BASE_DIR/.git" "$@" } ``` Not all modules have unit tests, so our script must verify test availability: ```bash test -d "$TESTS_PATH" || (echo "Module $1 does not contain Unit Tests in $TESTS_PATH" && exit 1) ``` and if the existing tests were not failing PHPUnit 6 before our modifications: ```bash /var/www/html/phpunit8/vendor/bin/phpunit -c dev/tests/unit/phpunit6.xml $TESTS_PATH ``` ### Script: Fix known issues There were plenty of issues with the PHP syntax, which is easy to fix with `sed`: * Invalid arguments order for PHPDoc: `$variable \Type` instead of `\Type $variable` ```bash find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i 's/@var\s+(\$[^\s]+)\s+([\\|A-Za-z_]+)/@var \2 \1/g' {} \; ``` * Introduce correct DOCBlock opening (replace `/*` with `/**`) ```bash find $TESTS_PATH -name "*.php" -exec sed 's/\/\*$/\/\*\*/g' {} \; ``` * Remove redundant `use` section alias ```bash find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i 's/use \\?(PHPUnit[^;]+) as MockObject/use \1/g' {} \; ``` * Remove redundant empty lines ```bash find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i ':a;N;$!ba;s/\n\n\n/\n\n/g' {} \; ``` * Replace `Phrase` with `string` for non `LocalizedException` ```bash find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i 's/( \\[A-Za-z]+)\(__\(([^)]+)\)\)/\1(\2)/g' {} \; ``` * Move `declare()` introduced by PHP CS Fixer below Magento Copyright ```bash find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i ':a;N;$!ba;s/(declare\(strict_types=1\);)\n(\/\*\*[^\/]+\/)/\n\2\n\1\n/g' {} \; ``` ### PHP Rector Once we had the PHP code ready for migration, we were finally able to use the right tools for the job. As recommended by the community, I decided to introduce [PHPRector](https://github.com/rectorphp/rector) to the project using the Composer command `composer require rector/rector --dev`. This tool offers a predefined set of rectors (scenarios), just like the PHPUnit 8.0 migration set. The backward-compatibility with PHPUnit 6 had to be kept, which is why some rectors were excluded in our `rector.yml` configuration file: ```yaml parameters: auto_import_names: true import_short_classes: false import_doc_blocks: true sets: - 'phpunit80' exclude_rectors: - 'Rector\PHPUnit\Rector\MethodCall\SpecificAssertInternalTypeRector' - 'Rector\PHPUnit\Rector\MethodCall\SpecificAssertContainsRector' ``` During the second stage of migration (PHPUnit 8 to PHPUnit 9 and drop for backward-compatibility) the `rector.yml` file was much more advanced: ```yaml parameters: auto_import_names: true import_short_classes: false import_doc_blocks: true sets: - 'phpunit70' - 'phpunit80' - 'phpunit90' - 'phpunit91' - 'phpunit-specific-method' - 'phpunit-mock' services: M2Coach\Rector\ReplacePartialMockRector: null Rector\Renaming\Rector\MethodCall\RenameMethodCallRector: $oldToNewMethodsByClass: PHPUnit\Framework\Assert: assertRegExp: assertMatchesRegularExpression expectExceptionMessageRegExp: expectExceptionMessageMatches PHPUnit\Framework\TestCase: assertRegExp: assertMatchesRegularExpression expectExceptionMessageRegExp: expectExceptionMessageMatches Rector\Renaming\Rector\Class_\RenameClassRector: $oldToNewClasses: # [GitHub issue #3123](https://github.com/sebastianbergmann/phpunit/issues/3123) PHPUnit\Framework\MockObject\Matcher\InvokedCount: 'PHPUnit\Framework\MockObject\Rule\InvokedCount' PHPUnit\Framework\MockObject\Matcher\Invocation: 'PHPUnit\Framework\MockObject\Invocation' PHPUnit_Framework_MockObject_MockObject: 'PHPUnit\Framework\MockObject\MockObject' ``` And that actually did most of the work. ### Code Style Once we had the code ready for PHPUnit 9, we had to adjust the Code Style to follow Magento Coding Standard. * PHP CS Fixer (`.php_cs`) ```php ->setRules([ '@PSR2' => true, 'array_indentation' => true, 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], 'declare_strict_types' => true, 'hash_to_slash_comment' => true, 'include' => true, 'method_chaining_indentation' => true, 'method_argument_space' => true, 'modernize_types_casting' => true, 'new_with_braces' => true, 'no_empty_statement' => true, 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_extra_consecutive_blank_lines' => true, 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_multiline_whitespace_around_double_arrow' => true, 'no_multiline_whitespace_before_semicolons' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_short_bool_cast' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_unused_imports' => true, 'no_whitespace_in_blank_line' => true, 'object_operator_without_whitespace' => true, 'ordered_imports' => true, 'php_unit_set_up_tear_down_visibility' => true, 'standardize_not_equals' => true, 'ternary_operator_spaces' => true, ]); ``` * PHP Mess Detector (default `dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml`) * PHP Code Beautifier (config shared with Mess Detector) ### Script: PHP Mess Detector fixer Some of the issues reported by Mess Detector were fixed automatically with custom scripts ```bash #!/bin/bash set -e TOFIX="$(/var/www/html/vendor/bin/phpmd "$1" text /var/www/html/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml)" || echo "Found $(echo $TOFIX | wc -l)" [[ "$TOFIX" == "" ]] && exit 0 sed -i ':a;N;$!ba;s/\nclass/\n\/\*\*\n \* \@SuppressWarnings\(PHPMD.CouplingBetweenObjects\)\n \*\/\nclass/' $(echo "$TOFIX" | grep 'number of dependencies' | grep -oE '^([^:]+)' | tr '\n' ' ') || true sed -i ':a;N;$!ba;s/\nclass/\n\/\*\*\n \* \@SuppressWarnings\(PHPMD.AllPurposeAction\)\n \*\/\nclass/' $(echo "$TOFIX" | grep 'processed HTTP methods' | grep -oE '^([^:]+)' | tr '\n' ' ') || true sed -i ':a;N;$!ba;s/\n \*\/\n\/\*\*//' $(echo "$TOFIX" | grep -oE '^([^:]+)' | tr '\n' ' ') || true /var/www/html/vendor/bin/phpmd "$1" text dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml || exit 1 ``` ## Wrap up The automated part of migration was wrapped into a `find` command that iterated through the module directories, executing Bash scripts. If the procedure failed, the information was written to a `todo-manual.txt` file, to review manually. ```bash #!/bin/bash set -e LocalGit () { git --work-tree="$BASE_DIR" --git-dir="$BASE_DIR/.git" "$@" } BASE_DIR="/var/www/html/${2:-}" TESTS_PATH="$BASE_DIR/app/code/$1/Test/Unit" test -d "$TESTS_PATH" || (echo "Module $1 does not contains Unit Tests in $TESTS_PATH" && exit 1) echo "=== Performing actions on $1 module ===" ( find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i 's/@var\s+(\$[^\s]+)\s+([\\|A-Za-z_]+)/@var \2 \1/g' {} \; \ && find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i ':a;N;$!ba;s/\n\n\n/\n\n/g' {} \; \ && /var/www/html/vendor/bin/rector --config=/var/www/html/rector.yml process $TESTS_PATH \ && find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i 's/use \\?(PHPUnit[^;]+) as MockObject/use \1/g' {} \; \ && find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i "s/expectException\('\\?(.*?)'\)/expectException(\\\\\1::class)/g" {} \; \ && find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i 's/( \\[A-Za-z]+)\(__\(([^)]+)\)\)/\1(\2)/g' {} \; \ && (/var/www/html/vendor/bin/phpcbf --standard=/var/www/html/dev/tests/static/framework/Magento/ruleset.xml $TESTS_PATH; test $? -le 1 ) \ && /var/www/html/vendor/bin/phpcs --standard=/var/www/html/dev/tests/static/framework/Magento/ruleset.xml $TESTS_PATH \ && /var/www/html/vendor/bin/php-cs-fixer fix --config=/var/www/html/.php_cs $TESTS_PATH \ && ( /var/www/html/fix-mess.sh "$TESTS_PATH" || echo "Mess fixer failed") \ && find $TESTS_PATH -name "*.php" -exec sed --regexp-extended -i ':a;N;$!ba;s/(declare\(strict_types=1\);)\n(\/\*\*[^\/]+\/)/\n\2\n\1\n/g' {} \; \ && /var/www/html/vendor/bin/phpunit --fail-on-warning -c dev/tests/unit/phpunit9.xml $TESTS_PATH \ && LocalGit commit -m "#27500 PHPUnit9 for $1 module" $TESTS_PATH && git push lbajsarowicz ) || ( echo "$TESTS_PATH" >> todo-manual.txt && LocalGit reset --hard) ``` The code was not expected to be shared, so it is not "pretty". However, this way we were able to accomplish the entire PHPUnit 6 to PHPUnit 9 migration in a few weeks, and having them compatible with Magento coding standards. ### Extension Developers As an extension developer, you can easily adjust the provided scripts to migrate existing PHPUnit tests to the latest version semi-automatically. If you followed the PHP coding standards during extension development, that should be enough to use PHP Rector with the configuration provided. ### Thank you! This project could not be accomplished without huge support of Slava Mankivski, Lena Orobei, Igor Sviziev. Community contributions could not be merged without the help of Oleksii Korshenko and Igor Miniailo. I really appreciate the huge impact of Mediotype, who supported me the whole way! Special thanks to my competent colleagues, who shared their experience and expertise. Thank you, David Alger, for the Warden Development Environment and introducing PHP 7.4 support after my request. --- ## Decomposition of Magento Controllers - URL: https://lbajsarowicz.me/blog/decomposition-of-magento-controllers/ - Published: 2020-03-18 - Category: Magento / Adobe Commerce - Tags: magento, controllers, architecture, composition, php - Description: Magento 2.4 became a perfect opportunity to proceed with backwards-incompatible changes that were waiting for years. One such change was the decomposition of Controllers using composition instead of inheritance. > **📋 Archival Copy:** This is an archival copy of a blog post originally published on [Adobe Magento DevBlog](https://community.magento.com/t5/Magento-DevBlog/Decomposition-of-Magento-Controllers/ba-p/430883). The original publication date has been preserved. Magento 2.4 became a perfect opportunity to proceed with backward-incompatible changes that were waiting for years. One such change was the decomposition of Controllers using composition instead of inheritance. When I spoke at Magento conferences about **replacing inheritance with composition**, I didn't realize I would be part of making this change a reality. The work began with [this Pull Request](https://github.com/magento/magento2/pull/27500) and its followup introduced by Vinai Kopp. Although this change was very expected, these PRs were not merged initially. At the beginning of 2020, Vinai encouraged me to continue the work on Controllers decomposition using his contribution. With his continuous support and tremendous work of Lena Orobei—together we finally delivered one of the biggest architectural changes towards decomposition of Controllers. ## Solution ![Controller Decomposition Solution](/images/blog/decomposition-of-magento-controllers-diagram.png) As a module developer, to implement a new Controller Action you only need to implement the `\Magento\Framework\App\ActionInterface`. The authentication mechanisms for Customers are also migrated! ## Benefits The decomposition of Controllers brings several key benefits that improve both developer experience and application performance: ### No need to extend Module developers don't have to extend from any class to create a fully functional action controller. #### Example controller GET Action ```php use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\View\Result\PageFactory; class MyController implements HttpGetActionInterface { /** @var PageFactory */ protected $resultPageFactory; public function __construct(PageFactory $resultPageFactory) { $this->resultPageFactory = $resultPageFactory; } public function execute() { return $this->resultPageFactory->create(); } } ``` You may have noticed that we use `\Magento\Framework\App\Action\HttpGetActionInterface`. It is a method-specific Interface extending `ActionInterface`. If you want to explicitly define what methods are going to be handled by the Controller, the most common interfaces are: * `\Magento\Framework\App\Action\HttpDeleteActionInterface` * `\Magento\Framework\App\Action\HttpGetActionInterface` * `\Magento\Framework\App\Action\HttpPostActionInterface` * `\Magento\Framework\App\Action\HttpPutActionInterface` Please be aware that the `HEAD` method is handled the same way that `GET` is. ### Performance Previously, `\Magento\Framework\App\Action\Context` was injected into Actions with a set of classes: ```php /** * @param \Magento\Framework\App\RequestInterface $request * @param \Magento\Framework\App\ResponseInterface $response * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Framework\UrlInterface $url * @param \Magento\Framework\App\Response\RedirectInterface $redirect * @param \Magento\Framework\App\ActionFlag $actionFlag * @param \Magento\Framework\App\ViewInterface $view * @param \Magento\Framework\Message\ManagerInterface $messageManager * @param \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory * @param \Magento\Framework\Controller\ResultFactory $resultFactory */ ``` There's no doubt that the new way of creating Controllers is much cleaner. The performance overhead caused by instantiating unused classes is significantly reduced. #### Differences * Simple controllers like `customer/account/logoutSuccess` experience a `5%` decrease in CPU time on generation. ![Performance Chart - Simple Controllers](/images/blog/decomposition-of-magento-controllers-blackfire-simple-controllers.png) * Complex controllers like `customer/section/load` experience a `> 30%` decrease in CPU time on generation. ![Performance Chart - Complex Controllers](/images/blog/decomposition-of-magento-controllers-blackfire-complex-controllers.png) * Common ones like `catalog/category/view` experience a `10%` decrease in CPU time on generation. ![Performance Chart - Common Controllers](/images/blog/decomposition-of-magento-controllers-blackfire-common.png) _Performance measurements were performed using [BlackFire.io](https://blackfire.io), in an isolated Docker environment with cURL requests, not being affected by Browser/Network overhead._ Thanks to Christophe Dujarric for BlackFire's support. These performance improvements demonstrate the tangible benefits of the composition approach, with CPU time reductions ranging from 5% for simple controllers to over 30% for complex ones. ### Testing Controllers are easier to test due to their reduced amount of dependencies. Inheriting from `AbstractAction` forces you to use at least the same dependencies as the parent class. Unit Test for Category View has more than 70 lines of mocking dependencies, mocking `Context` methods to return mocked dependencies. With the composition approach, you can inject dependencies directly into your class and inject only the ones you need (for example, only the ones you are going to use with your Unit Tests). This significantly reduces the complexity and verbosity of unit tests. ### Caution > **⚠️ Important Considerations:** * Keep in mind that some Modules have their own `AbstractAction`. For example `\Magento\Customer\Controller\AccountInterface` additionally handles Customer Authentication. * Controller "Supertypes" are deprecated (`\Magento\Backend\App\AbstractAction`, `\Magento\Framework\App\Action\Action`, `\Magento\Framework\App\Action\AbstractAction`, `Magento\Framework\App\Action\Action\AbstractAccount`) and you should not use them anymore. * It is recommended to avoid code migration till 2.5.0 since third-party observers may be subscribed to your controllers. Methods like `getRequest`, `getResponse`, `getActionFlag` are eliminated with the inheritance and it will lead to errors when accessing them through controller object from event. * It is recommended to use the new approach for new code only starting with the 2.4.0 release. * Existing Magento controllers will not be migrated until 2.5.0 to keep backward compatibility. --- ## Continuous Integration? Not a rocket science! - URL: https://lbajsarowicz.me/blog/continuous-integration-not-a-rocket-science-ukasz-bajsarowicz-mageconf18/ - Published: 2019-02-04 - Category: Magento / Adobe Commerce - Tags: video, youtube, magento, ci-cd, continuous-integration, devops, adobe-commerce - Description: Learn how to implement Continuous Integration (CI) for Magento projects. This presentation from MageCONF18 demystifies CI/CD pipelines and shows practical approaches for agencies and developers. Continuous Integration (CI) doesn't have to be complicated. This presentation from MageCONF18 breaks down CI/CD concepts and shows how to implement them in your Magento development workflow. In this talk, you'll learn about: - **CI/CD fundamentals** and why they matter for Magento projects - **Setting up CI pipelines** for automated testing and deployment - **Best practices** for agencies and development teams - **Practical examples** and real-world implementation strategies - **Common pitfalls** and how to avoid them Watch the full presentation below: https://www.youtube.com/watch?v=C-O2zu1PfVk --- Last generated: 2026-04-28