Laravel: autowire contracts or facades
Tags: php, laravel
Published: 2024-04-28 11:09:33

I'm coming from the Symfony framework where [interface autowiring](https://symfony.com/doc/current/service_container/autowiring.html#working-with-interfaces) is build-in. It is tiresome that you need to use [app binding](https://laravel.com/docs/11.x/container#binding-interfaces-to-implementations) in a ServiceProvider.

So I wrote a function that can be added to a ServiceProvider boot method.

```php
private function bindContracts(): void {
        $contractsPath = __DIR__ . '/../Contracts';
        $Directory = new RecursiveDirectoryIterator($contractsPath);
        $Iterator = new RecursiveIteratorIterator($Directory);
        $Regex = new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
        $interfaces = new \CallbackFilterIterator($Regex, fn($i) => is_array($i));

        foreach ($interfaces as $val) {
                $interfaceName = str_replace([$contractsPath, '.php', '/'], ['', '', '\\'], $val[0]);
                $interface = "App\Contracts$interfaceName";
                $class = "App$interfaceName"  ;

                if(class_exists($class)) {
                    $this->app->bind($interface, $class);
                }
        }
    }
```
This works by mirroring the Contracts directory structure with the App directory structure. 

To do this with facades, simply change the directory and the `$interface` value.

The next problem is that there is no artisan command that creates classes with interfaces.

```
<?php

namespace {{ namespace }};

use {{ interfaceNamespace }}\{{ interface }} as {{ interface }}Interface;

class {{ class }} implements {{ interface }}Interface
{
    public function __constuct()
    {

    }
}
```
```
<?php

namespace {{ namespace }};

interface {{ interface }}
{

}
```
These are the stub files for a class and an interface.

```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Attribute\AsCommand;

#[AsCommand('make:class')]
class MakeClass extends GeneratorCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $name = 'make:class';

    protected $type = 'Console command';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create all sorts of classes with their interface.';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        if ($this->isReservedName($this->getNameInput())) {
            $this->components->error('The name "'.$this->getNameInput().'" is reserved by PHP.');

            return false;
        }

        $name = $this->getNameInput();

        $classNamespace = $this->rootNamespace();
        $interFaceNamespace = $classNamespace . "Contracts\\";

        if(strpos($name, '\\') > 1) {
            [$partialNamespace, $name] = str_split($name, strrpos($name, '\\'));
            $name = substr($name, 1);
            $classNamespace .= $partialNamespace;
            $interFaceNamespace .= $partialNamespace;
        }

        $classFile = $this->getPath("$classNamespace\\$name");
        $interfaceFile = $this->getPath("$interFaceNamespace\\$name");

        
        $this->makeDirectory($classNamespace);
        $this->makeDirectory($interFaceNamespace);

        [$classStub, $interfaceStub] = $this->getStub();

        $interfaceStubFile = $this->files->get($interfaceStub);
        $interfaceContent = $this->replaceNamespaceService($interfaceStubFile, $interFaceNamespace);
        $interfaceContent = $this->replaceInterface($interfaceContent, $name);

        $this->files->put($interfaceFile, $interfaceContent);

        $classStubFile = $this->files->get($classStub);
        $classContent = $this->replaceNamespaceService($classStubFile, $classNamespace);
        $classContent =  $this->replaceInterface($classContent, $name, $interFaceNamespace);
        $classContent = $this->replaceClass($classContent, $name);

        $this->files->put($classFile, $classContent);



        $this->components->info(sprintf('%s [%s] created successfully.', $name, $classFile));
        $this->components->info(sprintf('%s [%s] created successfully.', $name, $interfaceFile));
    }

    protected function getStub()
    {
        return [
          __DIR__ . '/stubs/class.stub',
            __DIR__ . '/stubs/interface.stub'
        ];
    }

    protected function replaceNamespaceService($stub, $name)
    {
        $stub = str_replace(['{{ namespace }}', '{{namespace}}'], $name, $stub);

        return $stub;
    }

    protected function replaceInterface($stub, $name, $namespace = '')
    {
        $stub = str_replace(['DummyInterface', '{{ interface }}', '{{interface}}'], $name, $stub);
        if(strlen($namespace)) {
            $stub = str_replace(['{{ interfaceNamespace }}', '{{interfaceNamespace}}'], $namespace, $stub);
        }

        return $stub;
    }
}

```
I have overridden the `make:class` command to make all sorts of classes.

This makes working with Laravel easier for me.