doctrine / DoctrineMongoODMModule

Laminas Module for Doctrine MongoDB ODM
https://www.doctrine-project.org/projects/doctrine-mongo-odm-module.html
MIT License
83 stars 87 forks source link

Instantiation of MongoDB AnnotationDriver fails #261

Closed Phoennix closed 2 years ago

Phoennix commented 2 years ago

Hi,

I'm new to Laminas MVC v3 and tried to make an application with this framework and a MongoDB database, using Doctrine as the ODM. So I followed the Doctrine MongoDB ODM Module for Laminas User Guide, creating a new application skeleton with composer.

I encountered two problems :

1. Problem with getServiceLocator

Additional information:
Laminas\ServiceManager\Exception\ServiceNotFoundException
File:
/var/www/mongotest/vendor/laminas/laminas-servicemanager/src/AbstractPluginManager.php:161
Message:
A plugin by the name "getServiceLocator" was not found in the plugin manager Laminas\Mvc\Controller\PluginManager
Stack trace:
#0 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/PluginManager.php(109): Laminas\ServiceManager\AbstractPluginManager->get()
#1 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(258): Laminas\Mvc\Controller\PluginManager->get()
#2 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(273): Laminas\Mvc\Controller\AbstractController->plugin()
#3 /var/www/mongotest/module/Application/src/Controller/IndexController.php(18): Laminas\Mvc\Controller\AbstractController->__call()
#4 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\Controller\IndexController->indexAction()
#5 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\Mvc\Controller\AbstractActionController->onDispatch()
#6 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\EventManager\EventManager->triggerListeners()
#7 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\EventManager\EventManager->triggerEventUntil()
#8 /var/www/mongotest/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\Mvc\Controller\AbstractController->dispatch()
#9 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\Mvc\DispatchListener->onDispatch()
#10 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\EventManager\EventManager->triggerListeners()
#11 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\EventManager\EventManager->triggerEventUntil()
#12 /var/www/mongotest/public/index.php(42): Laminas\Mvc\Application->run()
#13 {main}

It seems that it was removed from Laminas MVC 3. After some research, I solved this problem by registering a custom factory and passing the dependency through a constructor.

Modification of module/Application/config/module.config.php :

    // ...
    'controllers' => [
        'factories' => [
            Controller\IndexController::class => Controller\IndexControllerFactory::class,
        ],
    ],
    // ...

Creation of module/Application/src/Controller/IndexControllerFactory.php :

namespace Application\Controller;

use Psr\Container\ContainerInterface;

class IndexControllerFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new IndexController($container->get('doctrine.documentmanager.odm_default'));
    }
}

Modification of module/Application/src/Controller/IndexController.php :

// ...
use Doctrine\ODM\MongoDB\DocumentManager;
// ...

class IndexController extends AbstractActionController
{
    private $dm;

    public function __construct(DocumentManager $dm)
    {
        $this->dm = $dm;
    }

    public function indexAction()
    {
        $message = new Message();
        $message->setText("Hello Doctrine!");

        $this->dm->persist($message);
        $this->dm->flush();

        var_dump($message);

        return new ViewModel();
    }
}

2. Problem with the Annotation Driver.

Now that the first problem is solved, I can access the Document Manager in my controller. But despite my research, I'm still stuck here :

Error
File:
/var/www/mongotest/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php:81
Message:
Call to a member function getClassAnnotations() on array
Stack trace:
#0 /var/www/mongotest/vendor/doctrine/persistence/src/Persistence/Mapping/Driver/MappingDriverChain.php(79): Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver->loadMetadataForClass()
#1 /var/www/mongotest/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php(175): Doctrine\Persistence\Mapping\Driver\MappingDriverChain->loadMetadataForClass()
#2 /var/www/mongotest/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(414): Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory->doLoadMetadata()
#3 /var/www/mongotest/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(281): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata()
#4 /var/www/mongotest/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/DocumentManager.php(316): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor()
#5 /var/www/mongotest/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/UnitOfWork.php(1742): Doctrine\ODM\MongoDB\DocumentManager->getClassMetadata()
#6 /var/www/mongotest/vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/DocumentManager.php(469): Doctrine\ODM\MongoDB\UnitOfWork->persist()
#7 /var/www/mongotest/module/Application/src/Controller/IndexController.php(26): Doctrine\ODM\MongoDB\DocumentManager->persist()
#8 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\Controller\IndexController->indexAction()
#9 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\Mvc\Controller\AbstractActionController->onDispatch()
#10 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\EventManager\EventManager->triggerListeners()
#11 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\EventManager\EventManager->triggerEventUntil()
#12 /var/www/mongotest/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\Mvc\Controller\AbstractController->dispatch()
#13 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\Mvc\DispatchListener->onDispatch()
#14 /var/www/mongotest/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\EventManager\EventManager->triggerListeners()
#15 /var/www/mongotest/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\EventManager\EventManager->triggerEventUntil()
#16 /var/www/mongotest/public/index.php(42): Laminas\Mvc\Application->run()
#17 {main}

