Doctrine ORM documentation V3 and V4

Good afternoon,

It’ my first message. i greet everybody!
Firstly, I want to inform you that English is not my first language, and I apologize for my imperfect English. I tried this week-end to configure Slim with doctrine. I had some problems:

Today, i compare the V3 and V4 versions of the documentation. Are there the same documentation? Is it the V3 version?
thank’s for your help.

1 Like

What is in your composer.json ?

i give you the longer. I tried an another version.

    "name": "gerard/slim-crud3",
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "authors": [
        {
            "name": "Gérard Le Rest",
            "email": "gerard.lerest@orange.fr"
        }
    ],
    "require": {
        "slim/slim": "^4.13",
        "slim/psr7": "^1.6",
        "php-di/php-di": "^7.0",
        "symfony/cache": "^6.4",
        "doctrine/dbal": "^4.0",
        "doctrine/orm": "^3.1",
        "doctrine/doctrine-bundle": "^2.12",
        "doctrine/cache": "^2.2",
        "doctrine/migrations": "^3.7"
    }
}
``
1 Like

You didn’t answer to my question. Are the two documentation similar (v3 and v4?
Isn’t it a problem to configure SLIM4 with Doctrine?

I use PHP-DI and the example documentation use uma/dic
. So i uninstall PHP-DI et and i install uma/dic. But it’ doesn’work/ Many underlines persist.
Is it a good way?

It looks like 1ma/DIC does not support autowiring, so better stick to PHP-DI.

return $container;
($routes = require DIR . ‘/routes.php’)($app);

This makes no sense to me, because the code after the return statement will not be executed, therefore the routes won’t be registered.

require composer

This will also not work. Run composer update to download the latest dependencies instead.

You are right odan ($routes = require DIR . ‘/routes.php’)($app); after return is stupid.
Thank you for your response, but keep uma/dic for the moment. It’s easier for me. I’m beginner in framwork. There are some errors in the documentation and the example. This is my bootstrap.php:

<?php
 
// bootstrap.php

use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use UMA\DIC\Container;
use Doctrine\ORM\EntityManager;
use App\Service\UserService;

$container = new Container(require __DIR__ . '/settings.php');
 
$container->set(EntityManager::class, static function (Container $c): EntityManager {
    /** @var array $settings */
    $settings = $c->get('settings');
 
    $config = ORMSetup::createAttributeMetadataConfiguration(
        $settings['doctrine']['metadata_dirs'],
        $settings['doctrine']['dev_mode'],
        null,
        $settings['doctrine']['dev_mode'] ?
            new ArrayAdapter() :
            new FilesystemAdapter(directory: $settings['doctrine']['cache_dir'])
    );
 
    return new EntityManager($settings['doctrine']['connection'], $config);
});

// définir un service dans un conteneur d'injection de dépendances.
$container->set(UserService::class, static function (Container $c) {
    return new UserService($c->get(EntityManager::class));
});

return $container;

three questions for the extract of the code:

// définir un service dans un conteneur d'injection de dépendances.
$container->set(UserService::class, static function (Container $c) {
    return new UserService($c->get(EntityManager::class));
``
- is this code correct?
- is it for the extract the good place?
- there is an error with Uservervice. I'm think the container set method is depreciated.

You’ve right odan, put code after a return is stupid. there are some errors in the documentation.
this is my bootstrap.php. I’ve three questions.

?php
 
// bootstrap.php

use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use UMA\DIC\Container;
use Doctrine\ORM\EntityManager;
use App\Service\UserService;

$container = new Container(require __DIR__ . '/settings.php');
 
$container->set(EntityManager::class, static function (Container $c): EntityManager {
    /** @var array $settings */
    $settings = $c->get('settings');
 
    $config = ORMSetup::createAttributeMetadataConfiguration(
        $settings['doctrine']['metadata_dirs'],
        $settings['doctrine']['dev_mode'],
        null,
        $settings['doctrine']['dev_mode'] ?
            new ArrayAdapter() :
            new FilesystemAdapter(directory: $settings['doctrine']['cache_dir'])
    );
 
    return new EntityManager($settings['doctrine']['connection'], $config);
});

// définir un service dans un conteneur d'injection de dépendances.
$container->set(UserService::class, static function (Container $c) {
    return new UserService($c->get(EntityManager::class));
});

return $container;
  • Is this code correct? (without UserDervice)
And about this abstract of the code

// définir un service dans un conteneur d’injection de dépendances.
$container->set(UserService::class, static function (Container $c) {
return new UserService($c->get(EntityManager::class));
});

- Is the code at the correct place?
- UserSercice seems me to be depreciated. How can I change this piece of code.
Thank's a lot.

This is how I usually hook doctrine in my projects:

  • dependencies definitions:
return [
    'entity-manager' => static function (
        ContainerInterface $container
    ): EntityManager {
        $config = ORMSetup::createAttributeMetadataConfiguration(
            paths: ($container->get('doctrine_orm'))['entities_paths'],
            isDevMode: $_ENV['APP_ENV'] === 'dev',
        );
        $config->setAutoGenerateProxyClasses(AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED);
        $config->addCustomStringFunction(
            DqlFunctions\JsonExtract::FUNCTION_NAME,
            DqlFunctions\JsonExtract::class
        );
        $dsnParser = new DsnParser();
        $connectionParams = $dsnParser
            ->parse($_ENV['DB_URL']);
        $connectionParams['driver'] = 'pdo_mysql';
        $connection = DriverManager::getConnection(
            $connectionParams,
            $config
        );
        return new EntityManager($connection, $config);
    },

    'doctrine_orm' => [
        'entities_paths' => [
            './path/Entity',
        ],
    ],

    SomeRepositoryInterface::class => static function (
        ContainerInterface $container
    ): SomeRepository {
        return new SomeRepository(
            $container->get('entity-manager')
        );
    },
];
  • composer.json :
        "ext-pdo": "*",
        "doctrine/orm": "^2.11.0",
        "doctrine/dbal": "^3.2",
        "doctrine/annotations": "^2.0",

It’s just a copy/paste (hope I have not missed anything) but it should work with v4

You are passing an callback function via the set method. This will not work, because this method requires a real instance. Again, please think about using PHP-DI, because UMA\DIC does not support what you are expecting, especially if you are starting with this topic. If you still want to keep UMA\DIC, then I would recommend to create an issue von github with this specific question, because this is not a Slim specific issue.

Thanks you tj_gumis for the examples of dependencies definitions. It will help me. Some configurations are complex. How do you define them? I want also understand what I can
Odan, I am agree to use PHP-DI. But I prefer to repeat I am a beginner. I hope you’re patient. I begin to understand the notion of dependencies injection.
I read the example of Dependency container. It seems simple. But where can i find good settings for EntityManager?

I have a question:

  • for a route, i fetch likes this (Documentation):
$app->get('/foo', function (Request $request, Response $response, $args) {
    $myService = $this->get('myService');

and for a function alone, we inject dependency with the constructor ?

class StudentController
{
     private EntityManager $entityManager;

     public function __construct(EntityManager $entityManager)
     {
           $this->entityManager = $entityManager;
     }

Thanks a lot for your help.

Do you want to learn something properly, or you just want to make it work?

I want to learn something properly. But I hope it will not to be too complex.

Why? If its simple, everyone can do it and you miss the whole fun and satisfaction :slight_smile:

Btw, it is a bit complex for a newbie, but it is not complicated at all. If you got some programming basics I can guide you, otherwise you will end up discouraged.

Ok.


The class StudentController has been generated by ChatGPT

this is my composer.json:

{
    "name": "gerard/slim-crud3",
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "authors": [
        {
            "name": "Gérard Le Rest",
            "email": "gerard.lerest@orange.fr"
        }
    ],
    "require": {
        "slim/slim": "^4.13",
        "slim/psr7": "^1.6",
        "php-di/php-di": "^7.0",
        "symfony/cache": "^6.4",
        "doctrine/dbal": "^4.0",
        "doctrine/orm": "^3.1",
        "doctrine/doctrine-bundle": "^2.12",
        "doctrine/cache": "^2.2",
        "doctrine/migrations": "^3.7",
        "uma/dic": "^3.0"
    }
}

the index.php:

<?php

// index.php

use Slim\Factory\AppFactory;

// Charger le fichier de configuration

require __DIR__ . '/../vendor/autoload.php';

// Charger le fichier de configuration

require __DIR__ . '/../config/bootstrap.php';

// Créer l'application Slim

$app = AppFactory::create();

//charger la route

(require __DIR__ . '/../config/routes.php')($app);

$app->run(); // Démarre l'application

and bootstrap.php:

<?php
 
// bootstrap.php

use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use UMA\DIC\Container;
use Doctrine\ORM\EntityManager;
use App\Service\UserService;

$container = new Container(require __DIR__ . '/settings.php');
 
$container->set(EntityManager::class, static function (Container $c): EntityManager {
    /** @var array $settings */
    $settings = $c->get('settings');
 
    $config = ORMSetup::createAttributeMetadataConfiguration(
        $settings['doctrine']['metadata_dirs'],
        $settings['doctrine']['dev_mode'],
        null,
        $settings['doctrine']['dev_mode'] ?
            new ArrayAdapter() :
            new FilesystemAdapter(directory: $settings['doctrine']['cache_dir'])
    );
 
    return new EntityManager($settings['doctrine']['connection'], $config);
});

// définir un service dans un conteneur d'injection de dépendances.
$container->set(UserService::class, static function (Container $c) {
    return new UserService($c->get(EntityManager::class));
});
``
return $container;

the settings.php:

<?php

// settings.php

define('APP_ROOT', __DIR__);

return [
    'settings' => [
        'slim' => [
            // Returns a detailed HTML page with error details and
            // a stack trace. Should be disabled in production.
            'displayErrorDetails' => true,

            // Whether to display errors on the internal PHP log or not.
            'logErrors' => true,

            // If true, display full errors with message and stack trace on the PHP log.
            // If false, display only "Slim Application Error" on the PHP log.
            // Doesn't do anything when 'logErrors' is false.
            'logErrorDetails' => true,
        ],

        'doctrine' => [
            // Enables or disables Doctrine metadata caching
            // for either performance or convenience during development.
            'dev_mode' => true,

            // Path where Doctrine will cache the processed metadata
            // when 'dev_mode' is false.
            'cache_dir' => APP_ROOT . '/var/doctrine',

            // List of paths where Doctrine will search for metadata.
            // Metadata can be either YML/XML files or PHP classes annotated
            // with comments or PHP8 attributes.
            'metadata_dirs' => [APP_ROOT . '/src/Domain'],

            // The parameters Doctrine needs to connect to your database.
            // These parameters depend on the driver (for instance the 'pdo_sqlite' driver
            // needs a 'path' parameter and doesn't use most of the ones shown in this example).
            // Refer to the Doctrine documentation to see the full list
            // of valid parameters: https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html
            'connection' => [
                'driver' => 'pdo_mysql',
                'host' => 'localhost',
                'port' => 3306,
                'dbname' => 'mydb',
                'user' => 'user',
                'password' => 'secret',
                'charset' => 'utf-8'
            ]
        ]
    ]
];
the routes.php:
```php
<?php

//routes.php

use App\Controllers\StudentController;
use Slim\App;

return function (App $app) {
    // Définition des routes
    $app->post('/create/student', StudentController::class . ':createStudent');
};

the StudentController.php:

<?php

namespace App\Controllers;

//StudentController.php

use App\Entities\Student;
use Doctrine\ORM\EntityManager;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class StudentController
{

    private EntityManager $entityManager;

    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function createStudent(Request $request, Response $response, array $args): Response
    {
        $data = $request->getParsedBody();

        $student = new Student();
        if (isset($data['name'])) {
            $student->setName($data['name']);
        }
        if (isset($data['firstname'])) {
            $student->setFirstname($data['firstname']);
        }
        if (isset($data['age'])) {
            $student->setAge($data['age']);
        }

        $response->getBody()->write("étudiant rentré avec succès");

        try {
            // Utilisez $this->entityManager pour interagir avec la base de données
            $this->entityManager->persist($student);
            $this->entityManager->flush();

            $response->getBody()->write('Étudiant créé avec succès avec l\'ID : ' . $student->getId());
        } catch (\Exception $e) {
            $response->getBody()->write('Erreur lors de la création de l\'étudiant : ' . $e->getMessage());
            return $response->withStatus(500);
        }

        return $response->withHeader('Content-Type', 'application/json');
    }
}

Student.php:

<?php

namespace App\Entities;

//Student.php

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: "students")]
class Student
{
    #[ORM\Id]
    #[ORM\Column(type: "integer")]
    #[ORM\GeneratedValue]
    #[ORM\Column(nullable: false)]
    private $id;

    #[ORM\Column(type: "string", length: 50, nullable: false)]
    private $name;

    #[ORM\Column(type: "string", length: 50, nullable: false)]
    private $firstname;

    #[ORM\Column(type: "integer", nullable: false)]
    private $age;

    // Getter pour l'id
    public function getId()
    {
        return $this->id;
    }

    // Getter et setter pour le nom
    public function getName()
    {
        return $this->name;
    }

    public function setName($name): void
    {
        $this->name = $name;
    }

    // Getter et setter pour le prénom
    public function getFirstname()
    {
        return $this->firstname;
    }

    public function setFirstname($firstname): void
    {
        $this->firstname = $firstname;
    }

    // Getter et setter pour l'âge
    public function getAge()
    {
        return $this->age;
    }

    public function setAge($age): void
    {
        $this->age = $age;
    }
}

Good afternoon,

tj_gumis, I’m always agree for your help. Have you an idea?

I sent you a private message a while ago :slight_smile:

Ah, Ok! I understand why you deleted your message. It’ ok for me.
Good evening!

I think after reading and progamming, I think I understand some things. I’m a beginner in framework, so, please be indulgent. I hope I don’t say stupid things.

Last program:

  • It doesn’t have a good structure because I followed an old version of Doctrine.
  • If I understand,the last program needs the doctrine console because the container uma/dic wich can’t do autowiring/
    The PHP-DI make it.

The new program (Slim - PHP-DI - Doctine) - it’s 's work.
I have used:

strong textSecutiy and gestion management and error management are not implemented at the momet**

Here are the programs:

  • public/index: (tutorial code)
<?php
(require __DIR__ . '/../config/bootstrap.php')->run();
  • config/bootstrap (code of the tutorial)
use DI\ContainerBuilder;
use Slim\App;
 
require_once __DIR__ . '/../vendor/autoload.php';
 
// Construire l'instance de container de type PHP-DI
$container = (new ContainerBuilder())
    ->addDefinitions(__DIR__ . '/container.php')
    ->build();

 
//  renvoyer l'instance du container
return $container->get(App::class);
  • config/settings.php (personnal code)
<?php
// settings.php
return [
    'doctrine' => [
        'pathToModels' => '__DIR__ . /../src/Models',
        'isDevMode' => true,
        'connectionMysql' => [
            'driver' => 'pdo_mysql',
            'dbname' => 'university',
            'user' => 'toto',
            'password' => 'mdp',
        ],
    ],
];
  • config/container.php (tutorial code modified)
<?php
 
use Slim\App;
use Slim\Factory\AppFactory;
use Doctrine\DBAL\DriverManager; 
use Doctrine\ORM\EntityManager; 
use Doctrine\ORM\ORMSetup;
use Psr\Container\ContainerInterface; 
 
return[
    'settings' => function () {
        $settings = require __DIR__ . '/settings.php';
        return $settings;
    },
    // Construction de l'instance EntutyManager
    EntityManager::class => function (ContainerInterface $container) : EntityManager {
 
        $settings = (array)$container->get('settings')['doctrine'];
 
 
        $paths = [$settings['pathToModels']]; //chemin de l'entité
        $isDevMode = $settings['isDevMode']; // est-ce en développement?
 
        // paramètres de connexion
        $dbParams = $settings['connectionMysql']; //connexion de la base de données
 
        // construction er retour de l'instance AppFactory
        $config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
        $connection = DriverManager::getConnection($dbParams, $config);
        return new EntityManager($connection, $config);   
    },
 
    // construction de l'application $app
    App::class => function (ContainerInterface $container) {
        $app = AppFactory::createFromContainer($container);
 
        // Register routes
        (require __DIR__ . '/routes.php')($app);
 
        // Register middleware
        (require __DIR__ . '/middleware.php')($app);
 
        return $app;
    }
];
  • config/routes.php (code personnal)
<?php
 
use Slim\App;
use App\Controllers\StudentController;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
 
return function (App $app) {
    //route de test
    $app->get('/', function (Request $request, Response $response) {
        $response->getBody()->write('Hello, World!');
        return $response;
    });
    //Appel de la méthode createUser de la classe StudentContreller à l'aide de l'URL "localhost:8082/create/student"
    $app->post('/create/student', StudentController::class . ':createStudent');
};
  • src/Contollers/StudentController.php (code perso)
<?php
 
namespace App\Controllers;
 
use App\Models\Student;
use Doctrine\ORM\EntityManager;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
 
class StudentController
{
    private  $entityManager;
 
    public function __construct(EntityManager $entityManager){
        $this->entityManager = $entityManager;
    }
 
    public function createStudent(Request $request, Response $response): Response
    {
        //récupération des données
        $data = $request->getParsedBody();
        error_log(print_r($data, true), 3, __DIR__ . '/StudentController.log');
 
        // création des données de l'étudiant
        $student = new Student();
        $student->setName($data['name']);
        $student->setFirstname($data['firstname']);
        $student->setAge($data['age']);
 
        //transfert des données de l'étudiant dans la base de données
        $this->entityManager->persist($student);
        $this->entityManager->flush();
 
        $response->getBody()->write('Étudiant créé avec succès avec l\'ID :' . $student->getId());
        return $response->withHeader('Content-Type', 'application/json');
    } 
}

src/Models/student.php (personnal code):

<?php

namespace App\Models;

//Student.php

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: "students")]
class Student{

    #[ORM\Id]
    #[ORM\Column(type: "integer")]
    #[ORM\GeneratedValue]
    private $id;

    #[ORM\Column(type: "string", length: 50, nullable: false)]
    private $name;

    #[ORM\Column(type: "string", length: 50, nullable: false)]
    private $firstname;

    #[ORM\Column(type: "integer", nullable: false)]
    private $age;

    // Getter de l'Id
    public function getId()
    {
        return $this->id;
    }

    // Getter et setterde "name" 
    public function getName()
    {
        return $this->name;
    }

    public function setName($name): void
    {
        $this->name = $name;
    }

    // Getter et setter pour firstname
    public function getFirstname()
    {
        return $this->firstname;
    }

    public function setFirstname($firstname): void
    {
        $this->firstname = $firstname;
    }

    // Getter et setter pour "age"
    public function getAge()
    {
        return $this->age;
    }

    public function setAge($age): void
    {
        $this->age = $age;
    } 
}
I can hear your remarks.
I especially want to thank Daniel Opitz for his fabulous work.

the DI container definition Entitymanager does too much. So It’s preferable to create an another one for the connexion (EntityManager and cache (slim 4) - #2 by odan)

// connexion à la base de données
    Connection::class => function (ContainerInterface $container) {
        $settings = (array)$container->get('settings')['doctrine'];
        // paramètres de connexion
        $dbParams = $settings['connectionMysql'];
        $config = $container->get('doctrineConfig');
        return DriverManager::getConnection($dbParams, $config);
    },

    //Configuration
    'doctrineConfig' => function (ContainerInterface $container) {
        $settings = (array)$container->get('settings')['doctrine'];
        return ORMSetup::createAttributeMetadataConfiguration(
            [$settings['pathToModels']], //chemin des l'entités
            $settings['isDevMode'] //est-on en développement?
        );
    },

    //EntityManager
    EntityManager::class => function (ContainerInterface $container) : EntityManager {
        $settings = (array)$container->get('settings')['doctrine'];
        $cache = $settings['isDevMode'] ?
            new ArrayAdapter() : 
            new FilesystemAdapter(directory: $settings['cache_dir']);
        $connection = $container->get(Connection::class); 
        $config = $container->get('doctrineConfig');
        return new EntityManager($connection, $config);