DEV Community

Cover image for Late Night Refactors #1: Composer Packages
Thanos Stantzouris
Thanos Stantzouris

Posted on • Originally published at sudorealm.com

Late Night Refactors #1: Composer Packages

This is the start of something I have never done before!
I welcome you all to the beginning of my first blog post series: Late Night Refactors 🎉🥳

In this series of X blog posts, I will share the great journey of refactoring (duh...) **Sudorealm* from scratch. I'll take it apart step by step and improve it from the ground up.

Sudorealm is a side project, so mainly, I am coding it during the times when the sun is no longer shining, and these are also the times that I'll be writing these reactors.

Stay with me now! Many were the times that I rewrote Sudorealm so that it could accommodate better features, better technologies, and whatnot. But this time is different! This time, I am bringing you along for the ride. I’ll share everything so you can either apply it to your own projects or maybe even spot a refactor I didn’t think of! HAHA!

Jokes aside, the real goal here is to build a sustainable, future-proof codebase, one that others can contribute to and help maintain. In short... I’m open-sourcing Sudorealm! There, I said it! I’m finally unleashing my monster upon the world! After the final refactor, of course. ❤️ Hopefully, we’ll both learn something in the process!

it's alive! Gif

Ready! Set! Go! 💥

Alright, the first thing I need to refactor is the very foundation of how I write code... duun dunduuun!

I need to ensure that Sudorealm stands on a clean, consistent, and maintainable codebase. That means adopting the right tools to improve code quality, enforce standards, and catch potential issues before they become nightmares on Realm Street 👻.

This is where Pint, Rector, Larastan, and Pest come into play.

  • 🖌 Pint will keep my code style clean and consistent, so I don’t waste time formatting manually.
  • 🛠 Rector will help automate code upgrades and refactors, making my life easier as the project evolves.
  • 🔍 Larastan will act as my strict type-checking guardian, catching subtle bugs that I’d otherwise miss.
  • 🧪 Pest will bring modern, expressive, and Laravel-friendly testing to ensure everything works as expected.

By setting up these tools from the start, I’m laying a strong foundation for future development—because a good refactor isn’t just about changing code, it’s about writing better code.

Now, let’s get our hands dirty and start transforming Sudorealm!

Laravel Upgrade

Before diving headfirst into refactoring, the first step was upgrading Sudorealm to the latest version of Laravel 11 at the time. Why? Because a proper refactor isn’t just about cleaning up code, it’s about keeping up. Staying on the latest version ensures:

  1. Security 🔒 – Outdated software is a hacker’s playground. Keeping Laravel up to date means patching vulnerabilities before they become real threats.
  2. New Features ✨ – The ecosystem moves forward, and so do the best packages. Sticking to an old version means missing out on powerful new features and improvements.

A neglected codebase is like an abandoned house, it doesn’t fall apart overnight, but one day, you’ll wake up to find it unlivable.
~ Thanos Stantzouris

Ok, now, let’s get to work! 💻🔥

How it went

It wasn't as easy as I thought, I had to tackle some breaking changes here and there but thankfully Laravel has a pretty neat upgrade guide to follow through.
The main headache, though, was the sync between already installed packages. I needed to find and upgrade all the latest versions of the packages I use. ChatGPT really helped with this process. Sometimes, the composer errors can be a bit scary, but ChatGPT fearlessly explains the steps you have to take clearly.

Weirdly, though, after I successfully ran composer update, I couldn't find any bugs in the app. Oh well. On to the next one!

Keeping Code Consistent with Pint

My biggest challenge while coding Sudorealm was dealing with code inconsistencies between features, caused by the long breaks I took between development sessions. I know you can relate to this! This is where laravel/pint 🍺 comes to play.

What is Pint?

Pint is an opinionated PHP code style fixer for Laravel applications, built on top of PHP-CS-Fixer but with Laravel community standards in mind.
It automatically formats your PHP code based on Laravel’s default coding style. It helps enforce clean, readable, and maintainable code without the need to manually fix style issues.

Installation

composer require laravel/pint --dev
Enter fullscreen mode Exit fullscreen mode

You can see the entire documentation, but I am gonna present to you how I'm about to be using it:

Inspecting code

./vendor/bin/pint --test
Enter fullscreen mode Exit fullscreen mode

You see, since I am installing pint to the project now there will be like a gazillion fixes if I run it immediately. I want to be able to keep track of the files I refactor so it's preferable for me to only inspect the code in chunks, like this for example:

./vendor/bin/pint --test app/Http/Controllers
Enter fullscreen mode Exit fullscreen mode

Setup

