Symfony4 - совместное использование пула кэша между двумя менеджерами сущностей, это плохая идея или я сталкиваюсь с ошибкой? - PullRequest
0 голосов
/ 01 апреля 2020

это будет длинный пост, я столкнулся со странным поведением, когда я вижу в профилировщике, что один из менеджеров сущностей, как говорят, отображает сущность, но не отображает ее. Это выглядит так: bottom of doctrine section in profiler Вот doctrine .yaml:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   "pdo_mysql"
                host:     "127.0.0.1"
                port:     "3306"
                dbname:   "example"
                user:     "root"
                password: ""
                charset:  utf8mb4
                server_version: "mariadb-10.4.10"
            logs:
                driver:   "pdo_mysql"
                host:     "127.0.0.1"
                port:     "3306"
                dbname:   "example_logs"
                user:     "root"
                password: ""
                charset:  utf8mb4
                server_version: "mariadb-10.4.10"
    orm:
        auto_generate_proxy_classes: true
        default_entity_manager: default
        entity_managers:
            default:
                query_cache_driver:
                    type: pool
                    pool: apcu.default.cache.pool
                metadata_cache_driver:
                    type: pool
                    pool: apcu.default.cache.pool
                result_cache_driver:
                    type: pool
                    pool: apcu.default.cache.pool
                connection: default
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                mappings:
                    App:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Main'
                        prefix: 'App\Entity\Main'
                        alias: App
            logs:
                query_cache_driver:
                    type: pool
                    pool: apcu.default.cache.pool
                metadata_cache_driver:
                    type: pool
                    pool: apcu.default.cache.pool
                result_cache_driver:
                    type: pool
                    pool: apcu.default.cache.pool
                connection: logs
                naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
                mappings:
                    LogBundle:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Logs'
                        prefix: 'App\Entity\Logs'
                        alias: App

А вот framework.yaml с конфигурацией пула кеша:

framework:
    secret: '%env(APP_SECRET)%'
    session:
        handler_id: null
        cookie_secure: auto
        cookie_samesite: lax

    php_errors:
        log: true

    cache:
        pools:
            apcu.default.cache.pool:
                adapter: cache.adapter.apcu
            apcu.logs.cache.pool:
                adapter: cache.adapter.apcu

Если я удаляю конфигурацию metadata_cache_driver из конфигурации журналов entity_manager или изменяю ее для использования пула кэша (apcu.logs.cache.pool), отличного от менеджера сущностей по умолчанию, тогда профилировщик сообщает о правильных сопоставлениях (пример сущности в em по умолчанию и регистрирует их) пусто).

Эта проблема возникает только в том случае, если сущность является фидом через форму и $form->handleRequest() обрабатывает ее, создание или изменение сущности без форм не вызывает такой проблемы. Вот мой контроллер:

<?php

namespace App\Controller;

use App\Entity\Main\Example;
use App\Form\Type\ExampleType;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ExampleController extends AbstractController {
    /**
     * @Route("/example1")
     * @Template
     */
    public function example1(EntityManagerInterface $em){
        $example = new Example();
        $example->setValue('example value');

        try {
            $em->persist($example);
            $em->flush();
        } catch(\Exception $e){
            return new Response('An error has occurred. '.$e->getMessage());
        }

        return [];
    }

    /**
     * @Route("/example2")
     * @Template
     */
    public function example2(EntityManagerInterface $em){
        $example = $em->getRepository(Example::class)->find(1);
        if(!$example){
            return new Response('No example found.');
        }

        $example->setValue(mt_rand(0, mt_getrandmax()));
        try {
            $em->flush();
        } catch(\Exception $e){
            return new Response('An error has occurred. '.$e->getMessage());
        }

        return [];
    }

    /**
     * @Route("/example3")
     * @Template
     */
    public function example3(Request $request, EntityManagerInterface $em){
        $example = $em->getRepository(Example::class)->find(1);
        if(!$example){
            return new Response('No example found.');
        }

        $form = $this->createForm(ExampleType::class, $example);
        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()){
            $em->flush();
        }

        return ['form' => $form->createView()];
    }


}

example1 и example2 маршруты НЕ вызывают проблему, только example3 делает и только при отправке формы, поэтому только когда я ввожу URL example3, затем нажмите Отправить форму только тогда, когда введите профилировщик для этого запроса, я вижу проблему.

Мой минимальный пример воспроизведения было создание symfony LTS проекта symfony new example-site --version=lts --full

Тогда это файлы, которые я изменились с тех пор:

file changes

Базы данных создаются symfony console doctrine:database:create --connection=default и symfony console doctrine:database:create --connection=logs, затем таблицы создаются symfony console doctrine:migrations:diff --em=default и symfony console doctrine:migrations:migrate --em=default

