教义L2缓存和Symfony验证失败

时间:2019-06-17 10:09:41

标签: symfony caching doctrine-orm doctrine

最近,我开始在导入脚本中使用Symfony Validator组件来验证实体是否设置了所有必填字段,并且没有“唯一约束违例”。无效的条目将被跳过并记录。

我不确定,但是这个github问题可能描述了相同的问题: https://github.com/doctrine/orm/issues/7277

  • 验证器组件的版本为v4.2.5

  • 并且该理论库的版本为v2.6.3

以前有人遇到这样的问题吗?我该如何解决?

error: {
    message: "Argument 2 passed to Doctrine\ORM\Cache\EntityCacheKey::__construct() must be of the type array, null given, called in /var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php on line 353",
    trace: [
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/EntityCacheKey.php:49",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:353",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:305",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php:426",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:181",
        "/var/www/myproject/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php:139",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:809",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:525",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:330",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:141",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveValidator.php:100",
        "/var/www/myproject/src/Service/Api/Import/StoreImporter.php:251",
        "/var/www/myproject/src/Command/Api/Import/StoreImporterCommand.php:92",
        "/var/www/myproject/vendor/symfony/console/Command/Command.php:255",
        "/var/www/myproject/vendor/symfony/console/Application.php:926",
        "/var/www/myproject/vendor/symfony/framework-bundle/Console/Application.php:89",
        "/var/www/myproject/vendor/symfony/console/Application.php:269",
        "/var/www/myproject/vendor/symfony/framework-bundle/Console/Application.php:75",
        "/var/www/myproject/vendor/symfony/console/Application.php:145",
        "/var/www/myproject/bin/console:39"
    ]
},

在另一个进口商中,出现了一个问题,该问题也与二级缓存有关。 堆栈跟踪显示了一个区别,它使用了liip / functional-test-bundle DataCollectingValidator,因为这是我在“开发”环境中运行命令的时候。

error: {
    message: "Notice: Undefined index: 0000000051f7928f000000005709f6ca",
    trace: [
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:2995",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:352",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultQueryCache.php:305",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php:426",
        "/var/www/myproject/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:181",
        "/var/www/myproject/vendor/symfony/doctrine-bridge/Validator/Constraints/UniqueEntityValidator.php:139",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:809",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:525",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:330",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveContextualValidator.php:141",
        "/var/www/myproject/vendor/symfony/validator/Validator/RecursiveValidator.php:100",
        "/var/www/myproject/vendor/symfony/validator/Validator/TraceableValidator.php:66",
        "/var/www/myproject/vendor/liip/functional-test-bundle/src/Validator/DataCollectingValidator.php:66",
        "/var/www/myproject/src/Service/Api/Import/DeviceImporter.php:272",
        "/var/www/myproject/src/Command/Api/Import/DeviceImporterCommand.php:92",
        "/var/www/myproject/vendor/symfony/console/Command/Command.php:255",
        "/var/www/myproject/vendor/symfony/console/Application.php:926",
        "/var/www/myproject/vendor/symfony/framework-bundle/Console/Application.php:89",
        "/var/www/myproject/vendor/symfony/console/Application.php:269",
        "/var/www/myproject/vendor/symfony/framework-bundle/Console/Application.php:75",
        "/var/www/myproject/vendor/symfony/console/Application.php:145",
        "/var/www/myproject/bin/console:39"
    ]
}

编辑:添加了唯一实体约束的配置:

/**
 * Device Entity
 * 
 * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
 * @ORM\Entity(repositoryClass = "App\Repository\DeviceRepository")
 * @ORM\Table(name = "s4_device", indexes = {
 *     @ORM\Index(name = "device_model_search_index", columns = {"device_model"}),
 *     @ORM\Index(name = "device_slug_search_index", columns = {"device_slug"}),
 * }, uniqueConstraints = {
 *     @ORM\UniqueConstraint(name = "UNIQUE_DEVICE_MODEL_AND_BRAND", columns = {"device_model", "brand_id"})
 * })
 * @UniqueEntity(fields = {"model", "brand"}, errorPath = "model", message = "device-model-is-not-unique")
 */
class Device extends AbstractEntity
{
    /* ... */

    /**
     * @var DeviceReference
     *
     * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
     * @ORM\OneToOne(targetEntity = "App\Entity\Model\Reference\DeviceReference", mappedBy = "device", cascade = {"persist", "remove"})
     */
    protected $reference;

