У нас есть устаревшее приложение, не основанное на 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;
}
}
}
Из источника доктрины: устарел, этот метод устарел и будет удален в доктрине / аннотациях 2.0. к тому времени автозагрузку следует передать глобально зарегистрированному автозагрузчику. На данный момент используйте AnnotationRegistry :: registerLoader ('class_exists')
Я считаю, что это описано в собственной документации Doctrine?
@JovanPerovic Может быть, вы можете отправить мне ссылку, потому что я ничего не нашел. Ценить это.
Извините, это то, что я имел в виду: doctrine-common.readthedocs.io/en/latest/reference/… - Часть «Чтобы предвидеть раздел конфигурации, чтобы заставить вышеуказанный PHP-класс работать с Doctrine Annotations, требуется следующая настройка:»
Спасибо @JovanPerovic, но эта функция устарела, и на данный момент это обходной путь, но не решение, которое будет работать в ближайшем будущем. Вот почему я спрашиваю, есть ли лучшее решение. Но спасибо, пока я буду использовать AnnotationRegistry :: registerLoader ('class_exists')




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
Спасибо за комментарий и объяснение. Проверю и добавлю в свой код.
Обнаружено, что добавление следующих строк решает эту проблему, но :: registerLoader устарел и будет удален. Как лучше всего вести себя в этом случае? $ loader = require_once ('vendor / autoload.php'); \ Doctrine \ Common \ Annotations \ AnnotationRegistry :: registerLoa der (array ($ loader, 'loadClass'));