169 lines
6.6 KiB
PHP
169 lines
6.6 KiB
PHP
<?php
|
|
namespace MailPoetVendor\Symfony\Component\Validator\Command;
|
|
if (!defined('ABSPATH')) exit;
|
|
use MailPoetVendor\Symfony\Component\Console\Command\Command;
|
|
use MailPoetVendor\Symfony\Component\Console\Helper\Dumper;
|
|
use MailPoetVendor\Symfony\Component\Console\Helper\Table;
|
|
use MailPoetVendor\Symfony\Component\Console\Input\InputArgument;
|
|
use MailPoetVendor\Symfony\Component\Console\Input\InputInterface;
|
|
use MailPoetVendor\Symfony\Component\Console\Input\InputOption;
|
|
use MailPoetVendor\Symfony\Component\Console\Output\OutputInterface;
|
|
use MailPoetVendor\Symfony\Component\Console\Style\SymfonyStyle;
|
|
use MailPoetVendor\Symfony\Component\Finder\Exception\DirectoryNotFoundException;
|
|
use MailPoetVendor\Symfony\Component\Finder\Finder;
|
|
use MailPoetVendor\Symfony\Component\Validator\Constraint;
|
|
use MailPoetVendor\Symfony\Component\Validator\Mapping\AutoMappingStrategy;
|
|
use MailPoetVendor\Symfony\Component\Validator\Mapping\CascadingStrategy;
|
|
use MailPoetVendor\Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
|
use MailPoetVendor\Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
|
|
use MailPoetVendor\Symfony\Component\Validator\Mapping\GenericMetadata;
|
|
use MailPoetVendor\Symfony\Component\Validator\Mapping\TraversalStrategy;
|
|
class DebugCommand extends Command
|
|
{
|
|
protected static $defaultName = 'debug:validator';
|
|
protected static $defaultDescription = 'Display validation constraints for classes';
|
|
private $validator;
|
|
public function __construct(MetadataFactoryInterface $validator)
|
|
{
|
|
parent::__construct();
|
|
$this->validator = $validator;
|
|
}
|
|
protected function configure()
|
|
{
|
|
$this->addArgument('class', InputArgument::REQUIRED, 'A fully qualified class name or a path')->addOption('show-all', null, InputOption::VALUE_NONE, 'Show all classes even if they have no validation constraints')->setDescription(self::$defaultDescription)->setHelp(<<<'EOF'
|
|
The <info>%command.name% 'App\Entity\Dummy'</info> command dumps the validators for the dummy class.
|
|
The <info>%command.name% src/</info> command dumps the validators for the `src` directory.
|
|
EOF
|
|
);
|
|
}
|
|
protected function execute(InputInterface $input, OutputInterface $output) : int
|
|
{
|
|
$class = $input->getArgument('class');
|
|
if (\class_exists($class)) {
|
|
$this->dumpValidatorsForClass($input, $output, $class);
|
|
return 0;
|
|
}
|
|
try {
|
|
foreach ($this->getResourcesByPath($class) as $class) {
|
|
$this->dumpValidatorsForClass($input, $output, $class);
|
|
}
|
|
} catch (DirectoryNotFoundException $exception) {
|
|
$io = new SymfonyStyle($input, $output);
|
|
$io->error(\sprintf('Neither class nor path were found with "%s" argument.', $input->getArgument('class')));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
private function dumpValidatorsForClass(InputInterface $input, OutputInterface $output, string $class) : void
|
|
{
|
|
$io = new SymfonyStyle($input, $output);
|
|
$title = \sprintf('<info>%s</info>', $class);
|
|
$rows = [];
|
|
$dump = new Dumper($output);
|
|
$classMetadata = $this->validator->getMetadataFor($class);
|
|
foreach ($this->getClassConstraintsData($classMetadata) as $data) {
|
|
$rows[] = ['-', $data['class'], \implode(', ', $data['groups']), $dump($data['options'])];
|
|
}
|
|
foreach ($this->getConstrainedPropertiesData($classMetadata) as $propertyName => $constraintsData) {
|
|
foreach ($constraintsData as $data) {
|
|
$rows[] = [$propertyName, $data['class'], \implode(', ', $data['groups']), $dump($data['options'])];
|
|
}
|
|
}
|
|
if (!$rows) {
|
|
if (\false === $input->getOption('show-all')) {
|
|
return;
|
|
}
|
|
$io->section($title);
|
|
$io->text('No validators were found for this class.');
|
|
return;
|
|
}
|
|
$io->section($title);
|
|
$table = new Table($output);
|
|
$table->setHeaders(['Property', 'Name', 'Groups', 'Options']);
|
|
$table->setRows($rows);
|
|
$table->setColumnMaxWidth(3, 80);
|
|
$table->render();
|
|
}
|
|
private function getClassConstraintsData(ClassMetadataInterface $classMetadata) : iterable
|
|
{
|
|
foreach ($classMetadata->getConstraints() as $constraint) {
|
|
(yield ['class' => \get_class($constraint), 'groups' => $constraint->groups, 'options' => $this->getConstraintOptions($constraint)]);
|
|
}
|
|
}
|
|
private function getConstrainedPropertiesData(ClassMetadataInterface $classMetadata) : array
|
|
{
|
|
$data = [];
|
|
foreach ($classMetadata->getConstrainedProperties() as $constrainedProperty) {
|
|
$data[$constrainedProperty] = $this->getPropertyData($classMetadata, $constrainedProperty);
|
|
}
|
|
return $data;
|
|
}
|
|
private function getPropertyData(ClassMetadataInterface $classMetadata, string $constrainedProperty) : array
|
|
{
|
|
$data = [];
|
|
$propertyMetadata = $classMetadata->getPropertyMetadata($constrainedProperty);
|
|
foreach ($propertyMetadata as $metadata) {
|
|
$autoMapingStrategy = 'Not supported';
|
|
if ($metadata instanceof GenericMetadata) {
|
|
switch ($metadata->getAutoMappingStrategy()) {
|
|
case AutoMappingStrategy::ENABLED:
|
|
$autoMapingStrategy = 'Enabled';
|
|
break;
|
|
case AutoMappingStrategy::DISABLED:
|
|
$autoMapingStrategy = 'Disabled';
|
|
break;
|
|
case AutoMappingStrategy::NONE:
|
|
$autoMapingStrategy = 'None';
|
|
break;
|
|
}
|
|
}
|
|
$traversalStrategy = 'None';
|
|
if (TraversalStrategy::TRAVERSE === $metadata->getTraversalStrategy()) {
|
|
$traversalStrategy = 'Traverse';
|
|
}
|
|
if (TraversalStrategy::IMPLICIT === $metadata->getTraversalStrategy()) {
|
|
$traversalStrategy = 'Implicit';
|
|
}
|
|
$data[] = ['class' => 'property options', 'groups' => [], 'options' => ['cascadeStrategy' => CascadingStrategy::CASCADE === $metadata->getCascadingStrategy() ? 'Cascade' : 'None', 'autoMappingStrategy' => $autoMapingStrategy, 'traversalStrategy' => $traversalStrategy]];
|
|
foreach ($metadata->getConstraints() as $constraint) {
|
|
$data[] = ['class' => \get_class($constraint), 'groups' => $constraint->groups, 'options' => $this->getConstraintOptions($constraint)];
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
private function getConstraintOptions(Constraint $constraint) : array
|
|
{
|
|
$options = [];
|
|
foreach (\array_keys(\get_object_vars($constraint)) as $propertyName) {
|
|
// Groups are dumped on a specific column.
|
|
if ('groups' === $propertyName) {
|
|
continue;
|
|
}
|
|
$options[$propertyName] = $constraint->{$propertyName};
|
|
}
|
|
\ksort($options);
|
|
return $options;
|
|
}
|
|
private function getResourcesByPath(string $path) : array
|
|
{
|
|
$finder = new Finder();
|
|
$finder->files()->in($path)->name('*.php')->sortByName(\true);
|
|
$classes = [];
|
|
foreach ($finder as $file) {
|
|
$fileContent = \file_get_contents($file->getRealPath());
|
|
\preg_match('/namespace (.+);/', $fileContent, $matches);
|
|
$namespace = $matches[1] ?? null;
|
|
if (!\preg_match('/class +([^{ ]+)/', $fileContent, $matches)) {
|
|
// no class found
|
|
continue;
|
|
}
|
|
$className = \trim($matches[1]);
|
|
if (null !== $namespace) {
|
|
$classes[] = $namespace . '\\' . $className;
|
|
} else {
|
|
$classes[] = $className;
|
|
}
|
|
}
|
|
return $classes;
|
|
}
|
|
}
|