Skip to content

Instantly share code, notes, and snippets.

@zanbaldwin
Last active August 31, 2018 14:10
Show Gist options
  • Save zanbaldwin/cebef05ae52998238dd585bd31c33937 to your computer and use it in GitHub Desktop.
Save zanbaldwin/cebef05ae52998238dd585bd31c33937 to your computer and use it in GitHub Desktop.

Constraint Structure Generating Validator

This is a hacked together prototype for an idea I had, do not rely on this implementation for anything other than research purposes.

Extends the standard Symfony validator to add methods that allow you to generate a valid constraint list from a given data class. This can then be used to validate a data structure such as a request payload without having to map that data onto a concrete instance of the data class (such as an entity or model).

Example

<?php declare(strict_types=1);

use Symfony\Component\Validator\Constraints as Assert;

class Address
{
    /**
     * @Assert\NotBlank
     * @Assert\Type(type="string")
     */
    public $postcode;
}

class User
{

    /**
     * @Assert\NotBlank
     * @Assert\Type(type="string")
     */
    public $username;

    /**
     * @Assert\NotBlank
     * @Assert\Date
     */
    public $dob;

    /**
     * @Assert\NotNull
     * @Assert\Valid
     * @Assert\Type(type="Address")
     */
    public $address;
}

/** @var ConstraintStructureGeneratingValidator $validator */
$constraintList = $validator->getConstraintStructureFor(User::class);
$validator->validate($request->request->all(), $constraintList);
<?php declare(strict_types=1);
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Validator\RecursiveValidator;
use Symfony\Component\Validator\Validator\ValidatorInterface;
interface ConstraintStructureGeneratorInterface extends ValidatorInterface
{
public function getConstraintStructureFor($target): array;
public function getConstraintStructureFromMetaData(MetadataInterface $metadata): array;
}
class ConstraintStructureGeneratingValidator extends RecursiveValidator implements ConstraintStructureGeneratorInterface
{
public function getConstraintStructureFor($target): array
{
if (is_object($target)) {
$target = \get_class($target);
}
if (\is_string($target) && \class_exists($target)) {
return $this->getConstraintStructureFromMetadata($this->getMetadataFor($target));
}
throw new \InvalidArgumentException('Generating constraint structure only available for objects and class names.');
}
public function getConstraintStructureFromMetadata(MetadataInterface $metadata): array
{
$constraints = $metadata->getConstraints();
if ($metadata instanceof ClassMetadataInterface) {
$properties = $metadata->getConstrainedProperties();
$fields = array_combine($properties, array_map(function (string $property) use ($metadata) {
$constraints = array_map(function (MetadataInterface $propertyMetaData) {
return $this->getConstraintStructureFromMetadata($propertyMetaData);
}, $metadata->getPropertyMetadata($property));
$constraints = array_reduce($constraints, function (array $carry, array $constraints): array {
return array_merge($carry, $constraints);
}, []);
$constraints = array_map(function (Constraint $constraint) {
if ($constraint instanceof Type && class_exists($constraint->type)) {
return $this->getConstraintStructureFromMetadata($this->getMetadataFor($constraint->type));
}
return $constraint;
}, $constraints);
return $constraints;
}, $properties));
$constraints[] = new Collection(['fields' => $fields]);
}
return $constraints;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment