How to pass app directory paths to services

about a month ago by Rick Strafy     edit

In this very short article, I will show you 2 simple ways how to pass directory constants to services, it's very elementary thing that most of you know, but it never hurts to see it again.

Let's say we have CronTaskExecutor for executing cron tasks from database via shell_exec function that needs path to our application, as you can see, that service require $logger service from DI container and $appDir string that we must pass from our configuration file.

class CronTaskExecutor
{
	public function __construct(
		private Logger $logger,
		private string $appDir,
	) {}
}
services:
    - CronTaskExecutor(appDir: %appDir%)

It's not necessary to add services to configuration manually, since we have built-in search extension for registering our classes as services automatically based on suffixes such as Facade, Repository, Factory, Executor, Manager, Sender, Helper and so on. And the only reason we had to edit configuration file was because we needed to pass the %appDir% constant.

Imagine you have pretty large project, and you often have to use directory paths like %tempDir% %appDir% or %vendorDir% and maybe your custom directory paths for backups, media and so on. Every time you need some directory path, you would have to add service to your configuration.

Straightforward solution to avoid this, is to create service that will provide all important project directories for other services and register it only once, and when service needs some directory path, we will simply inject our DirectoryProvider service. This is an example service class with added $backupDir:

(PHP 8.1 readonly properties are used only to avoid long class with getters in this article.)

declare(strict_types=1);

namespace App\Model;

class DirectoryProvider
{
    public readonly string $backupDir;

    public function __construct(
        public readonly string $appDir,
        public readonly string $tempDir,
        public readonly string $vendorDir,
    ) {
        $this->backupDir = $this->appDir . '/../backups';
    }
}

and now we have to register that service in the configuration file like this:

services:
    - App\Model\DirectoryProvider(
		appDir: %appDir%,
		tempDir: %tempDir%,
		vendorDir: %vendorDir%,
	)

So with that service, we'll no longer need to add services that works with app directories to our configuration file, because now we can inject our DirectoryProvider instead and use $this->directoryProvider->appDir.

class CronTaskExecutor
{
	public function __construct(
		private Logger $logger,
		private DirectoryProvider $directoryProvider,
	) {}

	public function execute(CronTask $task): void
	{
		$appDir = $this->directoryProvider->appDir;
	}
}

Final thoughts

The only reason I have used the new syntax from PHP 8.1 (will be released soon) in example is only to simplify class and spare some space in article, I still prefer syntax with getters, the reason is that you can pass some parameters to the method, for instance getMediaDir(MediaType::Images), and when you decide to do that and you will be already using public readonly properties, it will bring a little inconsistency.

Further reading