I am using a strict pint setup for my project. In general, I want to take my coding skills to a lower level Go, C, Rust, as time passes by, so I believe it is good to be strict with my PHP coding.

{
    "preset": "laravel",
    "rules": {
        "array_push": true,
        "backtick_to_shell_exec": true,
        "date_time_immutable": true,
        "declare_strict_types": true,
        "lowercase_keywords": true,
        "lowercase_static_reference": true,
        "final_public_method_for_abstract_class": true,
        "fully_qualified_strict_types": true,
        "global_namespace_import": {
            "import_classes": true,
            "import_constants": true,
            "import_functions": true
        },
        "mb_str_functions": true,
        "modernize_types_casting": true,
        "new_with_parentheses": false,
        "no_superfluous_elseif": true,
        "no_useless_else": true,
        "no_multiple_statements_per_line": true,
        "ordered_class_elements": {
            "order": [
                "use_trait",
                "case",
                "constant",
                "constant_public",
                "constant_protected",
                "constant_private",
                "property_public",
                "property_protected",
                "property_private",
                "construct",
                "destruct",
                "magic",
                "phpunit",
                "method_abstract",
                "method_public_static",
                "method_public",
                "method_protected_static",
                "method_protected",
                "method_private_static",
                "method_private"
            ],
            "sort_algorithm": "none"
        },
        "ordered_interfaces": true,
        "ordered_traits": true,
        "protected_to_private": true,
        "self_accessor": true,
        "self_static_accessor": true,
        "strict_comparison": true,
        "visibility_required": true
    }
}
Enter fullscreen mode Exit fullscreen mode

Now look... this might come to bite me in the future... But let's worry about the future when it finally comes. Check out Pint Strict Presets and if you want to learn what each rule does. Well, I am not telling you... go find out here PHP Coding Standards Fixer

Styling Code

I will add this GitHub pre-commit hook so that pint can inspect and change the files that I 🤬 up.

 #!/bin/sh

 # Run Laravel Pint
 # This script will run Laravel Pint on newly staged PHP Files. 

 files=$(git diff --cached --name-only --diff-filter=AMCR | grep "\.php$")
 if echo "$files" | grep --quiet "\.php$"; then
     echo "Running Laravel Pint..."
     ./vendor/bin/pint --dirty
 fi
Enter fullscreen mode Exit fullscreen mode

With this hook pint styles only the files that are about to be committed. So the process of a push would be like this.

 - git add . # stage all the files that you changed
 - git commit -m 'feature: best feature ever bro!' # commit the files
 # if pint fixes files. 
 - git commit --amend --no-edit # commit pint changes to previous commit
 - git push # push to prod and break it
Enter fullscreen mode Exit fullscreen mode

Why do I use Pint in my project?

  • Zero configuration required – It works out of the box with Laravel's style guide.
  • Automation– No more manually fixing indentation, spaces, or alignment.
  • Better collaboration – Ensures consistent formatting across all team members.
  • Integration-friendly – Works well with Git hooks and CI/CD pipelines.

Goodbye wasting time on manual formatting.

Automating Code Upgrades with Rector

One of the biggest pains in maintaining Sudorealm, or any project, to be honest, is keeping the codebase up to date, especially when upgrading versions or adopting new best practices. This is where Rector comes into play.

What is Rector?

Rector is an automated refactoring tool for PHP that can upgrade your codebase, apply modern best practices, and even transform legacy code with predefined rules. Instead of manually updating syntax or refactoring large chunks of code, Rector does the heavy lifting for you.

Installation

composer require rector/rector --dev
Enter fullscreen mode Exit fullscreen mode

Setup

Following the rector docs I created a rector.php in my root directory and I inserted the following rules:

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__.'/app',
        __DIR__.'/bootstrap/app.php',
        __DIR__.'/database',
        __DIR__.'/public',
    ])
    ->withPreparedSets(
        deadCode: true,
        codeQuality: true,
        typeDeclarations: true,
        privatization: true,
        earlyReturn: true,
        strictBooleans: true,
    )
    ->withPhpSets();

Enter fullscreen mode Exit fullscreen mode

Breaking It Down:

  • withPaths([...]): Tells Rector which parts of the project to scan and refactor. In this case:
  • The app/ directory (core application logic)
  • The bootstrap/app.php file (app setup)
  • The database/ directory (migrations, seeders, factories)
  • The public/ directory (entry point, assets, etc.)
  • withPreparedSets([...]): Activates predefined rule sets that help clean up and improve the codebase:
  • Dead Code – Removes unused variables, methods, and classes.
  • Code Quality – Applies general best practices for cleaner, more maintainable code.
  • Type Declarations – Ensures type hints (int, string, etc.) are correctly applied.
  • Privatization – Converts class properties and methods to private where possible.
  • Early Return – Rewrites if conditions to reduce nesting and improve readability.
  • Strict Booleans – Forces explicit true/false checks instead of loose comparisons.
  • withPhpSets(): Applies PHP version-specific upgrades to keep the code modern.