Вот код для других файлов, которые я еще не включил в сообщение:

<?php
//src/Entity/Main/Example.php
namespace App\Entity\Main;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Example {
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $value;

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

    public function getValue(){
        return $this->value;
    }

    public function setValue(string $value){
        $this->value = $value;
    }
}
<?php
//src/Form/Type/ExampleType.php
namespace App\Form\Type;

use App\Entity\Main\Example;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ExampleType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options){
        $builder->add('value', TextType::class);
        $builder->add('submit', SubmitType::class);
    }

    public function configureOptions(OptionsResolver $resolver){
        $resolver->setDefaults([
            'data_class' => Example::class,
        ]);
    }
}
<!-- template/s/example/example1.html.twig -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Example</title>
</head>
<body>
    Example1
</body>
</html>
<!-- template/s/example/example2.html.twig -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Example</title>
</head>
<body>
    Example2
</body>
</html>
<!-- template/s/example/example3.html.twig -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Example</title>
</head>
<body>
{{ form(form) }}
</body>
</html>

Последнее, что я хочу добавить, это то, что в других спроецируйте эту проблему более отчетливо, потому что, когда у сущности есть ссылка на другую сущность, сообщается об ошибке (на стороне, не являющейся собственником в ассоциации со ссылками «один ко многим»): other example with an error reported В этом случае Item сущность - это форма кормушки Для тех, кому любопытно, есть Item. php: Но я не знаю, как это будет иметь значение, так как он не управляется менеджером сущностей журналов и не должен отображаться под ним. Менеджер объекта по умолчанию, который управляет объектом, не сообщает о каких-либо проблемах с ним.

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ItemRepository")
 * @ORM\Table(indexes={
 *          @ORM\Index(name="item_image", columns={"image"})
 *     })
 */
class Item {

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=32)
     * @Assert\NotBlank()
     * @Assert\Length(min=3, max=32)
     */
    private $name;

    /**
     * @ORM\Column(type="string")
     */
    private $description = '';

    /**
     * @ORM\Column(type="string", length=25, nullable=true)
     */
    private $image;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Item", mappedBy="container")
     */
    private $items;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Item", inversedBy="items")
     * @ORM\JoinColumn(name="container", referencedColumnName="id")
     * @var $container Item
     */
    private $container;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\TagItem", mappedBy="item")
     * @var $tags TagItem[]
     */
    private $tags;

    /**
     * @Assert\Image(mimeTypes="image/jpeg")
     * @var $imageFile null|UploadedFile
     */
    private $imageFile;

    public function __construct() {
        $this->items = new \Doctrine\Common\Collections\ArrayCollection();
        $this->tags = new \Doctrine\Common\Collections\ArrayCollection();
    }

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

    public function getName(){
        return $this->name;
    }

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

    public function getDescription(){
        return $this->description;
    }

    public function setDescription($description){
        $this->description = $description;
    }

    public function hasImage(){
        return isset($this->image);
    }

    public function getImage(){
        return $this->image;
    }

    public function setImage($image){
        $this->image = $image;
    }

    public function hasImageFile(){
        return isset($this->imageFile);
    }

    public function getImageFile(){
        return $this->imageFile;
    }

    public function setImageFile($imageFile){
        $this->imageFile = $imageFile;
    }

    public function getItems(){
        return $this->items;
    }

    public function hasContainer(){
        return isset($this->container);
    }

    public function getContainer(){
        return $this->container;
    }

    public function setContainer(?Item $container){
        return $this->container = $container;
    }

    public function getTags(){
        return $this->tags;
    }

    public function setTags($tags){
        $this->tags = $tags;
    }
}

PHP версия 7.3.12 и размещена с symfony serve

1 Ответ

0 голосов
/ 24 апреля 2020

Я вернулся на github в ветке выпуска от fancyweb , который сказал:

Статус: проверен

Ошибка не связана в WebProfilerBundle. Причина в том, что вы используете одну и ту же соль кэша для двух менеджеров сущностей. В некоторых местах мы слепо вызываем getClassMetadata () для обоих EM последовательно (ie: DoctrineLoader, DoctrineExtractor). Первый вызов законного EM заполняет кеш. Второй вызов EM, который не должен знать класс, попадает в кэш и, таким образом, считает, что класс загружен.

Использование одного и того же кэша - это нормально, вам просто нужно использовать разные соли.

И когда меня спросили, как, если в конфигурации есть опция для установки соли для менеджера сущностей, я получил ответ от stof :

Чистый способ достичь этого - использовать 2 отдельных пула кэша, так как FrameworkBundle учитывает имя пула для начального числа, чтобы изолировать ключи от каждого пула, когда они совместно используют одно и то же хранилище.

...