    /* ... */
}
/**
 * Store Entity
 *
 * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
 * @ORM\Entity(repositoryClass = "App\Repository\StoreRepository")
 * @ORM\Table(name = "s4_store", indexes = {
 *     @ORM\Index(name = "store_name_search_index", columns = {"store_name"}),
 *     @ORM\Index(name = "store_slug_search_index", columns = {"store_slug"})
 * }, uniqueConstraints = {
 *     @ORM\UniqueConstraint(name = "UNIQUE_STORE_NAME", columns = {"store_name"})
 * })
 * @UniqueEntity(fields = {"name"}, message = "store-name-is-not-unique")
 * @UniqueEntity(fields = {"uri"}, message = "store-uri-is-not-unique")
 */
class Store extends AbstractEntity
{
    /* ... */

    /**
     * @var StoreReference|null
     *
     * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
     * @ORM\OneToOne(targetEntity = "App\Entity\Model\Reference\StoreReference", mappedBy = "store", cascade = {"persist", "remove"})
     */
    protected $reference;

    /* ... */
}

Store Importer(示例,其中省略了不相关的代码)

namespace App\Service\Api\Import;

/* ... */

use Symfony\Component\Validator\Validator\ValidatorInterface;

class StoreImporter
{
    /**
     * @var StoreManagerInterface
     */
    private $_storeManager;

    /**
     * @var ValidatorInterface
     */
    private $_validator;

    /**
     * @param StoreManagerInterface    $storeManager
     * @param ValidatorInterface $validator
     */
    public function __construct(StoreManagerInterface $storeManager, ValidatorInterface $validator)
    {
        $this->_storeManager = $storeManager;
        $this->_validator = $validator;
    }

    public function import()
    {
        /* ... */

        foreach ($storeHashes as $md5hash => $storeDetails) {

            /* @var Store $store */
            $store = $this->_storeManager->findOrCreateByReference(
                $storeDetails["company_id"], // cs-cart ID
                $storeDetails["company"]     // store name
            );

            /* ... */

            $validationErrors = $this->_validator->validate($store);

            if (count($validationErrors) > 0) {

                $validationContext = [
                    "errors"  => (string) $validationErrors,
                    "details" => $storeDetails,
                ];

                $this->logError("Skipping " . (string) $store . " - Store has validation errors", $validationContext);

                continue; // skip saving store details when there are validation errors
            }

            /* ... */

            $this->_storeManager->saveOne($store, false);

        }

        /* ... */

        $this->_storeManager->flush();
        $this->_storeManager->clear();

        /* ... */
    }

    /* ... */
}

编辑2:这是在唯一实体验证器的第139行发生的情况。

138
139 $result = $repository->{$constraint->repositoryMethod}($criteria);
140

转储的变量($ constraint和$ criteria)

"constraint" => Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity {#2                                462
    +message: "device-model-is-not-unique"
    +service: "doctrine.orm.validator.unique"
    +em: null
    +entityClass: null
    +repositoryMethod: "findBy"
    +fields: array:2 [
      0 => "model"
      1 => "brand"
    ]
    +errorPath: "model"
    +ignoreNull: true
    +payload: null
    +"groups": array:2 [
      0 => "Default"
      1 => "Device"
    ]
  }
"criteria" => array:2 [
    "model" => "Xperia XZ"
    "brand" => App\Entity\Model\DeviceBrand {#2183
      #type: "device_brand"
      #devices: Doctrine\ORM\PersistentCollection {#2184
        -snapshot: []
        -owner: App\Entity\Model\DeviceBrand {#2183}
        -association: array:16 [ …16]
        -em: Doctrine\ORM\EntityManager {#561 …11}
        -backRefFieldName: "brand"
        -typeClass: Doctrine\ORM\Mapping\ClassMetadata {#128 …}
        -isDirty: false
        #collection: Doctrine\Common\Collections\ArrayCollection {#2185
          -elements: []
        }
        #initialized: false
      }
      #id: 14
      #name: "Sony"
      #slug: "sony"
      #createdAt: DateTime @1560754604 {#2182
        date: 2019-06-17 08:56:44.0 Europe/Berlin (+02:00)
      }
      #createdBy: null
      #deleted: false
      #deletedAt: null
      #deletedBy: null
      #updatedAt: null
      #updatedBy: null
    }
  ]

编辑#3:

我通过尝试调试一些奇怪的行为 教义ORM库中的UnitOfWork类。 显然,在objectIdentifiers数组中找不到对象哈希。 找不到的实体类型是与我要更新的实体的OneToOne关系。

UnitOfWork(经过修改)

    /**
     * Gets the identifier of an entity.
     * The returned value is always an array of identifier values. If the entity
     * has a composite identifier then the identifier values are in the same
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
     *
     * @param object $entity
     *
     * @return array The identifier values.
     */
    public function getEntityIdentifier($entity)
    {
        if (!array_key_exists(spl_object_hash($entity), $this->entityIdentifiers)) {
            dump([
                "entity-class" => get_class($entity),
                "object-hash"  => spl_object_hash($entity),
                "identifiers"  => $this->entityIdentifiers,
            ]);
            exit();
        }

        return $this->entityIdentifiers[spl_object_hash($entity)];
    }

