Search code examples
phpeloquentslimphp-di

database connection problems with Eloquent, slim 4 and php-di (bridge for slim4)


I'm trying to build a test backend with slim 4, php-di (bridge for slim 4) and eloquent. In particular I got to the point of wanting to try connecting to the database (mysql provided with xampp). Trying to start the program I get the following error:

 Uncaught Error: Call to a member function connection() on null in C:\xampp\htdocs\smk-backend\vendor\illuminate\database\Capsule\Manager.php

Here is my index.php file:

<?php
(require_once __DIR__ . '/../config/bootstrap.php')->run();

Here is my bootstrap.php file:

<?php

use App\Controllers\Home\HomeController;
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use function DI\create;
use Illuminate\Database\Capsule\Manager;
require_once __DIR__ . '/../vendor/autoload.php';

$container = (new ContainerBuilder())
    ->addDefinitions([
        Defaults::class => create(Defaults::class),
        EloquentManager::class => create(EloquentManager::class),
        HomeController::class => create(HomeController::class)
        
    ])
    ->build();
$app = \DI\Bridge\Slim\Bridge::create();
$app->setBasePath('/smk-backend');
(require_once __DIR__ . '/routes.php')($app);

return $app;

Here is my defaults.php file:

<?php

class Defaults {
    private $dbSettings;

    public function __construct()
    {
        $this->dbSettings = [
            'database' => 'prova',
            'username' => 'root',
            'password' => '',
            'host' => '127.0.0.1:3306',
            'driver' => 'mysql',
            'charset'   => 'utf8mb4',
            'collation' => 'utf8mb4_general_ci',
            'prefix'    => '',
        ];
    }

    public function getDbSettings() {
        return $this->dbSettings;
    }
}

Here is my eloquent.php file:

<?php 
use Slim\App; 
use Illuminate\Database\Capsule\Manager;

class EloquentManager {
    private $capsule;

    public function __construct(Defaults $defaults)
    {
        $capsule = new Manager;
        $capsule->addConnection($defaults->getDbSettings());
        $capsule->setAsGlobal();
        $capsule->bootEloquent();
        $this->capsule = $capsule;
    }
}

Here is my HomeController.php file:

<?php

namespace App\Controllers\Home;

use Illuminate\Database\Capsule\Manager;
use Illuminate\Database\Query\Builder;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final class HomeController
{
    protected $manager;
    public function __construct(Manager $manager) 
    {
        $this->manager = $manager;
    }

    public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $response->getBody()->write('Welcome!');

        return $response;
    }

    public function getTest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface 
    {
        $prova = $this->manager->table('Test')->get();
        $response->getBody()->write(var_export($prova, true));

        return $response;
    }
}

Finally here is my routes.php file:

<?php
use Slim\App;
use App\Controllers\Home\HomeController;

return function (App $app)
{
    $app->get('/', [HomeController::class, 'getTest']);
};

All files are in the same folder except index.php and HomeController.php. From what I understand from the error, the problem is that no connection is created with the database. I tried to make some changes in the configuration (defaults.php file) but it seems that the problem is not there. I have a feeling that the HomeController class object receives nothing when the constructor is invoked. I tried to use Autowiring

From PowerShell I can connect to the database without problems

Thank you


Solution

  • I managed to fix my code.

    Here is my new bootstrap.php file:

    <?php
    use App\Controllers\Home\HomeController;
    use DI\ContainerBuilder;
    use function DI\autowire;
    use DI\Bridge\Slim\Bridge;
    
    require_once __DIR__ . '/../vendor/autoload.php';
    
    $container = (new ContainerBuilder())
        ->addDefinitions([
            HomeController::class => autowire()
        ])
        ->build();
    $app = Bridge::create($container);
    $app->setBasePath('/smk-backend');
    // boot eloquent
    require_once __DIR__ . '/eloquent.php';
    (require_once __DIR__ . '/routes.php')($app);
    
    return $app;
    

    I eliminated several useless classes, also from the container definitions, and fixed the most important error: I wasn't passing the container instance in the call to the create function. I replaced the create function call with autowire where I add the definitions.

    Here is my new eloquent.php file:

    <?php
        $dbSettings = [
            'database' => 'prova',
            'username' => 'root',
            'password' => '',
            'host' => '127.0.0.1:3306',
            'driver' => 'mysql',
            'charset'   => 'utf8mb4',
            'collation' => 'utf8mb4_general_ci',
            'prefix'    => ''
        ];
        $capsule = new Illuminate\Database\Capsule\Manager;
        $capsule->addConnection($dbSettings);
        $capsule->setAsGlobal();
        $capsule->bootEloquent();
    

    The biggest difference is that it is no longer a class, and now also contains the entire database configuration part. I will evaluate whether to move it to a separate configuration file anyway.

    It is not yet clear to me how it is possible that when the HomeController class is instantiated, the Eloquent Manager object passed to the constructor still maintains the configuration used for the connection. It's not even clear to me how I was supposed to use the Illuminate\Database\Connection class that was suggested to me. I think the last thing missing is declare(strict_types=1) in every file.

    Update

    This part of code is useless:

    $container = (new ContainerBuilder())
        ->addDefinitions([
            HomeController::class => autowire()
        ])
        ->build();
    

    This part of code is useless. The code works perfectly without it. From what I understood by carefully rereading the php-di documentation, autowire works automatically even without going through the addDefinitions method