読者です 読者をやめる 読者になる 読者になる

Best effort JavaScript

All essay implicitly 個人的には.

【Symfony2】Validatorクラスのvalidate()メソッドがConstraintsを取得するまでの流れ

group validationの条件を別ファイルに定義したいという要件があったので軽く処理を追ってみました。

環境

Symfony 2.8.6

$this->get('validator')->validate()の実装からConstraintsの取得まで

1. 内部で$visitor->validate()がコールされる

<?php // Symfony\Component\Validator\Validator

public function validate($value, $groups = null, $traverse = false, $deep = false)
{
    $visitor = $this->createVisitor($value);

    foreach ($this->resolveGroups($groups) as $group) {
        $visitor->validate($value, $group, '', $traverse, $deep);
    }

    return $visitor->getViolations();
}

2. metadataFactory->getMetadataFor()がコールされる

<?php // Symfony\Component\Validator\ValidationVisitor

public function validate($value, $group, $propertyPath, $traverse = false, $deep = false)
{
...
    } else {
        $this->metadataFactory->getMetadataFor($value)->accept($this, $value, $group, $propertyPath);
    }
}

3. ここでバリデーション対象クラスのMetadataをロードする

<?php // Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory

    public function getMetadataFor($className)
    {
        if (isset($this->loadedMetadata[$className])) {
            return $this->loadedMetadata[$className];
        }
...

ConstraintsはMetadataに含まれており、Metadataのロード中で行われている模様。

<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Validator\Mapping\Loader;

use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;

/**
 * Loads validation metadata by calling a static method on the loaded class.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class StaticMethodLoader implements LoaderInterface
{
    /**
     * The name of the method to call.
     *
     * @var string
     */
    protected $methodName;

    /**
     * Creates a new loader.
     *
     * @param string $methodName The name of the static method to call
     */
    public function __construct($methodName = 'loadValidatorMetadata')
    {
        $this->methodName = $methodName;
    }

    /**
     * {@inheritdoc}
     */
    public function loadClassMetadata(ClassMetadata $metadata)
    {
        /** @var \ReflectionClass $reflClass */
        $reflClass = $metadata->getReflectionClass();

        if (!$reflClass->isInterface() && $reflClass->hasMethod($this->methodName)) {
            $reflMethod = $reflClass->getMethod($this->methodName);

            if ($reflMethod->isAbstract()) {
                return false;
            }

            if (!$reflMethod->isStatic()) {
                throw new MappingException(sprintf('The method %s::%s should be static', $reflClass->name, $this->methodName));
            }

            if ($reflMethod->getDeclaringClass()->name != $reflClass->name) {
                return false;
            }

            $reflMethod->invoke(null, $metadata);

            return true;
        }

        return false;
    }
}

まとめ

  • 調べる前はConstraintsとgroup validationの関係性が理解出来ていなかったので、group validationの条件を別ファイルに定義したいという要件とはズレた調査を行ってしまったが、(Constraintsのロード方法を調べてしまった)そこの認識がやや整理された
  • バリデーション対象クラスのConstraints取得はDoctrine\Common\Persistence\Mapping\AbstractClassMetadataFactoryで行なわれている
  • そもそもgroup validationは1つのConstraints内で場合分けする機能なので別にファイルに分けるのは難しそう