php - Symfony Validator Component issue in Standalone Applicatin

156

I am realizing that perhaps the way I want to make use of the Validator Component from Symfony is not possible. Here is the idea.

I have a class called Package which for now has only one property named namespace. Usually I would include the ClassMetadata and any constraint object I would like to validate against within my Package class. However, my idea is that instead of doing that I would rather keep my subject clean and only responsible for the things it must be responsible for.

Below is a class I wrote and call it PackageValidater:

<?php
 use Symfony\Component\Validator\Constraints as Assert;
 use Symfony\Component\Validator\Mapping\ClassMetadata;
 use Symfony\Component\Validator\Validation;

 class PackageValidator
 {
   protected $subject;

   public function PackageValidator($subject){
    $this->subject = $subject;
   }

   public static function loadMetadata(){
    $metadata->addPropertyConstraint('namespace', new new Assert\Type(['type' => 'string']));
   }

   public function getViolations(){
    $validator = Validation::createValidatorBuilder()
       ->addMethodMapping('loadMetadata')
       ->getValidator();

    $violations = $validator->validate($this->subject);

    return !empty($violations) ? $violations : [];
   }

  }

Despite of the fact that I am not sure about the usage of my constraint since most reference uses annotations and I do not we can ignore that part. I also am aware of the fact that my test fails due to this fact. However, my issue is with my design because I have not added the static function that the Validation object uses to build the validation. Instead of my method mapping where constraints reside being in the actual object it resides on a separate class.

The idea is to enforce separation of concerns and single responsibility on my objects. Below is a diagram that depicts exactly what I am trying to achieve:

enter image description here

I have written my test as shown below:

$packageValidator = new PackageValidator(new Package([0 => 'test']));
$this->assertTrue(true, empty($packageValidator->getViolations()));

Above I have passed in an array instead of a string which would make my test fail because there can never be a single namespace that is in a form of array - at least not in what I am trying to achieve.

The issue is with my getViolations method inside the PackageValidator object because I am not passing my subject outside the context of my validation process that is define the subject metadata inside the subject itself then when getting the validator object with the refence to the subject's metadata get the validation errors.

All in all Package does not have loadMetadata method but PackageValidator. How can I make this possible without polluting every object I want to validate with the metadata functionality?

Below is what I get from PHPUnit:

SimplexTest\Validate\Package\PackageValidatorTest::testIfValidatorInterfaceWorks Symfony\Component\Validator\Exception\RuntimeException: Cannot validate values of type "NULL" automatically. Please provide a constraint.

659

Answer

Solution:

You can use yml or xml configuration to add constraints to your object.

http://symfony.com/doc/current/book/validation.html#the-basics-of-validation

You do this by creating a file calledvalidation.yml in your Bundle configuration directory. Add the following content to validate your object:

Some\Name\Space\Package:
    properties:
        name:
            - NotBlank: ~

That's one way to keep things you don't consider a responsibility for your object out of said object. It also removes the need for a custom validator class for every object you create. You can simply make use of the validator service already provided by the framework.

Edit

Alright I think I figured something out you might be looking for: you can create aMetadataFactory to loadMetadata the way you want. There are a couple of examples here: https://github.com/symfony/validator/tree/master/Mapping/Factory

It basically boils down to a Factory class that returns an instance ofMetadataInterface where you attach your constraints. This means that you can have the Factory read metadata from anything. You could for example do something like this:

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Your\Package;

class PackageMetadataFactory implements MetadataFactoryInterface
{
    /**
     * Create a ClassMetaData object for your Package object
     *
     * @param object $value The object that will be validated
     */
    public function getMetadataFor($value)
    {

        // Create a class meta data object for your entity
        $metadata = new ClassMetadata(Package::class);

        // Add constraints to your metadata
        $metadata->addPropertyConstraint(
            'namespace', new Assert\Type(['type' => 'string']));

        // Return the class metadata object
        return $metadata;
    }

    /**
     * Test if the value provided is actually of type Package
     *
     * @param object $value The object that will be validated
     */
    public function hasMetadataForValue($value)
    {
        return $value instanceof Package::class;
    }
}

Then in yourPackageValidator all you have to do is:

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Validation;
use Your\PackageMetadataFactory;

class PackageValidator
{
    protected $subject;

    public function PackageValidator($subject) {
        $this->subject = $subject;
    }

    public function getViolations() {
        $validator = Validation::createValidatorBuilder()
            ->setMetadataFactory(new PackageMetadataFactory())
            ->getValidator();

        $violations = $validator->validate($this->subject);

        return !empty($violations) ? $violations : [];
    }

}

Hopefully this is more in line of what you're looking for.

184

Answer

Solution:

I have followed your suggestion above as you have put it. The only thing I had to change was the hasMetadaFor method implementation inside the PackageMetadataFactory. Below is how I rather check for property existence.

public function hasMetadataFor( $value ){
 return property_exists(Package::class, $value);
}

Everything else as you suggested works perfectly. Below is my test function.

$validator = new PackageValidator(new OrderPackage(125787618));
$this->assertSame(true, $validator->validates());

The test fails because the namespace cannot be numbers. Passing the fully qualified class name of the OrderPackage by doing OrderPackage ::class validates the object.

Thank you very much for your advice.

People are also looking for solutions to the problem: javascript - Check if an external url is working PHP or JS

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.