Jump to content

Recommended Posts

A while back, I added a post which discussed how to validate PHP's scripts to ensure that all attributes had an available class.  My reason for doing so is annotations will throw an error yet attributes will not work but will not provide a warning and are difficult to troubleshot.   For example, I will get a warning for $propertyWithError but not for $propertyWithoutError.

<?php

namespace App\Entity;
// use Symfony\Component\Validator\Constraints as Assert;

class SomeEntity
{
    /**
     * @Assert\NotBlank
     */
    private string $propertyWithError;

    #[Assert\NotBlank]
    private string $propertyWithoutError;
}

I then created the a composer package which would parse all the PHP scripts and look for attributes without existing classes.

I then created a Symfony command script (App\Command/AttributeValidator) which would use my package and at the CLI I could execute: bin/console app:attribute-validator

<?php
declare(strict_types=1);

namespace App\Command;

use Nette\Utils\Strings;
use Nette\Utils\Json;
use RuntimeException;
use Exception;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;

use NotionCommotion\AttributeValidator\AttributeValidator as NotionCommotionAttributeValidator;
use NotionCommotion\AttributeValidator\AttributeValidatorException;

final class AttributeValidator extends Command
{
    private const COMMANDS = ['validate', 'getClassesWithUndeclaredAttributes', 'getClassesWithoutUndeclaredAttributes', 'getSuspectClasses', 'getNotFoundClasses', 'getTraits', 'getInterfaces', 'getAbstracts', 'jsonSerialize', 'debugSuspectFiles', 'debugFile'];
    // the name of the command (the part after "bin/console")
    /**
     * @var string
     */
    protected static $defaultName = 'app:attribute-validator';

    protected function configure(): void
    {
        $this
        ->setDescription('Attribute Validator.  --help')
        ->setHelp('This command allows you to check for PHP8 attributes without classes')
        ->addArgument('path', InputArgument::OPTIONAL, 'Path to check', 'src')
        ->addOption('command', null, InputOption::VALUE_REQUIRED, 'What command to run?', 'validate')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $command = $input->getOption('command');
        if(!in_array($command, self::COMMANDS)) {
            throw new \Exception(sprintf('Invalid command %s.  Only %s are allowed', $command, implode(', ', self::COMMANDS)));
        }
        if($command==='validate') {
            return $this->$command($input, $output);
        }
        else {
            $output->writeln([
                'Attribute Validator',
                '============',
                '',
            ]);
            $output->writeln("Command: ".$command);
            $path = $input->getArgument('path');
            $output->writeln("Path to check: ".$path);
            if($command==='debugFile'){
                print_r(NotionCommotionAttributeValidator::debugFile($path));
            }
            else{
                print_r(NotionCommotionAttributeValidator::create($path)->$command());
            }
            return Command::SUCCESS;
        }
    }

    private function validate(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln([
            'Attribute Validator',
            '============',
            '',
        ]);

        $path = $input->getArgument('path');
        $output->writeln("Path to check: ".$path);

        $helper = $this->getHelper('question');

        /*
        $question = new ConfirmationQuestion('Continue with this action?', true);
        if (!$helper->ask($input, $output, $question)) {
            return Command::SUCCESS;
        }
        */

        $validator = NotionCommotionAttributeValidator::create($path);
        $errors=0;

        foreach($validator->validate() as $type=>$errs) {
            $output->writeln($type.' errors');
            switch($type) {
                case 'classesWithUndeclaredAttributes':
                    foreach($errs as $e) {
                        $output->writeln(sprintf('   %s: %s', $e['fqcn'], $e['filename']));
                        if(!empty($e['classAttributes'])) {
                            $errors++;
                            $output->writeln(sprintf('      classAttributes: %s', implode(', ', $e['classAttributes'])));
                        }
                        foreach(array_intersect_key($e, array_flip(['propertyAttributes', 'methodAttributes', 'parameterAttributes', 'classConstantAttributes'])) as $attrType=>$ar) {
                            $a = [];
                            foreach($ar as $class=>$t) {
                                $errors++;
                                $a[] = sprintf('%s: %s', $class, implode(', ', $t));
                            }
                            if($a) {
                                $output->writeln(sprintf('      %s: %s', $attrType, implode(', ', $a)));
                            }
                        }
                    }
                    break;
                case 'notFoundClasses':
                case 'suspectClasses':
                    foreach($errs as $e) {
                        $errors++;
                        $a = [];
                        foreach(['class', 'trait', 'interface', 'abstract'] as $t) {
                            if($e[$t]) {
                                $a[] = sprintf('%s: %s', $t, implode(', ', $e[$t]));
                            }
                        }
                        $output->writeln(sprintf('   %s: %s %s', $e['filename'], $e['namespace'], implode(' | ', $a)));
                    }
                    break;
                default: throw new AttributeValidatorException('Invalid test: '.$type);
            }
        }
        $output->writeln('Error count: '.$errors);

        return Command::SUCCESS;
    }
}


It all works except I don't want to have to copy App\Command/AttributeValidator to each project and so created a second composer package to install it.  When executing composer require notion-commotion/attribute-validator-command, this second package is installed but not any of its dependencies.

What am I doing wrong?

Also, off topic and my primary question is related to composer installing dependencies, however, if anyone knows the proper way to have composer install a symfony command, please let me know.

Thanks!

{
    "name": "notion-commotion/attribute-validator-command",
    "description": "Creates Symfony command to find attributes which do not have classes assigned.",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Reed",
            "email": "xxx@gmail.com"
        }
    ],
    "require": {
        "php": ">=8",
        "symfony/console": "5.3.*",
        "nette/utils": "4",
        "notion-commotion/attribute-validator": "^1"
    },
    "autoload": {
        "psr-4": {
            "NotionCommotion\\AttributeValidatorCommand\\": "src/"
        }
    },
    "minimum-stability": "stable",
    "require": {}
}

 

  • Like 1
29 minutes ago, kicken said:

Your composer.json file defines two require keys.  The first one has your intended requirements, the second later on is empty.  The empty one overrides your first one.

Well, I feel stupid.  Potentially, I did not really do so but just did so when posting this question but hope that I really did so...

13 hours ago, kicken said:

Your composer.json file defines two require keys.  The first one has your intended requirements, the second later on is empty.  The empty one overrides your first one.

Thanks again kicken,  Yes, you are 100% correct.  I should have initially noticed my error but with multiple new concepts looked right past it.  Made the corrections and initially no go because of tag issues but eventually worked.

While I could go forward with having my new notion-commotion/attribute-validator-command along with its dependencies in the vendor directory, it will be a bit of a hack.  I will then need to manually add a small script in App\Command which uses this new class.  Know how to modify composer.json in order to make this automatic?  Normally I add my custom command scripts to App\Command namespace but I am thinking doing so isn't right since composer probably should have the ability to modify anything in \src.  True?  If not, does it go in \bin?

Have a look at the composer's documentation for vendor binaries and maybe scripts.  By adding a script to your project, you could run your command via something like composer attribute-validator.  If you add the vendor/bin path to your PATH then you could just run attribute-validator directly.

 

 

  • Like 1
This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.