Symfony Аннотации не существуют или не могут быть загружены автоматически - Проверка Symfony с помощью Doctrine

У нас есть устаревшее приложение, не основанное на Symfony. Доктрина используется, и теперь мы хотели бы добавить валидацию к моделям. Кажется, что аннотации никогда не загружаются автоматически, даже когда используются операторы использования.

[Семантическая ошибка] Аннотация «@Symfony \ Component \ Validator \ Constraints \ NotBlank» в свойстве Test \ Stackoverflow \ User :: $ Username не существует или не может быть загружена автоматически.

Написал небольшое демонстрационное приложение, чтобы продемонстрировать проблему и то, как мы создаем диспетчер сущностей и экземпляр проверки.

composer.json:

{
    "require": {
        "symfony/validator"     :   "~3.1"
        , "doctrine/orm"        :   "~2.6.1"
    }
}

index.php

require_once ('vendor/autoload.php');

// Load Entities, would normally be done over composer since they reside in a package
require_once('test/User.php');
require_once('MyAnnotationTestApp.php');

// create test app
$app = new MyAnnotationsTestApp();
$app->initEntityManager('localhost', 'annotation_test', 'root', 'mysql', 3306);

if (key_exists('test', $_GET)){
    // Create entity and validate it
    $entity = new \Test\Stackoverflow\User();
    $entity->setUsername('StackoverflowUser');

    if ($app->testAnnotationWithoutLoading($entity)){
        print "Seems the validation was working without preloading the asserts\n<br>";
    }

    if ($app->testAnnotationWithLoading($entity)){
        print "Seems the validation was working because we loaded the required class ourself.\n<br>";
    }

    print "\n<br><br>The question is why the required annotation classes never get autoloaded?";

    }else{

    // Load the validator class otherwise the annotation throws an exception
    $notBlankValidator = new \Symfony\Component\Validator\Constraints\NotBlank();

    print "We have cerated the tables but also had to load the validator class ourself.\n<br>\n<br>";

    // create tables and
    $app->updateDatabaseSchema();
    print sprintf('<a href = "%s?test">Now lets run the test</a>', $_SERVER['REQUEST_URI']);
} 

Сущность пользователя Doctrine

<?php
namespace Test\Stackoverflow;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity()
 * @ORM\Table(name = "users")
 *
 */
class User{
    /**
     * @ORM\Id
     * @ORM\Column(name = "Id",type = "integer")
     * @ORM\GeneratedValue(strategy = "AUTO")
     */
    protected $Id;

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

    /**
     * @ORM\Column(type = "text", length=80, nullable=false)
     * @Assert\NotBlank()
     */
    protected $Username;

    /**
     * @return string
     */
    public function getUsername()
    {
        return $this->Username;
    }

    /**
     * @param string $Username
     */
    public function setUsername($Username)
    {
        $this->Username = $Username;
    }


} 

Демо-приложение с инициализацией доктрины / валидатора:

<?php


final class MyAnnotationsTestApp {

    /**
     * @var \Doctrine\ORM\EntityManager
     */
    private $entityManager;

    /**
     * @param string $host
     * @param string $database
     * @param string $username
     * @param string $password
     * @param integer $port
     * @param array $options
     * @return \Doctrine\ORM\EntityManager
     */
    public function initEntityManager($host, $database, $username, $password, $port, array $options=null){

        if ($this->entityManager){
            return $this->entityManager;
        }

        $connectionString = sprintf('mysql://%3$s:%4$s@%1$s/%2$s', $host, $database, $username, $password, $port);
        $isDevMode = true;
        $dbParams = array(
            'url'               =>  $connectionString
            , 'driver'          =>  'pdo_mysql'
            , 'driverOptions'   =>   array(
                1002 =>     "SET NAMES utf8mb4"
            )
        );

        $cacheDriver = null;

        $config = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(array(), $isDevMode, '.cache/', $cacheDriver, false);

        if ($cacheDriver){
            $config->setMetadataCacheImpl($cacheDriver);
            $config->setQueryCacheImpl($cacheDriver);
            $config->setResultCacheImpl($cacheDriver);
        }


        $this->entityManager = \Doctrine\ORM\EntityManager::create($dbParams, $config);
        return $this->entityManager;
    }


    /**
     * @return \Doctrine\ORM\EntityManager
     */
    public function getEntityManager(){
        return $this->entityManager;
    }



    public function updateDatabaseSchema(){
        $metaData = array();
        $usedEntities = array(
            'Test\Stackoverflow\User'
        );
        foreach($usedEntities as $entity){
            $metaData[] = $this->entityManager->getClassMetadata($entity);
        }

        $tool = new \Doctrine\ORM\Tools\SchemaTool($this->entityManager);
        $tool->updateSchema($metaData);
        $this->generateProxies($metaData);
    }

