Redesigning Laravel.io

In this blogpost by Tailwind CSS co-creators Adam Wathan and Steve Schoger share lots of actionable tips to improve the layout of your site.

Laravel.io is a forum and community portal for developers who use the Laravel PHP framework.

They recently launched a brand new version of the site, but being a volunteer-driven open source project, finding the resources to put together a really polished design can be tough.

We asked and got the go ahead to try our hands at a bit of a facelift, so let’s see what we can do to take the UI up a notch!

https://medium.com/refactoring-ui/redesigning-laravel-io-c47ac495dff0

Read more

Airplanes and Ashtrays

Harry Roberts makes the case for making technical debt visible instead of trying to hide it.

This means that, although far from ideal, the impact of these hacks is well contained and signposted, meaning that they won’t slip through the cracks and remain hidden in the codebase for the next five years.

This pragmatism and lack of stubbornness can make your codebase much more malleable, resilient, and ultimately much more useful. Everything is a compromise.

https://csswizardry.com/2017/10/airplanes-and-ashtrays/

Read more

Join 9,500+ smart developers

Get my monthly newsletter with what I learn from running Spatie, building Oh Dear, and maintaining 300+ open source packages. Practical takes on Laravel, PHP, and AI that you can actually use.

No spam. Unsubscribe anytime. You can also follow me on X.

Cruddy by design

At this year's Laracon US Adam Wathan gave a talk titled "Cruddy By Design" on how to structure your controllers better. After the conference he published a new GitHub repo that contains the demo app he refactored on stage. The 4 main tips to improve your code come as PRs on the repo with a full description on why the change is valuable. Very cool stuff.

Using this convention as a "rule" is a good way to force yourself to keep your controllers from becoming bloated, and can often lead to learning interesting new things about your domain.

For the presentation, I put together a demo app called "CastHacker" that showcases podcasts about software development. It's not a "real" app by any means (lots of imaginary features, no tests, etc.); it's just enough code to demonstrate the concepts from the presentation. Feel free to clone it and play with it locally if you like though.

I've written up each refactoring I shared in the presentation as a detailed pull request.

https://github.com/adamwathan/laracon2017

Read more

The broken windows theory or “Why some projects are just destined to suck”

In a new post on his blog, my favorite stalwart of the industry Frederick Vanbrabant, gives a explanation on why some projects turn into a big mess and how you can avoid it.

I truly believe that the broken window theory can be applied to software projects. In my personal experiences I’ve rarely seen a project start out as a total mess. If it ended up as a mess, it was gradually. I also believe that this is not necessary the fault of developers working on the project, think of it more as frogs in a pot with gradually increased temperature of water. One morning you just wake up and take a look at the project and realise that it has gotten really messy.

http://frederickvanbrabant.com/2017/06/12/broken-windows-theory.html

Read more

Moving from PHP (Laravel) to Go

Danny Van Kooten did an interesting experiment. He completely rewrote an Laravel app to a version in Go. In a post on his blog he shares some details about his project along with some benchmarks.

Earlier this year, I made an arguably bad business decision. I decided to rewrite the Laravel application powering Boxzilla in Go.

No regrets though.

Just a few weeks later I was deploying the Go application. Building it was the most fun I had in months, I learned a ton and the end result is a huge improvement over the old application. Better performance, easier deployments and higher test coverage.

https://dannyvankooten.com/laravel-to-golang/

Read more

Making overloaded functions readable

Sometimes you might allow a function to accept multiple data types. I don't know for certain if it's the correct term but for the remainder of this post I'm going to call such a function overloaded. In this post I'd like to show you a little trick to make overloaded functions more readable.

Let's first take a look at a function in Laravel that accepts multiple data types. To store something in a session you can pass a key and value to the session helper:

session($key, $value);

But you can also give it an array:

session(['key' => 'value']);

Now behind the scenes Laravel is calling a put function. It could have been implemented like this:

public function put($key, $value = null)
{
    if (is_array($key)) {
       foreach ($key as $arrayKey => $arrayValue) {
           $this->set($arrayKey, $arrayValue);
       }
    }
    else {
       $this->set($key, $value);
    }
}

In the function above there's a path for doing the work if an array was passed and another path for when (hopefully) a string was passed.

The actual implementation is a bit different (and much better):

public function put($key, $value = null)
{
    if (! is_array($key)) {
        $key = [$key => $value];
    }

    foreach ($key as $arrayKey => $arrayValue) {
        $this->set($arrayKey, $arrayValue);
    }
}

The cool thing to note is that what the function first converts the passed arguments to a certain format (in this case an array) and then perform the work on the format. The actual work, the call to $this->set is only coded up once. When reading the source code of Laravel you'll often come across this pattern.