转储结果:

array:3 [
  "entity-class" => "App\Entity\Model\Reference\StoreReference"
  "object-hash" => "000000002f4c81ef00000000678832e6"
  "identifiers" => array:6 [
    "000000002f4c805b00000000678832e6" => array:1 [
      "id" => 1
    ]
    "000000002f4c805700000000678832e6" => array:1 [
      "id" => 2
    ]
    "000000002f4c813600000000678832e6" => array:1 [
      "id" => 6
    ]
    "000000002f4c816100000000678832e6" => array:1 [
      "id" => 2
    ]
    "000000002f4c819f00000000678832e6" => array:1 [
      "id" => 1
    ]
    "000000002f4c81e200000000678832e6" => array:1 [
      "id" => 1
    ]
  ]
]

商店参考实体


namespace App\Entity\Model\Reference;

use App\Entity\Model\Store;
use Doctrine\ORM\Mapping as ORM;

use App\Entity\Model\Reference\AbstractReferenceEntity;

/**
 * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
 * @ORM\Entity()
 * @ORM\Table(name = "s4_store_reference", indexes = {
 *     @ORM\Index(name = "reference_external_id_search_index", columns = {"external_id"})
 * })
 */
class StoreReference extends AbstractReferenceEntity
{
    /**
     * @return string
     */
    public function __toString()
    {
        return "[" . $this->id . "] StoreReference";
    }

    /**
     * ID
     *
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(name = "store_reference_id", type = "integer", unique = true)
     * @ORM\GeneratedValue(strategy = "AUTO")
     */
    protected $id;

    /**
     * @var Store
     *
     * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
     * @ORM\OneToOne(targetEntity = "App\Entity\Model\Store", inversedBy = "reference", cascade = { "persist", "remove" })
     * @ORM\JoinColumn(name = "store_id", referencedColumnName = "store_id", nullable = false)
     */
    protected $store;

    /* ... additional methods ... */
}

我也在尝试在this github repository

中重现此问题。

1 个答案:

答案 0 :(得分:0)

因此,我找到了解决该问题的方法,这是一个Doctrine错误。这不是很常规,但在此之前一直有效,直到Doctrine ORM 2.6.4发行版可用为止。

我对所有错误的OneToOne关联进行了不同的配置。通过将关系更改为ManyToOne关系,并以编程方式坚持并仅选择一个实体,我仍然具有“一对一”关系,并且我的应用程序将继续正常运行。代码:

设备实体:

    /**
     * Device References
     *
     * One Device has one Reference
     *
     * note: this relation is configured as a One to Many association to work around a bug in Doctrine
     *
     * @var ArrayCollection|DeviceReference[]
     *
     * @ORM\Cache(region = "rarely_changing", usage = "NONSTRICT_READ_WRITE")
     * @ORM\OneToMany(targetEntity = "App\Entity\Model\Reference\DeviceReference", mappedBy = "device", cascade = {"persist", "remove"})
     */
    protected $references;

    /**
     * Add reference
     *
     * @param DeviceReference $reference
     *
     * @return self
     */
    public function addReference(DeviceReference $reference)
    {
        $reference->setDevice($this);

        $this->references = new ArrayCollection([$reference]);

        return $this;
    }

    /**
     * Remove reference
     *
     * @param DeviceReference $reference
     */
    public function removeReference(DeviceReference $reference)
    {
        $this->references->removeElement($reference);
    }

    /**
     * Get references
     *
     * @return Collection|DeviceReference[]
     */
    public function getReferences()
    {
        return $this->references;
    }

    /**
     * Set reference
     *
     * @param DeviceReference $reference
     *
     * @return self
     */
    public function setReference(DeviceReference $reference)
    {
        $this->addReference($reference);

        return $this;
    }

    /**
     * Get reference
     *
     * @return DeviceReference
     */
    public function getReference()
    {
        if (0 === $this->references->count()) {
            return null;
        }

        return $this->references->first();
    }

DeviceReference实体:


    /**
     * Device
     *
     * One Reference has one Device
     *
     * note: this relation is configured as a Many to One association to work around a bug in Doctrine
     *
     * @var Device
     *
     * @ORM\Cache(region = "commonly_changing", usage = "NONSTRICT_READ_WRITE")
     * @ORM\ManyToOne(targetEntity = "App\Entity\Model\Device", inversedBy = "references")
     * @ORM\JoinColumn(name = "device_id", referencedColumnName = "device_id", nullable = false, unique = true)
     */
    private $device;

    /* ... getter and setter remain the same ... */

别忘了更新存储库中的任何自定义查询!