    /**
     * Generate all the proxy classes for orm in the correct directory.
     * Proxy dir can be configured over application configuration
     *
     *
     * @throws \Exception
     */
    final public function generateProxies($metaData)
    {
        $em = $this->getEntityManager();
        $destPath = $em->getConfiguration()->getProxyDir();

        if (!is_dir($destPath)) {
            mkdir($destPath, 0777, true);
        }

        $destPath = realpath($destPath);

        if (!file_exists($destPath)) {
            throw new \Exception("Proxy destination directory could not be created " . $em->getConfiguration()->getProxyDir());
        }

        if (!is_writable($destPath)) {
            throw new \Exception(
                sprintf("Proxies destination directory '<info>%s</info>' does not have write permissions.", $destPath)
            );
        }

        if (count($metaData)) {
            // Generating Proxies
            $em->getProxyFactory()->generateProxyClasses($metaData, $destPath);
        }
    }


    /**
     * @var \Symfony\Component\Validator\Validator\ValidatorInterface
     */
    protected $validator;

    /**
     * @return \Symfony\Component\Validator\Validator\ValidatorInterface
     */
    final protected function getValidator(){
        if ($this->validator){
            return $this->validator;
        }
        $this->validator = \Symfony\Component\Validator\Validation::createValidatorBuilder()
            ->enableAnnotationMapping()
            ->getValidator();
        return $this->validator;

    }


    /**
     * @param \Test\Stackoverflow\User $entity
     * @return bool
     */
    final public function testAnnotationWithoutLoading(\Test\Stackoverflow\User $entity){
        try {
            print "test to validate the entity without preloading the Assert classes\n<br>";
            $this->getValidator()->validate($entity);
            return true;
        } catch(\Exception $e){
            print "<strong>Does not work since the Asserts classes never get loaded: </strong> Exception-message: ".$e->getMessage()."\n<br>";
            return false;
        }
    }


    /**
     * @param \Test\Stackoverflow\User $entity
     * @return bool
     */
    final public function testAnnotationWithLoading(\Test\Stackoverflow\User $entity){


        // Here we force the autoloader to require the class
        $notBlankValidator = new \Symfony\Component\Validator\Constraints\NotBlank();

        try {
            print "Loaded the validator manually, will test of it fails now\n<br>";
            $this->getValidator()->validate($entity);
            return true;
        } catch(\Exception $e){
            print "<strong>Was not working: </strong> Exception-message: ".$e->getMessage()."\n<br>";
            print sprintf("<strong>Even when we autoload the class it is not working. Type of assert: %s</strong>\n<br>", get_class($notBlankValidator));
            return false;
        }
    }

} 

Обнаружено, что добавление следующих строк решает эту проблему, но :: registerLoader устарел и будет удален. Как лучше всего вести себя в этом случае? $ loader = require_once ('vendor / autoload.php'); \ Doctrine \ Common \ Annotations \ AnnotationRegistry :: registerLoa‌ der (array ($ loader, 'loadClass'));

Rob 03.08.2018 12:16

Из источника доктрины: устарел, этот метод устарел и будет удален в доктрине / аннотациях 2.0. к тому времени автозагрузку следует передать глобально зарегистрированному автозагрузчику. На данный момент используйте AnnotationRegistry :: registerLoader ('class_exists')

Rob 03.08.2018 12:20

Я считаю, что это описано в собственной документации Doctrine?

Jovan Perovic 03.08.2018 12:24

@JovanPerovic Может быть, вы можете отправить мне ссылку, потому что я ничего не нашел. Ценить это.

Rob 03.08.2018 13:48

Извините, это то, что я имел в виду: doctrine-common.readthedocs.io/en/latest/reference/… - Часть «Чтобы предвидеть раздел конфигурации, чтобы заставить вышеуказанный PHP-класс работать с Doctrine Annotations, требуется следующая настройка:»

Jovan Perovic 03.08.2018 13:58

Спасибо @JovanPerovic, но эта функция устарела, и на данный момент это обходной путь, но не решение, которое будет работать в ближайшем будущем. Вот почему я спрашиваю, есть ли лучшее решение. Но спасибо, пока я буду использовать AnnotationRegistry :: registerLoader ('class_exists')

Rob 03.08.2018 14:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
0
6
1 021
1

Ответы 1

If you are using the Symfony Standard Edition, you must update your autoload.php file by adding the following code [1]

How are these annotations loaded? From looking at the code you could guess that the ORM Mapping, Assert Validation and the fully qualified annotation can just be loaded using the defined PHP autoloaders. This is not the case however: For error handling reasons every check for class existence inside the AnnotationReader sets the second parameter $autoload of class_exists($name, $autoload) to false. To work flawlessly the AnnotationReader requires silent autoloaders which many autoloaders are not. Silent autoloading is NOT part of the PSR-0 specification for autoloading. [2]

// at the top of the file
use Doctrine\Common\Annotations\AnnotationRegistry;

// at the end of the file
AnnotationRegistry::registerLoader(function($class) use ($loader) {
    $loader->loadClass($class);
    return class_exists($class, false);
});

[1] https://symfony.com/blog/symfony2-2-0-rc4-released

[2] https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/annotations.html

Спасибо за комментарий и объяснение. Проверю и добавлю в свой код.

Rob 26.03.2019 11:04

Другие вопросы по теме