Let's take a look at another real life example to make the benefit of this pattern more clear. This next snippet is taken from a recent PR to the laravel-permission package. It aims to a add a query scope to a User model to perform the query only on users that have the given role(s). Roles can be passed through as an array, a string or an instance of Role.

/**
 * Scope the user query to certain roles only.
 *
 * @param string|array|Role|\Illuminate\Support\Collection $roles
 *
 * @return bool
 */
public function scopeRole($query, $roles)
{
    if (is_string($roles)) {
        return $query->whereHas('roles', function ($query) use ($roles) {
            $query->where('name', $roles);
        });
    }

    if ($roles instanceof Role) {
        return $query->whereHas('roles', function ($query) use ($roles) {
            $query->where('id', $roles->id);
        });
    }

    if (is_array($roles)) {
        return $query->whereHas('roles', function ($query) use ($roles) {
            $query->where(function ($query) use ($roles) {
                foreach ($roles as $role) {
                    if (is_string($role)) {
                        $query->orWhere('name', $role);
                    }

                    if ($role instanceof Role) {
                        $query->orWhere('id', $role->id);
                    }
                }
            });
        });
    }

    return $query;
}

The query is being build up in a few different ways depending on the type of the argument being passed through. The readability of this code can vastly be improved by:

  • first converting all arguments to a single format to work with
  • performing the work on that format
public function scopeRole($query, $roles)
{
    if ($roles instanceof Collection) {
        $roles = $roles->toArray();
    }

    if (! is_array($roles)) {
        $roles = [$roles];
    }

    $roles = array_map(function ($role) {
        if ($role instanceof Role) {
            return $role;
        }

        return app(Role::class)->findByName($role);
    }, $roles);

    return $query->whereHas('roles', function ($query) use ($roles) {
        $query->where(function ($query) use ($roles) {
            foreach ($roles as $role) {
                $query->orWhere('id', $role->id);
            }
        });
    });
}

In the snippet above all input, no matter what type is being passed through, is first converted to an array with Role objects. U sing that array the query is only being build up once. I should mention that the author of the PR did provide a good set of tests so it was very easy to refactor the code.

Let's do one more example. This one's taken from our laravel-fractal package which aims to make working with Fractal more developer friendly.

To return a response with json data you can to this in a Laravel app.

$books = fractal($books, new BookTransformer())->toArray();

return response()->json($books);

In the last version of our package a respond() method was added. Here's the equivalent code using the respond method.

return fractal($books, new BookTransformer())->respond();

You can pass a response code as the first parameter and optionally some headers as the second

return fractal($books, new BookTransformer())->respond(403, [
    'a-header' => 'a value',
    'another-header' => 'another value',
]);

You can also set the status code and the headers using a callback:

use Illuminate\Http\JsonResponse;

return fractal($books, new BookTransformer())->respond(function(JsonResponse $response) {
    $response
        ->setStatusCode(403)
        ->header('a-header', 'a value')
        ->withHeaders([
            'another-header' => 'another value',
            'yet-another-header' => 'yet another value',
        ]);
});

This is original code for the function that was submitted through a PR (slightly redacted):

public function respond($callbackOrStatusCode = 200, $callbackOrHeaders = [])
{
    $response = new JsonResponse();

    $response->setData($this->createData()->toArray());

    if (is_callable($callbackOrStatusCode)) {
        $callbackOrStatusCode($response);
    } else {
        $response->code($callbackOrStatusCode);

        if (is_callable($callbackOrHeaders)) {
            $callbackOrHeaders($response);
        } else {
            $response->withHeaders($callbackOrHeaders);
        }
    }

    return $response;
}

Sure, that code does the job. Unfortunately the real work (in this case: modifying $response) is done all over the place. Let's refactor! In the code below we're going to convert all input to callables first and then use them to modify $response.

public function respond($statusCode = 200, $headers = [])
{
    $response = new JsonResponse();

    $response->setData($this->createData()->toArray());

    if (is_int($statusCode)) {
        $statusCode = function (JsonResponse $response) use ($statusCode) {
            return $response->setStatusCode($statusCode);
        };
    }

    if (is_array($headers)) {
        $headers = function (JsonResponse $response) use ($headers) {
            return $response->withHeaders($headers);
        };
    }

    if (is_callable($statusCode)) {
        $statusCode($response);
    }

    if (is_callable($headers)) {
        $headers($response);
    }

    return $response;
}

Hopefully you can use this neat little trick to improve the readability of your code as well.

Read more

How I refactor to collections