I ran ./vendor/bin/rector and I got changes like:

1) app/Listeners/User/AwardBadgeAfterUserVerified.php:34

    ---------- begin diff ----------
@@ @@
             $this->badgeAwardService->awardBadgesToUser($user, BadgeTypeEnum::USER);
         } else {
             Log::warning('Verified event user is not an instance of App\Models\User', [
-                'user_type' => get_class($user),
+                'user_type' => $user::class,
             ]);
         }
     }
    ----------- end diff -----------
Enter fullscreen mode Exit fullscreen mode

How cool is that? Now I know that I can get the class from a model instance just by doing $modelVar::class.
I mean I, kinda, already knew that, but If I didn't, now I do... You get it... 😂

I can also do this:

./vendor/bin/rector --dry-run
Enter fullscreen mode Exit fullscreen mode

and only view the changes that are about to be made. It may be better this way for now because I want to get my hands dirty so that I won't make the same mistakes again in the future. Does that make sense?

Maybe I'll add that command as well to the previous pre-commit hook and see the whole world burn whenever I am doing a new commit, maybe that way I'll force myself to do smaller commits.

Cool video from Nuno Maduro about rector: Why You Should Start Using Rector PHP Today

Why Use Rector in My Project?

  • Effortless Laravel Upgrades – Rector provides Laravel-specific rules to help migrate between framework versions with minimal manual work.
  • Consistent Refactoring – It enforces best practices and modern coding styles automatically.
  • Saves Time – No need to manually track and update deprecated code.
  • Highly Customizable – You can define your own set of refactoring rules to match your project’s needs.

So now not only do I have cool formatting, but I also have a coder buddy fixing up my stupid no-brain code and keeping it up to date with the latest standards! Yes pleaseeee! What's next?

Making Sure It All Works with Larastan

Automating refactoring with Rector ensures my code is modern and follows best practices, but that doesn’t mean it's error-free. Even small changes can introduce type mismatches, undefined properties, or incorrect method calls—issues that might not break the app immediately but could cause hard-to-debug failures later.

Installation

composer require --dev "larastan/larastan:^3.0"
Enter fullscreen mode Exit fullscreen mode

Setup

Then, the docs tell us to create a phpstan.neon file in the root of your application. This file defines the analysis level and rules Larastan should follow. Place it in your project root:

Mine looks like this:

includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/nesbot/carbon/extension.neon

parameters:

    paths:
        - app/

    # The level 9 is the highest level
    level: 2
Enter fullscreen mode Exit fullscreen mode

I am going with level: 2 because I want to work my way up to 9. And I am pretty sure that 9 is an overkill. 🤓

./vendor/bin/phpstan analyse #--memory-limit=2G
Enter fullscreen mode Exit fullscreen mode

Even with level:2 I got 70 errors...

my image saved on sudorealm.com

Why Use Larastan in My Project?

  • Works with Existing Code – No need to rewrite everything; just start analyzing and improving step by step.
  • Detects Logical Errors – Finds undefined properties, incorrect method calls, wrong return types, etc.
  • Understands Laravel's magic – Knows about Laravel methods, facades, container resolution, etc.
  • Can create custom rules – When I face the need to enforce a specific rule in my codebase, I can create a custom rule that checks that.

You can also follow the Readme to learn more about Larastan.

Rector and Larastan - B.F.Fs.

While Rector improves your code automatically, Larastan ensures your code is correct. They complement each other perfectly, and using both saves time, reduces bugs, and makes upgrading Sudorealm smoother.

From Static Analysis to Real-World Testing

While Larastan will help me significantly catch potential issues before runtime, it doesn't prove that my app is in fact working as I want it to. I mean, look! It may work and load completely fine, with no bugs, server errors and all... But, there is a possibility that my business logic might break from these automated changes! No program will ever be able to comprehend exactly my business logic, even if it's the simplest one.

Static analysis can only guess potential failures based on type mismatches and method signatures. To be truly confident in my code, I need real tests that simulate user interactions, validate business logic, and check for regressions.

That’s why the next step in my workflow is Pest—a modern, elegant testing framework for Laravel built by the legend, Nuno Maduro. Let’s talk about how testing ensures my app isn’t just "correct" in theory, but actually works as expected.

Pest

I left this for the end because, in my opinion, it's the most important addition to the toolbelt.

