Why Config Coding Sucks

Rector and static analysis help us to work with code better, but it also helps us spot new weak-points of our PHP code.

One of the biggest evils is config coding. How it can hurt you and how get rid of it?

Many frameworks propagate config coding over PHP code. It's cool, it's easy to type, short and we have a feeling we learned something new.

One of the good examples is Laravel with its config/app.php - really good work!

Since Symfony 3.3+ service autodiscovery feature, there is almost no reason to use code in config.

By coding in the config I mean anything more complex than:

The Dark Side

Less discussed side of config coding is that in exchange for sweet syntax sugar we lose:

PHP code written in config format has the same value to these tools as a screen-shot of code... with scrollbar:

Here are 3 problems you invite to your code with config coding and how to get rid of them with PHP.

1. Crappy Code Refactoring Automation

services:
    - FirstService(@secondService::someMethod())

What if...

With PHPStorm these changes are pretty easy:

But the config has to be changed manually - everywhere where Symfony/Nette/... plugins cannot reach.

How can we do this better?

In PHP ✅

<?php

final class FirstServiceFactory
{
    /**
     * @var SecondService
     */
    private $secondService;

    public function __construct(SecondService $secondService)
    {
        $this->secondService = $secondService;
    }

    public function createFirstService(): FirstService
    {
        return new FirstService($this->secondService);
    }
}
 services:
-   - FirstService(@secondService::someMethod())
+   - FirstServiceFactory

2. Learn the Neon/YAML Syntax by Heart

When you start to use more and more "cool" syntax of your favorite markup language, you'll have to remember the spacing, chars and key names:

services:
    FirstService:
        setup:
        # or was is?
        calls:
            - 'setLogger', '@logger']
            # or was it?
            - ['setLogger', ['@logger']]
            # or was it?
            setLogger: '@logger'

You can also say goodbye to rename method refactoring.

We don't want to pollute our brains with these syntax details, we want to code with light and clear mind.

How can we do this better?

In PHP ✅

<?php

namespace App;

final class FirstServiceFactory
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function create(): FirstService
    {
        $someService = new FirstService();
        $someService->setLogger($this->logger);

        return $someService;
    }
}

The service is autowired by return type declaration public function create(): FirstService.

3. Service Re-use With Bugs

services:
    - EmailCodeCleaner(HTMLPurifier(HTMLPurifier_Config::create({
        Attr.EnableID: true
    })))

Later that year somebody wants to use HTMLPurifier:

<?php

final class MicrositeHtmlCodeCleaner
{
    /**
     * @var HTMLPurifier
     */
    private $htmlPurifier;

    public function __construct(HTMLPurifier $htmlPurifier)
    {
        $this->htmlPurifier = $htmlPurifier;
    }

    // ...
}

Let's run the code:

Service 'HTMLPurifier' was not found. Register it in the config.

Ups! It looks like it's the first use of this service. It requires HTMLPurifier_Config class, so we have to create it too:

services:
    - HTMLPurifier(HTMLPurifier_Config::create())

Done!

A few months later, you have an email campaign with a link to a microsite. Both with the same content. But a weird bug is reported - the microsite and email have different HTML outputs with the same content.

You already know, that's because create() had different arguments.

How can we remove this potential bug?

In PHP ✅

<?php

namespace App;

use HTMLPurifier;

final class HTMLPurifierFactory
{
    public function create(): HTMLPurifier
    {
        return new HTMLPurifier(HTMLPurifier_Config::create());
    }
}

We just returned the benefits of PHP code:


Instead of config coding, use factories and autowired parameters. You can also remove factory from configs with AutoReturnFactoryCompilerPass.

 services:
     App\:
        resource: ../src
-
-    SomeClass:
-         factory: ['@SomeClassFactory', 'create']

That's it!


Happy coding!




Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!