Christopher Rumpel posted some good practical examples on how to refactor common loops to collections.

Refactoring to Collections is a great book by Adam Wathan where he demonstrates, how you can avoid loops by using collections. It sounds great from the beginning, but you need to practice it, in order to be able to use it in your own projects. This is why I refactored some of my older projects. I want to share these examples today with you.

http://christoph-rumpel.com/2016/11/How-I-refactor-to-collections/

Read more

Clean up your Vue modules with ES6 Arrow Functions

On the .dev blog Jacob Bennett shared some nice refactorings using arrow functions.

Recently when refactoring a Vue 1.0 application, I utilized ES6 arrow functions to clean up the code and make things a bit more consistent before updating to Vue 2.0. Along the way I made a few mistakes and wanted to share the lessons I learned as well as offer a few conventions that I will be using in my Vue applications moving forward.

https://dotdev.co/clean-up-your-vue-modules-with-es6-arrow-functions-2ef65e348d41

Read more

How to refactor code with PhpStorm

Matthew Setter demonstrates PhpStorm's handy refactorings. Personally I use "extracting code to a new method" quite a lot.

Refactoring covers a range of different techniques, including moving, extracting, copying, deleting, and renaming. These cover all the types of changes which you are likely to make to your code on an ongoing basis.

Gladly, PhpStorm’s refactoring functionality, which is included as part of the core package, has support for all of these. In this tutorial, I’m going to step through a couple of them; specifically:

  • Extracting code to a new method
  • Renaming a function
  • Changing a function's signature

https://www.matthewsetter.com/refactoring-code-with-phpstorm/

Read more

Using class_alias to maintain BC while moving/renaming classes

I've been using PHP for quite some time now, and I had never heard of class_alias before reading this post by Fabian Schmengler.

Sometimes you want to rename a class or move it to a different namespace. But as soon as it is used anywhere outside the package, this is breaking backwards compatibility and should not be done lightheartedly.

Luckily there is a way in PHP to have both, the old class and the new class, while deprecating the old one: class_alias().

https://www.schmengler-se.de/en/2016/09/php-using-class_alias-to-maintain-bc-while-move-rename-classes/

Read more

Simplifying presenters in Laravel original

by Freek Van der Herten – 3 minute read

In the Laravel template that we use to kickstart all our client projects at Spatie, I recently changed the way we handle presenters. Instead of using Jeffrey Way's popular presenter package we now use simple traits. In this post I want to give some background on that change. In case you've never…

Read more

Symfony components in a legacy PHP application

Joeri Verdeyen, a developer at Yappa, explains how you can use some Symfony components in a legacy application.

Symfony Components are a set of decoupled and reusable PHP libraries. They are becoming the standard foundation on which the best PHP applications are built. You can use any of these components in any of your applications independently from the Symfony Framework.

The purpose of this post is to roughly describe how to implement some of the Symfony Components.

http://tech.yappa.be/symfony-components-in-a-legacy-php-application

Alternatively if you want to use Laravel's Illuminate components, check out Matt Stauffer's Torch repository on GitHub.

Read more

The Quiet Crisis unfolding in Software Development

Bill Jordan wrote an absolutely amazing piece on the things he learned in the twenty-eight years he worked in the software industry. There are so many good insights that I can nearly quote the entire article.

Here are some of the things that resonated with me:

Odds are far better than good that your high performers are achieving what appears to be high levels of productivity by building technical debt into the application by taking shortcuts whether intentionally or unintentionally. These kinds of high performers are actually low performers when when TCO is factored in.
Encourage developers to improve the application while working on their projects. Examples of improvements are creating reusable objects out of copypasta code and breaking up large objects that are difficult to maintain into smaller objects that individually are easier to reason about. Improve the database schema even if it hurts in the short term. Delete old and unused code. With the benefit of hindsight update the user interface to improve user experience — sometimes even just changing a word or two makes a big difference.
When continual improvement is part of the DNA of your team you’ll be amazed with the results, but give those results some time to become apparent — it won’t happen overnight. It also means management will need to recognize that things will take more time since developers will be working on their primary project while simultaneously making incremental improvements.

Be sure to read the entire post: https://medium.com/@billjordan1/the-quiet-crisis-unfolding-in-software-development-cffbdafbf450#.1j7a7qos3

Read more

Make Everything The Same

Sandi Metz solved the Roman numerals kata in a very interesting way. Along the way she makes the case for keeping code simple.

The desire for simplicity means that I abhor special cases. I am willing to trade CPU cycles to achieve sameness. I'll happily perform unnecessary operations on objects that are already perfectly okay if that lets me treat them interchangeably. Code is read many more times that it is written, and computers are fast. This trade is a bargain that I'll take every time.

