I recently got a new opportunity to work with a company as a developer. One of my first tasks is to implement [PHP Static Analysis](https://phpstan.org) on an 8 year old-ish codebase. The task seemed daunting at first but I had 2 things going for me; first, they were using Laravel framework so we can easily pull up [Larastan](https://github.com/nunomaduro/larastan), and second, the codebase has good amount of tests which gave me a huge boost in confidence. In this blog, I aim to give you few tips when implementing static analysis on a legacy codebase, and explain to you some of the static analysis errors that I encountered and how I solved it. ## First Time If this is your first time implementing static analysis, I would suggest reading through the [PHPStan Documentation](https://phpstan.org/user-guide/getting-started) first before even installing it on your codebase. Their documentation is quite comprehensive so it should not take you very long. I would also suggest, though not necessary, to watch [Nuno Maduro's talk about Types in PHP](https://youtu.be/jObcE58UCB8) for better understanding of PHP types and Larastan. By now you should know that there are **rule levels** in PHPStan - from Level 0 to Level 9. Levels determine the strictness of the rules where 0 is the loosest and 9 is the strictest. If you're implementing it on a huge codebase you should **always start at Level 0** or you will get overwhelmed with too many errors to fix. ## Installation Installing [Larastan](https://github.com/nunomaduro/larastan) should be quick and easy, just follow through the documentation on the readme file in the repository. By now, you should have a configuration file within the root of your project directory. Mine is stored as `phpstan.neon` and looks more or less like this: ``` includes: - ./vendor/nunomaduro/larastan/extension.neon parameters: checkMissingIterableValueType: false noUnnecessaryCollectionCall: false reportUnmatchedIgnoredErrors: false # Paths to scan and analyse. paths: - app # The level: 9 is the highest level. level: 0 # Circle CI configuration. parallel: jobSize: 20 maximumNumberOfProcesses: 8 # List of errors to be ignored. ignoreErrors: - '#PHPDoc tag @var#' - '#Unsafe usage of new static#' # List of paths that are excluded. excludePaths: - tests/ ``` Once you've created the configuration file, just run `./vendor/bin/phpstan analyse`, wait for couple of seconds (might take longer depending on the size of your project) and it should show you the errors on your terminal. Let's go through some of the errors that I have encountered. ## Error Patterns ### Access to an undefined property App\Foobar::$baz. This error is self-explanatory. Within the class Foobar you are doing a `$this->baz` property call. However the property is not actually declared in the class. Although PHP allows this through dynamic property, **static analysis is protecting you from making unexpected property calls to an object**. ***Fix: Just declare the property on the class or remove it entirely.*** ### Method App\Foobar::handle() should return int but return statement is missing. Another self-explanatory error. The method `handle()` within class Foobar is expected to return something but there is no return statement. This is usually because there is a doc block above the method declaration. ```php /* * @return int */ public function handle() { // Some code without return statement... } ``` ***Fix: Add a return statement or remove the doc block.*** ### Relation 'user' is not found in App\Models\Post model. This error was a bit tricky at first, because it would still appear even though the relation `user` has been declared on the `App\Models\Post` model class. ***Fix: Add a return type on the relation method.*** ```php // In App\Models\Post class... use Illuminate\Database\Eloquent\Relations\BelongsTo; public function user() : BelongsTo // Add this.. { return $this->belongsTo(User::class); } ``` ### Deprecated in PHP 8.0: Required parameter $foo follows optional parameter $bar. Another self-explanatory error and is obviously an issue only on PHP 8.0 and up. Basically, what's happening is that there is a method within your class that looks something like this. ```php public function something($bar = null, int $foo) { // ... } ``` As you can see, `$bar` is an optional parameter while `$foo` is not. Required parameters should be at the left of optional parameters. ***Fix: Refactor the method and its usages to make sure required parameters are on the left.*** ### app/Console/Commands/Foo.php: Result of method Illuminate\Console\Command::error() (void) is used. This is because in the `handle` method of the `Foo.php` class, we have this call ```php public function handle() { // Some codes... return $this->error('...'); } ``` The issue is that the result of `$this->error()` call is void type and therefore should not be used as a return statement. ***Fix: Update return statement with the correct integer code.*** And that's pretty much all of the error patterns I have encountered implementing level 0 static analysis. On the first run, there were 380 errors found with these patterns on different parts of the codebase. Hopefully you are starting to realize the benefits and protection static analysis can give you at the very beginning. I will continue this article once I get to work on the next levels. Cheers!
Published Articles
One of the easiest and most common techniques you can reach out for when refactoring your code is a **Lookup Table**. A lookup table is basically just a table, can be objects, arrays, database tables, redis cache or whatever, that you can look in to find values. Let's take a look at some common refactoring examples. # Refactoring multiple 'OR's For our first example, let's say that we have a `User` model that has a `type` property. ``` // Imagine you have something like this... if ($user->type === 'administrator' || $user->type === 'teacher' || $user->type === 'guardian') { // Do something here... } ``` You can image if in the future we need to add more user type that the condition could get long. This is one perfect candidate where we can use lookup tables. Take a look... ``` // Put our values in a lookup table, then proceed with our condition... $table = ['administrator', 'teacher', 'guardian']; if (in_array($user->type, $table)) { // Do something here... } ``` We've refactored to a lookup table, in this case an array, to group our possible values. And then used the lookup table in our condition to check against the user type. With this refactor, should we need to add more user types, all we have to change is our lookup table. This is what we call **isolating the change**. And with Laravel, we can refactor this further to be much more readable, like so... ``` $table = ['administrator', 'teacher', 'guardian']; if(collect($table)->contains($user->type)) { // Do something here... } ``` Using the collection the condition becomes much more clearer that we are checking if the table contains the given user type. 👌 # Imperative vs Declarative Programming Using the same example above, imagine a scenario that when you login a user, you need to redirect them to their dedicated homepage based on their user type. You would probably have something like this... ``` public function redirectUser($user) { if ($user->type === 'administrator') { return redirect('/administrator/dashboard'); } elseif ($user->type === 'teacher') { return redirect('/teachers/schedules'); } elseif ($user->type === 'guardian') { return redirect('/guardians/learners'); } } ``` As you can see, this approach is kind of describing the step by step on how to determine where to redirect the user based on their type. When you are specifying the exact steps to get the results, this is what we call **imperative programming**. Now maybe your first instinct is to refactor using switch statements. While that would definitely work, I would say switch statements would still somehow describe the step by step of the process. Using a lookup table would be much more cleaner. Let's take a look... ``` public function redirectUser($user) { $homepages = [ 'administrator' => '/administrator/dashboard', 'teacher' => '/teachers/schedules', 'guardian' => '/guardians/learners', ]; return redirect($homepages[$user->type]); } ``` As you can see with this approach, its much more results-focused. It is able to determine the outcome without having to describe the process step by step. This is what we call **declarative programming**. 😎 Generally speaking, it's easier for human brain to describe step by step approach instead of results-based approach especially when writing alorithms. This is why declarative programming is often a result of a refactor. # As Strategy Pattern This is almost the same as the last example, but you can use lookup tables to determine strategy that you can use for certain logic. To illustrate, using the same user type example above, imagine if we have different strategies for processing payments based on the user type... ``` public function processPayment($user) { $handler; if ($user->type === 'administrator') { $handler = new BankPaymentHandler; } elseif ($user->type === 'teacher') { $handler = new GcashPaymentHandler; } elseif ($user->type === 'guardian') { $handler = new PaymayaPaymentHandler; } // Assuming the handler classes are abiding by a contract with // a process method that accepts an instance of the user... return $handler->process($user); } ``` Again, we can refactor to a more declarative programming approach using a lookup table, like so... ``` public function processPayment($user) { $strategies = [ 'administrator' => BankPaymentHandler::class, 'teacher' => GcashPaymentHandler::class, 'guardian' => PaymayaPaymentHandler::class, ]; return (new $strategies[$user->type])->process($user); } ``` Hope you've learned how to use lookup tables to refactor your code. Happy coding!
Here are some straight-forward and practical tips on how to make your PHP code beautiful. # Give it some space As romantically-cliche as it may sound, sometimes all your code needs is a bit of space. A little bit of breathing room so you and your team can easily read it better. And as basic as it may seem, many, both new and experienced developers tend to still forget the idea of adding spaces on their code. Take a look at these code blocks below... ``` public function test_can_upload_avatar() { $this->user = User::factory()->create(); $avatar = UploadedFile::fake()->image($filename = 'avatar.jpeg'); $this->post(route('user.profile.avatar'), ['avatar' => $avatar]); $this->assertDatabaseHas('media', [ 'model_type' => $this->user->getMorphClass(), 'model_id' => $this->user->id, 'file_name' => $avatar->hashName(), ]); $this->assertNotNull($this->user->avatar->first()); $this->assertInstanceOf(Media::class, $this->user->avatar->first()); } ``` ``` public function test_can_upload_avatar() { $this->user = User::factory()->create(); $avatar = UploadedFile::fake()->image($filename = 'avatar.jpeg'); $this->post(route('user.profile.avatar'), ['avatar' => $avatar]); $this->assertDatabaseHas('media', [ 'model_type' => $this->user->getMorphClass(), 'model_id' => $this->user->id, 'file_name' => $avatar->hashName(), ]); $this->assertNotNull($this->user->avatar->first()); $this->assertInstanceOf(Media::class, $this->user->avatar->first()); } ``` They are obviously the same piece of code. I don't know about you, but just eye-balling these code blocks, I definitely find the latter much easier to take in and understand because of its ample spacing. Like, I don't feel the need to bring my face closer to the screen of my laptop just to be able to understand what each line of code does, if you know what I mean. As a rule of thumb, try to put each statement (anything that ends with a semi-colon) on its own line unless 2 or more lines are doing the same type of action (i.e. the assertions on the example above, assigning variables, etc.). # Use PHP CS Fixer One advantage PHP has over other programming languages is that we have our own set of coding standards that developers generally agree upon. The [PHP Standards Recommendation or PSR](https://www.php-fig.org/psr/) are set of recommendations for styling our code. From **Basic Coding Standard**, **Coding Style** and **Autoloading Standards** the PSR has you covered so you don't have to invent your own. Another good thing is that you can automate the implementation of these standards on your IDE using the [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) tool. Modern IDEs like Sublime Text, PhpStorm and VS Code have their own packages that you can just download from their respective package managers to set this up. All you need to do is to configure the set of rules that may want to implement for you or your team. If you have no idea on what rules to implement, here's a link to a [gist of set of rules that I personally use in my projects](https://gist.github.com/ambengers/c112d75bb7e14b41daf99dc2abad7690). I have mine set to auto run whenever I save the PHP file so that I am sure that these standards will always apply no matter what. You can also refer to this [PHP-CS-Fixer Cheat Sheet](https://mlocati.github.io/php-cs-fixer-configurator/#version:3.8) if you want to check on the meaning and effect of each rule. # Leave Meaningful Comments Another very basic yet many-tend-to-forget way of beautifying their code and helping themselves and/or their teammates understand their code better is by using comments. Not just comments, meaningful comments. Comments that actually makes sense when we you read it. I personally believe that writing meaningful comments actually reflects how much care a developer have put into their code. This is actually one reason why Laravel framework is so popular. And like-minded developers tend to gravitate towards it, developers who actually care for their code. Personally, as a rule of thumb, I leave comments on areas that I feel like, **if I don't remember the full context of how the code works, it will be quite hard for me to understand it again if I read it 6 months from the time that I wrote it**. I also try to follow Laravel's way of writing comments - where each line is a couple of characters shorter the the one above it.
In this post I will show how we can transform enums to array format and dynamically append attributes to it. Disclaimer: This article is a continuation to the [Supercharged Backend Enums with Collection](https://marvinquezon.com/posts/supercharged-backed-enums-with-collection) post that I've previously published. If you haven't already, I would highly recommend reading through that article first as we will continue with the examples from there. Now that that's out of the way, let's begin. ***Enums, as objects, are plain and boring!*** Enums as objects are plain and boring - because they can never hold other properties except `name` and `value` of each case. PHP will actually throw an error when you try to declare properties within enum classes. However, there are times that you would want to append additional properties to an enum case. For instance, going back to our `TicketStatus` enum class, imagine that for each status, we also want them to have a `color` attribute. So that whenever our frontend consumes it, we can use it to design maybe the background color of the tickets based on the status. This is very useful since the enum class is becoming the single source of truth for the ticket status. # Create a method and match each case First, let's see how we can assign a color for each enum case: ```
In this post, I am going to talk about my personal opinion regarding DRY, KISS and YAGNI versus Coding Standards especially when working in a team setting. I understand that you might not agree on some (or maybe even all) of the things that I have written here and I respect that. Everyone is entitled to their own opinion. So I encourage you all my readers, to look at this article with an objective perspective and a broader-than-usual mindset. So with the disclosure out of the way, let me get to the point. Always prioritize DRY, KISS and YAGNI over Coding Standards - even if you are in a team setting. This comes with one condition however, that you need to have a good amount of test coverage so you have confidence in case that you need to refactor your code in the future. **DRY *(Don't Repeat Yourself)*** - means that each small pieces of knowledge (code) may only occur exactly once in the entire system **KISS *(Keep It Simple, Stupid!)*** - means to try to keep each small piece of software simple and unnecessary complexity should be avoided to not incur any technical debt. **YAGNI *(You Ain't Gonna Need It!)*** - means that always implement things when you actually need them and never implement things before you need them. I personally think these principles trump any coding standards a team might implement. And I am willing to go above and beyond that and say **DRY, KISS and YAGNI should be the guiding principles of teams when implementing Coding Standards**. ## Story Time! I have recently started working as a Lead Developer in a team, and one of their Coding Standard is to **always use a FormRequest class** in the controller. So here I am, working on a feature and I have one endpoint that only receives one parameter on the request, which is already covered with written tests. Also, mind you that this request parameter is only used for this endpoint and not anywhere else in the application. ``` public function update(Request $request) { $request->validate(['processed' => 'required|boolean' ]); // Some other process here then return a json response... } ``` Now, I am being told to refactor this into a `FormRequest` class. So I asked, why? Because I honestly don't see the need to. Why do I have to incur additional tech debt by adding a separate class that will only be used for validating a single request attribute? And this piece is not going to be used in any other place,so i'm not violating code duplication. What gives? KISS and YAGNI! ***But we have to follow Coding Standards!*** This is a classic example of letting the developers incur additional techical debt without any benefit at all, just for the sake of Coding Standards. In my most honest opinion, when the code is well-tested, readable and at its simplest form; when it can be easily changed when the need arise, then that is a good code. And I understand the need to be one with the team you are working with, but programming is not a military industry but a creative one. And the possible amount of incurred technical debt when blindly following coding standards far outweighs that argument. And most of the time, that incurred technical debt is never paid. I have seen it multiple times. Also, developers grow when they get to figure out their ways of deciding when to use certain methodologies and tools, and when to keep things simple. That decision can then be guided by the team during a code review session. And again, as long as the code is covered with tests, refactoring should never be a big concern.