I was randomly drifting through Github trending PHP repositories, and checking how popular projects use `rector.php`. A lot of them use it extensively, with [prepared sets](https://getrector.com/documentation/set-lists) and [even levels](https://getrector.com/documentation/levels). What I didn't like to see is the *skip section*. It's like a baseline in PHPStan, but I take it really seriously. In…
Easy Coding Standard got a [new release last week](/blog/ecs-13-2-lighter-smarter-more-fun-for-large-legacy-projects) with a couple of cool features and new dead docblock rules. But the tool is only as powerful as its master. With agents, even more so. I've noticed people use ECS in an overly complex way, which is not needed... and definitely not fun. If it's longer than 15 lines, you're probably…
Easy Coding Standard focuses on easy setup, easy run, and easy use. Last year we [simplified the config](/blog/zen-config-in-ecs) to a single fluent line. Today we ship **ECS 13.2** - a release that makes ECS lighter to install, smarter about docblocks, and more fun to integreate to a large project, step by step. Let's look at what's new.
PHPStan core gets you to level 10 with solid type checks. But some gems, the rules that catch *your* anti-patterns, live scattered across a dozen community packages most people never hear about. I got tired of hunting through GitHub READMEs to find "is there a rule for this?", so I built a single searchable page that indexes 11 custom PHPStan rules packages.
We've all seen it: `new Human()` followed by three `setName()`, `setAge()`, `setEmail()` calls that are always there. Not optional - just pretending. These are constructor arguments in a fluent disguise, and they leave your objects half-valid every time someone forgets one. So I made a small PHPStan extension to find them.
In previous parts ([part 1](/blog/alice-nelmio-hautelook-faker-and-how-to-upgrade-doctrine-fixtures-part-1) and [part 2](/blog/alice-nelmio-hautelook-faker-and-how-to-upgrade-doctrine-fixtures-part-2)) we looked at a situation where we are stuck with Alice, Nelmio, Hautelook, Faker legacy mess and need a way out to modern maintainable Doctrine. This is the final part. **The upgrade is finished and…
Keeping the tradition from [previous](/blog/5-books-i-enjoyed-reading-in-2024) [years](/blog/5-books-i-enyjoed-reading-in-2023), here is my short list of 5 books I really enjoyed reading in 2025.
[I switched this website from Symfony to Laravel](/blog/why-I-migrated-this-website-from-symfony-to-laravel) 2,5 years ago, and I [love Laravel Container](/blog/what-i-prefer-about-laravel-dependency-injection-over-symfony) ever since. Symfony and Laravel containers are very similar - read this [compare post](/blog/experiment-how-i-replaced-symfony-di-with-laravel-container-in-ecs) if you know one…
There are dozens of posts and talks about how Symfony Workflows work and what they are for. To give you the simplest example, if you have a post - it can be drafted, reviewed, or published. The workflow component makes sure the transitions between these states are valid. That's it. Yet, there is not a single post about how terrible the configuration is. On one hand, we have Symfony components,…
The `@Security` annotation, which originated in the Sensio extra bundle, goes a long way. The official upgrade docs have a few misleading pointers, that force you to use unnecessary verbose language. Fortunately, few hidden levels make code much less verbose and more readable. This post sums up upgrading the Sensio `@Security` annotation to Symfony `#[IsGranted()]` attribute in one place.
There are 2 ways to use PHPStan. You can use native levels, and official extensions and raise the level from 0 to 8. This is a good start, but it often requires enormous work and brings must-have value. There is also a 2nd way: I wanted PHPStan **to be more fun and more tailored to the unique projects I work with**. That's why I made…
As I'm manually writing this article, GPTs are on the hype train now. In this post, we'll use freshly released ChatGPT 4.5 and Grok 3 and see, if they know the AST of PHP well enough to be used on **a large PHP project**. Understanding AST takes longer than writing an English sentence in a chat. But once you see the abstract syntax tree in a code you're reading, it cannot be unseen. Today I'll try…
Using PHPStan is not just about getting to level 8 with less than 100 ignored cases. Yes, there are also [official extensions](https://packagist.org/?query=phpstan%2Fphpstan-) that improve the type support of Symfony, Doctrine, and Laravel projects. But more rules are needed to get our PHP project into a future-proof state. **It takes less effort than getting to level 5 and we can use them since…
Today, I'll share a trademark secret that allows us to move fast and make huge changes without fear. When we're starting a new [Symfony 2.8-7.2 upgrade project](/blog/off-the-beaten-path-to-upgrade-symfony-28-to-72), we cover it with a couple of tests first. These tests are not units, but smoke tests - with a couple of lines they cover a huge portion of the Symfony framework layer we use. With a…
In the first part, [we've kicked off the plan](/blog/alice-nelmio-hautelook-faker-and-how-to-upgrade-doctrine-fixtures-part-1) to upgrade all these packages to their latest version, like a blind map into unknown territory. Since then, we've put in a couple of months of hard work and climbed the terrain. Today, we look at the practical steps we've taken and the new challenges we discovered [after…
Symfony 3 introduced a [`@required` annotation](https://symfony.com/doc/3.x/service_container/calls.html) (now also an attribute) that allows injecting more services via the setter method apart constructor. At the time, it was good. The goal was to solve circular dependencies: when A needs B, B needs C, and C needs A. But more often than not, I see PHP projects where it got completely out of hand.…
Recently, I've been working on projects with Behat tests. There are hundreds of definitions that can be used in feature file scenarios. I accidentally noticed that one of the definitions is not used at all and could be removed. This would result in less code to maintain, less code to read, and less code to upgrade. But I thought, "That's weird." **Why did not Behat report this definition** in our…
Final classes bring much more value than `extends` it lacks. It teaches composition over inheritance, makes upgrades easier, and [even mocking](/blog/2019/03/28/how-to-mock-final-classes-in-phpunit) is fine. If you're lazy like me, you can [automate the `final` keyword addition](/blog/finalize-classes-automated-and-safe) to your code - quickly, safely, and check it the CI!
There are two types of upgrades. One follows only `UPGRADE.md` files on every release, replacing what has been removed with new alternatives. It works, and we could say that the codebase will be "up-to-date." The other upgrade doesn't stop at the required minimum but **makes use of all modern features the framework provides**. It will be faster, easier to understand, and easier to upgrade to the…
While working with legacy projects, I often encountered this anti-pattern of misusing repositories. Instead of easy-to-inject service, projects are locked into a service locator. This makes code hard to upgrade and locks your project heavily to the Doctrine ODM packages. And there are plenty of them. Each extra package bites off its share of upgrade costs. Today, we look at how to refactor the ODM…
Upgrading Doctrine Fixtures can be challenging work. It requires the coordination of four different package groups on Github. One cannot be upgraded without the other. We first prepare for the jump, then take the leap with confidence. I'm writing this post as I handle the upgrade, so my thoughts are fresh, and the process is ongoing. It may evolve based on new information. Let's see where the…
The legacy projects we work with are often flooded with mocks. I already wrote [How to Remove Dead Mock Calls from PHPUnit Tests](/blog/how-to-remove-dead-mock-calls-from-phpunit-tests), which focuses on dealing with PHPUnit bloated syntax. Today, we look at the next wave of improvements that make tests more valuable, more accessible to upgrade and read, and even avoid false types.
The `withConsecutive()` method [was deprecated in PHPUnit 9](https://github.com/sebastianbergmann/phpunit/issues/4255#issuecomment-636422439) and removed in PHPUnit 10. It sparked many [questions](https://stackoverflow.com/questions/75389000/replace-phpunit-method-withconsecutive-abandoned-in-phpunit-10), [on…
Recently, I listened to Lex Friedman's [podcast with Pieter Levels](https://open.spotify.com/episode/6KBpL2XfR9VdojbKNpE7cX). Pieter talked about his technology stack for building startups: vanilla PHP, jQuery, and SQLite. Hype is exciting, but there is no better technology proof than long-term usage by sustainable business. The podcast inspired me to write my views about why PHP is the best…
Yesterday, I came across a cool PHP tool. I wanted to try it, but the installation instructions were a bit tricky. The tool required a specific PHP version and a specific version of each dependency. It required Symfony 5.4+, but our project has Symfony 3.3. I was unable to use it. Many PHP tools suffer from the same issue, so I thought I'd share a way to do it better.
Last month, I spent in Japan to travel and absorb the local culture. I've also pinged a few friends I knew from Rector and AST projects in the past. I was surprised by how productive the PHP community is around here, with tools I'd personally use and ideas that inspire me.
I believe that every Symfony app can fit service config under 5 lines. Configs are among the most underestimated parts of Symfony projects and deserve to be done right. Like a healthy tree trunk, which brings power to the branches and leaves, clear configs keep the design architecture clear and easy to grow.
I use Livewire 3 for interactive forms and fast clickable maps. Last week, I worked on a filter page for the Rector website, where you can use text input to search for core and community rules. I typed "add param" to the input and got the results that best matched the rules I could use. But the code highlight was gone; what now? Current proposed solutions in Laracast/Livewire forums are…
In PHP, we have classes with methods inside them. Would making all your methods `public` be a good idea? No, because some of them should be used only by the class they're in and not anywhere else. What about class constants?
When using a Swiss knife, we think of a tool with many practical abilities. They're useful for different situations we might experience in the wild. Opening a box of milk? Here is a knife. Cutting wood to start a fire? Here is a chainsaw. Are the letters on the paint bucket too tiny? Try this magnifying class. Now, we apply the same approach to PHP tooling.
When I come to a new project, I want to make a rough idea of what I'm dealing with in a few minutes. I usually check `composer.json` and [measure the lines](/blog/easy-and-quick-way-to-measure-lines-of-code-in-php). Then, I'd love to run PHPStan and get a rough idea of the current state. But are there baselines, custom extensions, global ignores, or no PHPStan at all?
...and how to avoid it. [Type coverage](/blog/how-to-measure-your-type-coverage) is a way to gradually add type declarations to your PHP project—step by step, one by one. It's a PHPStan package that helps you maintain a specific minimal level from 0 % to 100 %. Once we reach high coverage of 80-90 %, we feel safer. But our code can actually be in worse, even dangerous, shape.
When we upgrade a new project to the best version possible, the latest PHP and framework versions, it's not only about changing syntax sugar to a more fancy one. It's about the vast focus shift in project management so far. It's a change to master. I want to share the basic rules we apply to make the "impossible" upgrades successful and steady.
Final classes have [many](https://ocramius.github.io/blog/when-to-declare-classes-final/) [great](https://tomasvotruba.com/blog/2019/01/24/how-to-kill-parents) [benefits](https://matthiasnoback.nl/2018/09/final-classes-by-default-why/) for future human readers of your code. They have even more benefits for static analysis and Rector rules. But what if we have a project with 1000+ classes and 10…
I'm happy to introduce the latest update to our book - *Rector, the Power of Automated Refactoring*, along with Rector version 0.19.5 from this week. This release includes 2 new commands, brand new configuration with smart IDE autocomplete, brand new chapter and DX improvements to help you master code refactoring with ease.
In [the first post](/blog/3-signs-your-project-is-becoming-legacy-and-how-to-avoid-them), we looked at the long-term effects of our decisions. Turning a legacy project into a fresh one is a matter of the "just do it" approach. But there are 3 things we should take with care even if our project seems outside the legacy project category. The second of those is mocks.
Early this year, I created a few custom Rector rules for our client. It modified the code based on the PHPUnit error result report. The only problem is that PHPUnit outputs a string. So, I had to parse it manually with regexes. Having a JSON output would make my life easier. I'm used to PHP tools that provide the JSON out of the box, but I could not find it in PHPUnit.
Easy Coding Standard focuses on easy run, setup, and use. From composer requirement through the automated setup to the config. The config was based on rather cumbersome Symfony closure service configs. But last year, I [switched the DI container to Laravel](/blog/experiment-how-i-replaced-symfony-di-with-laravel-container-in-ecs). Eventually, this opened the door to further innovation that I'll…
In [the first post](/blog/3-signs-your-project-is-becoming-legacy-and-how-to-avoid-them), we looked at the long-term effects of our decisions. Turning a legacy project into a fresh one is a matter of the "just do it" approach. But there are 3 things we should take with care even if our project seems outside the legacy project category. First of those are arrays.
Taking care of a software project long-term is not about immediate action but routine and regular checks. We have to know we're heading in the right direction; otherwise, we might end up trapped in a legacy project that is hard to get out of. When clients contact our [Rector team](https://getrector.com/hire-team) for help with their project, **they often share a few common treats**. I wish they…
Do you work with Symfony from 2 through 7? Then you know the main challenge in the upgrade path is to trim your YAML configs to a minimum. **Where we needed 300+ lines in configs, we are now good with 2**. How to get there fast and reliable? I'll show a trick we use in the PHP project to get there once and for all.
Do you use Symfony-console based tools? If you're using Composer, Rector, PHPSpec, PHPStan, ECS or [Class Leak](https://github.com/TomasVotruba/class-leak), you probably know the long list of commands you can use. In most of the times, most of command you read are not part of the real package. Do we really need them?
Framework migration is a challenge few choose to take - yet in some cases, it makes sense for business, project health, and pure joy from coding. Once you know [the recipe](https://getrector.com/blog/how-to-migrate-legacy-php-applications-without-stopping-development-of-new-features), it's clear the switch [is…
Do you want to migrate your Symfony project to Laravel and not sure if it "handles it"? Switching containers is pretty [straightforward](/blog/experiment-how-i-replaced-symfony-di-with-laravel-container-in-ecs) for the most parts. But can Laravel handle advanced features such as compiler passes?
When Nuno sent me a Pest plugin for type coverage that runs [TomasVotruba/type-coverage](https://github.com/TomasVotruba/type-coverage), I looked for the PHPStan container use. Why? Because the type-coverage package is PHPStan rules that easily plugin into PHPStan. But what if you want to use them in a tool that has a different container? I've found the solution the hard way - so it might be…
The famous [phploc](https://github.com/sebastianbergmann/phploc) package to measure project size was archived by Sebastian on Jan 10, 2023. I used this package to get feedback on [CLI apps vendor shrink](/blog/unleash-the-power-of-simplicity-php-cli-app-with-minimal-dependencies) and for [fast estimation of project size in Rector upgrades](https://getrector.com/hire-team). **That's why I needed a…
Last month I successfully [switched the Symfony container for Laravel one](/blog/experiment-how-i-replaced-symfony-di-with-laravel-container-in-ecs) in Easy Coding Standard. The tiny container is a joy to work with - it consists of 2 files I can read and understand all its features. **I wanted to put this package into pressure test**, so I migrated the project I work on daily -…
In the last post [I shared a trick](/blog/unleash-the-power-of-simplicity-php-cli-app-with-minimal-dependencies) on how to **reduce CLI project /vendor size by 70 %**. Today we'll trim off a bit more with the no-so-known composer feature.