http://www.sandimetz.com/blog/2016/6/9/make-everything-the-same

Amen to that. I did not know that additive Roman numerals are perfectly valid.

If you want to read more posts by Sandi, be sure to subscribe to her newsletter.

Read more

Splitting controllers

Jerome Dalbert on his blog:

Splitting your Rails controllers when they have a very specific scope, too much logic, or too many mixed concerns can have a lot of good side effects in your code.

It doesn’t mean that you never abstract. It just comes later down the road. At some point some logic needs to be shared by several controllers. Sometimes even a splitted controller with only one public method gets too big. Et cetera. This is where concerns, model methods, possibly background jobs, and even sometimes service objects (hopefully not too many) come into play.

The more your app grows, the more time you will need to spend to understand it, no matter how clean the code is. But splitting your controllers makes things easier.

http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/

Read more

Cleaning Up Form Input with Transpose

Adam Wathan has another excellent article on using collections. This time he tackles the less used transpose-function.

Transpose is an often overlooked list operation that I first noticed in Ruby.

The goal of transpose is to rotate a multidimensional array, turning the rows into columns and the columns into rows.

http://adamwathan.me/2016/04/06/cleaning-up-form-input-with-transpose/

Personally, I can't wait until the release of his book: Refactoring To Collections.

Read more

Things I used to do (that aren’t cool now)

In a post on his blog Michael Stivala looks back on his own code written a year ago. Because he learned a lot of stuff in a year time, he seems a lot of room for improvement.

As I’m picking up the codebase ahead of this Summer’s updates, I can’t help but review and refactor the existing code. It’s interesting to dissect previous design decisions. Also, I’ve learnt so much in the last year that it’s only natural for me to want to bring the standard of an older project up a bit.
https://michaelstivala.com/things-i-used-to-do-that-arent-cool-now/ If you look at your own code from a year (or longer ago) and think "this is allright, I can't improve on this", chances are you're not learning enough.

Read more

Converting PHP 7 code to equivalent PHP 5 code

In the JavaScript world converting modern code to an older syntax is quite common. In the PHP world you don't see that happen often. Symfony provides a few polyfills, but a full fledged conversion isn't available. At the meetup of our local PHP user group Jens Segers, Hannes Van de Vreken and I were toying around with the idea of converting PHP 7 code to equivalent PHP 5 code automatically.

Today our little hobby project called 7to5 was tagged 1.0.0. You can view the repo on GitHub.

What does it do?

The tool will convert PHP 7 to PHP 5 by:
  • removing scalar type hints
  • removing return type hints
  • removing the strict type declaration
  • replacing the spaceship operator by an equivalent PHP 5 code
  • replacing null coalesce statements by equivalent PHP 5 code
  • replacing group use declarations by equivalent PHP 5 code
  • replacing defined arrays by equivalent PHP 5 code
  • converting anonymous classes to regular classes

Because there are a lot of small things that cannot be detected and/or converted properly it is not guaranteed that the converted code will work. It's highly recommended to run your automated tests against the converted code to determine if it works.

Using the tool

7to5 can be installed globally by running:
composer install global spatie/7to5

Once that's done you can use php7to5 to convert a whole directory in one go:

php7to5 convert {$directoryWithPHP7Code} {$destinationWithPHP5Code}

Behind the curtains

Removing some of the features of PHP 7 like scalar type hints and return type hints seems quite easy. Replacing anonymous classes, spaceship operators and null coaleasance operators is al little harder.

Image how you would convert this:

class Test
{
    public function test()
    {
        $class = new class() {
            public function method(string $parameter = '') : string {
                return $parameter ?? 'no parameter set';
            }
        };
        
        $class->method();

        $anotherClass = new class() {
            public function anotherMethod(int $integer) : int {
                return $integer > 3;
            }
        };
    }
            
}

to this:

class AnonymousClass0
{
    public function method($parameter = '')
    {
        return isset($parameter) ? $parameter : 'no parameter set';
    }
}

class AnonymousClass1
{
    public function anotherMethod($integer)
    {
        return $integer < 3 ? -1 : ($integer == 3 ? 0 : 1);
    }
}

class Test
{
    public function test()
    {
        $class = new AnonymousClass0();
        $class->method();
        $anotherClass = new AnonymousClass1();
    }
}