Installation

composer remove phpunit/phpunit
composer require pestphp/pest --dev --with-all-dependencies

./vendor/bin/pest --init
Enter fullscreen mode Exit fullscreen mode

Finally, you can run your tests by executing the pest command.

./vendor/bin/pest
Enter fullscreen mode Exit fullscreen mode

There is also a way to migrate all your tests from PHPUnit to Pest. But I have ZERO tests... so... 😅

Why Choose Pest over PHPUnit

Firstly, let’s address the elephant in the room, Pest is PHPUnit. It’s not a replacement but rather a wrapper that enhances the developer experience. Asking "Why Pest over PHPUnit?" is similar to asking "Why Laravel over vanilla PHP?", both provide a smoother, more elegant way to achieve the same results.

Pest was designed from the ground up for simplicity and ease of use, offering a modern, expressive, and fun way to write tests in PHP. It removes the clutter of traditional PHPUnit syntax, making tests feel more natural and readable.

Cleaner, More Expressive Syntax
Pest simplifies the verbosity of PHPUnit, reducing boilerplate. Instead of writing:

//this function
public function test_example()
{
    $this->assertTrue(true);
}

//can be written like this in Pest
test('example')->assertTrue(true);
Enter fullscreen mode Exit fullscreen mode

This is not a very real world. But it's just an example of the verbosity and power that Pest gives.

Built for Laravel

Pest was created by Nuno Maduro, a core Laravel team member. As a result, it has first-class support for Laravel testing, with features like:

  • API testing
  • Model factories
  • Artisan commands
  • Livewire support ( main differentiator for me )

Pest Plugins for Laravel-Specific Features

Pest has some crazy cool plugins that gives us tools to make our life easier:

composer require pestphp/pest-plugin-laravel --dev
Enter fullscreen mode Exit fullscreen mode

now I can create tests like:

php artisan pest:test UsersTest
Enter fullscreen mode Exit fullscreen mode

And many more helpful commands that I'll surely discover during this crazy refactor odyssey.

Pest even provides official plugins to make Livewire testing even smoother:

composer require pestphp/pest-plugin-livewire --dev
Enter fullscreen mode Exit fullscreen mode

So yeah, these are the main reasons why I went with Pest, even though my team works with PHPUnit. Who knows, maybe in the future, I'll manage to convince them to change to Pest... Or I'll figure out that PHPUnit was the best choice after all! 🫣

Fun Fact: In LaraconEU 25, I met both the creators of PHPUnit and Pest 😅

Learn more about packages

These packages surely have some overhead, setup, and getting used to but I believe they are the barebones of this refactor, cause they will be the guide towards a safer and more structured project.

Conclusion

To wrap things up, I want to reiterate why I started this refactor series. After attending Laracon EU, I realized the importance of sharing my code, not just to help others and expand the possibilities of open-source development, but also to collaborate more and continue learning myself.

I don’t, by any means, claim to be the best developer I can be yet, nor that this refactor is some kind of holy grail of software development, this is all part of the journey to improve. By putting my work out there, I hope to contribute, grow, and refine my skills alongside the community.

In the future, I know I’ll return to Sudorealm’s codebase and spot new things to improve, and that thought excites me! It’ll be proof that I’ve grown as a developer. But next time, there will be a difference: the project will be open-source, exposed to the sharp, vulture-like eyes of all you insanely talented coders out there.
So please bear with me, hopefully, there will be people out there who will get something out of this lunacy that possesses me.

I'd like to end this article with my favorite photo of LaraconEU 25 that essentially sparked the fire in me to go through with this series of articles!

Aaron Francis LaraconEu 2025

Catch this part of the talk here.
Thank you Aaron Francis 🙏

🚀 Spread the Love & Support the Realm

If you enjoyed this post and want to help improve the project, consider these quick ways to contribute:

  • 🛍 Affiliate Treasures Below – Check the list of cool gadgets below 👀
  • ☕️ Coffee Driven Development: We are nothing but coffee-to-code machines! BuyMeACoffee

Spread the Love

👑 Crown & Share: If you found value in this post, please give it a crown and share it with your fellow coder/hacker enthusiasts. Spreading knowledge is what Sudorealm is all about! Fun fact the Author with the most crowns inside a realm will be crowned as the Realm King! 🤴

🆇 X Shoutout: Feeling extra grateful or have some cool feedback? Drop me a shoutout on Twitter – I'd love to hear from you! d3adR1nger on X

💬 Join our Discord Server: Join the Sudorealm Discord Server

Thanks for being a part of our realm. Every bit of support propels our community to new horizons. Until next time, keep exploring!

Top comments (0)