Could you please tell me if I'm doing something wrong or if this is due to a compatibility problem with the latest version of Laminas ? And if so, is there any workaround ?

Working environment: PHP 7.4 laminas/laminas-mvc 3.3.5 doctrine/mongodb-odm 2.4.2 doctrine-mongo-odm-module 4.1.0

malarzm commented 2 years ago

According to provided stack trace, ODM chokes on getting metadata for the Message class during persist. Can you please post it?

driehle commented 2 years ago

The first issue is indeed an outdated documentation that has not yet been updated to reflect usage with laminas-servicemanager v3. I have created PR #262 to update the documentation.

Phoennix commented 2 years ago

@malarzm The Message class is the same as the one in user guide :

<?php

namespace Application\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document
 */
class Message
{
    /**
     * @ODM\Id
     */
    protected $id;

    /**
     * @ODM\Field(type="string")
     */
    protected $text;

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getText()
    {
        return $this->text;
    }

    public function setText($text)
    {
        $this->text = $text;
    }
}

The only difference with the user guide example is that the path is now module/Application/src/Document/Message.php instead of module/Application/src/Application/Document/Message.php. I changed the driver settings module/Application/config/module.config.php to this :

    // ...
    'doctrine' => [
        'driver' => [
            __NAMESPACE__ . '_driver' => [
                'class' => 'Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver',
                'paths' => [__DIR__ . '/../src/Document']
            ],
            'odm_default' => [
                'drivers' => [
                    __NAMESPACE__ . '\Document' => __NAMESPACE__ . '_driver'
                ]
            ]
        ]
    ]
    // ...

Here is the project hierarchy :

image

It seems that there is a problem in the initilialisation of the AnnotationDriver. The constructor in the AnnotationDriver class is waiting for an AnnotationReader instance, as we can see here, in vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php :

    /**
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
     * docblock annotations.
     *
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
     */
    public function __construct($reader, $paths = null)
    {
        $this->reader = $reader;

        $this->addPaths((array) $paths);
    }

The line 81 in vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php throwing the error Call to a member function getClassAnnotations() on array is this one :

    $classAnnotations = $this->reader->getClassAnnotations($reflClass);

If I dump $this->reader just before this line, I get an array (seems to bet the value of paths set in the config file module/Application/config/module.config.php) instead of an AnnotationReader object.

array(1) {
  [0]=>
  string(67) "/var/www/mongotest/module/Application/config/../src/Document"
}

For the moment, I haven't found why the AnnotationDriver is not correctly initalised.

@driehle Thank you for your comment. As I said, I'm just starting to learn Laminas 3, and try to make this skeleton project work by reading a lot of posts, so my code might not be optimal. If there is a better way to solve the problems I reported, let me know, I would be glad to learn better practices. ;)

malarzm commented 2 years ago

We had a similar report in ODM: https://github.com/doctrine/mongodb-odm/issues/2461. Alas I don't know what fixed the problem for OP. I think in this case the class is being wrongly instantiated by either DoctrineModule or this one. @driehle maybe you'll have a better insight? Or is it the incompatibility you're mentioning in https://github.com/doctrine/DoctrineMongoODMModule/pull/263 ?

driehle commented 2 years ago

The issue is indeed caused by a wrong instantiation in of the MongoODM driver in DoctrineModule. I have created PR doctrine/DoctrineModule#788 and will release DoctrineModule 5.2.1 soon, which should fix the issue mentioned here. I'll keep you posted.

driehle commented 2 years ago

@Phoennix Please upgrade DoctrineMongoODMModule to 4.1.1, which has just been released. This should hopefully solve the issue. In case there are more issues, please don't hesitate to open a new issue.

Phoennix commented 2 years ago

Thank you @driehle. It works. The user guide example is now fully functional.