After the meetup of our usergroup the idea lay dormant for a while, but at this year's PHPUKConference Hannes decided to just do it™. I was immediately pulled in. We ran over a few options. It quickly became apparent that using regex was out of the question. Using a state machine would become very unwieldy fast too. We settled on using the PHP parser tool created by Nikita Popov. This tool can convert PHP code to an abstract syntax tree. It's very similar to a domtree for html code. Here's an example (taken from the php parser docs). This code:

echo 'Hi', 'World';
hello\world('foo', 'bar' . 'baz');

will get converted to this tree:

[
    0: Stmt_Echo(
        exprs: [
            0: Scalar_String(
                value: Hi
            )
            1: Scalar_String(
                value: World
            )
        ]
    )
    1: Expr_FuncCall(
        name: Name(
            parts: [
                0: hello
                1: world
            ]
        )
        args: [
            0: Arg(
                value: Scalar_String(
                    value: foo
                )
                byRef: false
            )
            1: Arg(
                value: Expr_Concat(
                    left: Scalar_String(
                        value: bar
                    )
                    right: Scalar_String(
                        value: baz
                    )
                )
                byRef: false
            )
        ]
    )
]

Here's the code to create the abstract syntax tree.

//create a parser
$parser = (new PhpParser\ParserFactory())->create(PhpParser\ParserFactory::PREFER_PHP7);

//feed it PHP 7 code
$php7code = file_get_contents($pathToPhp7File);

//this variable now contains the entire syntax tree
$syntaxTree = $parser->parse($php7code);

Now that we have created the abstract syntax tree, let's manipulate it. The tree can be traversed by a Traverser. The manipulation can be done by one or more Visitors objects. Let's view some code that traverses the tree and converts it back to regular PHP code.

$traverser = new PhpParser\NodeTraverser();

$traverser->addVisitor(new NullCoalesceReplacer());

$manipulatedTree = $traverser->traverse($syntaxTree);

//convert the tree back to regular code
$code = (new \PhpParser\PrettyPrinter\Standard())->prettyPrintFile($manipulatedTree);

file_put_contents($pathToFileWithManipulatedPhpCode, $code);

The real magic happens in the NullCoalesceReplacer class. It will convert all usages of the null coalesce operator, which is exclusive to PHP 7, to equivalent PHP 5 code. This class is not a part of Nikita's package. Hannes and I created it together with all other visitors.

Before taking a look at the code of that class, we'll first examine how we would manually replace a null coalesce operator.

//PHP 7 code
$result = $input ?? 'fixed-value';

//equivalent PHP 5 code
$result = isset($input) ? $input : 'fixed-value';

Let's describe the needed conversion in plain English. We'll need to create a ternary statement. The left hand side of the '??' needs to put in an isset statement. The true branch of the ternary statement needs to use the left hand side of the '??'-operator, the false branch needs the right hand side.

Here's the code of the NullCoalesceReplacer-visitor.

namespace Spatie\Php7to5\NodeVisitors;

use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\NodeVisitorAbstract;

class NullCoalesceReplacer extends NodeVisitorAbstract
{
    public function leaveNode(Node $node)
    {
        //don't do at thing if the given node is not a Coalesce operator we
        if (!$node instanceof Coalesce) {
            return;
        }

        //create an isset functional call with the left side of '??' as the first parameter
        $issetCall = new Node\Expr\FuncCall(new Node\Name('isset'), [$node->left]);

        //replace the entire node by a ternary statement
        return new Node\Expr\Ternary($issetCall, $node->left, $node->right);
    }
}

Let's review another visitor. This one will remove all scalar type hints:

namespace Spatie\Php7to5\NodeVisitors;

use PhpParser\Node;
use PhpParser\Node\Param;
use PhpParser\NodeVisitorAbstract;

class ScalarTypeHintsRemover extends NodeVisitorAbstract
{
    public function leaveNode(Node $node)
    {
        if (!$node instanceof Param) {
            return;
        }

        if ($this->isScalar($node->type)) {
            $node->type = null;
        }
    }

    /**
     * @param string|null $type
     *
     * @return bool
     */
    protected function isScalar($type)
    {
        return in_array($type, ['int', 'integer', 'float', 'string', 'bool', 'boolean']);
    }
}

Using an abstract syntax tree sure makes manipulating code fairly readable. There are quite a number of other visitors. You can take a look at them on GitHub.

In closing

Honestly, I don't think I will ever use this tool myself. I'd rather upgrade a server to PHP 7 that converting code to PHP 5. That being said, it was a fun project to work on. It has a certain coolness factor. I learned that manipulating code using PHP parser is not that hard. Maybe I can use that knowledge on another project someday.

Meanwhile a more featured rich project that converts PHP 7 code has popped up (so if you do want to convert some PHP 7 code take a look at that one too).

Read more