Skip to content

Proof concept errors in Symfony 7 #11

@logosur

Description

@logosur

Describe the bug

The library is currently unusable for its main purpose, registering an invoice, due to two critical issues:

  1. Overly Strict Initial Validation: The InvoiceSubmission::validate() method, which is called internally by Verifactu::registerInvoice(), requires several properties to be non-empty strings even before they can be logically generated by the library itself (e.g., xmlSignature) or are optional according to the documentation (e.g., operationDate, invoiceAgreementNumber). This creates a catch-22 situation where it's impossible to submit an invoice because the validation fails before the submission process can even begin.

  2. Violation of Encapsulation: The HashGeneratorService attempts to directly access the protected property $invoiceId on the InvoiceSubmission object ($invoice->invoiceId). This violates PHP's encapsulation rules and causes a fatal error, preventing the hash generation from completing. The service should use a public getter method instead.

These two issues combined make it impossible to follow the basic usage example in the README.md without patching the vendor code.

To Reproduce

Steps to reproduce the behavior:

  1. Setup a new Symfony 7 project with PHP 8.3 and install the library:

    composer require eseperio/verifactu-php:dev-master
  2. Create a console command to execute the "Register an Invoice" example from the README.md examples within the library. Create the file src/Command/TestVerifactuCommand.php with the following content:

    <?php
    namespace App\Command;
    
    use eseperio\verifactu\models\BreakdownDetail;
    use eseperio\verifactu\models\Chaining;
    use eseperio\verifactu\models\ComputerSystem;
    use eseperio\verifactu\models\enums\HashType;
    use eseperio\verifactu\models\enums\InvoiceType;
    use eseperio\verifactu\models\enums\OperationQualificationType;
    use eseperio\verifactu\models\enums\YesNoType;
    use eseperio\verifactu\models\InvoiceId;
    use eseperio\verifactu\models\InvoiceSubmission;
    use eseperio\verifactu\models\LegalPerson;
    use eseperio\verifactu\Verifactu;
    use Symfony\Component\Console\Attribute\AsCommand;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    use Symfony\Component\Console\Style\SymfonyStyle;
    
    #[AsCommand(name: 'verifactu:test')]
    class TestVerifactuCommand extends Command
    {
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $io = new SymfonyStyle($input, $output);
    
            Verifactu::config(
                '/path/to/your/test-certificate.p12', // Adjust path
                'testpassword',
                Verifactu::TYPE_CERTIFICATE,
                Verifactu::ENVIRONMENT_SANDBOX
            );
    
            $invoice = new InvoiceSubmission();
            $invoiceId = new InvoiceId();
            $invoiceId->issuerNif = '99999999R';
            $invoiceId->seriesNumber = 'FA2025/POC001';
            $invoiceId->issueDate = '2025-07-16';
            $invoice->setInvoiceId($invoiceId);
            $invoice->issuerName = 'Empresa Ejemplo POC SL';
            $invoice->invoiceType = InvoiceType::STANDARD;
            $invoice->operationDescription = 'Venta de productos de prueba POC';
            $invoice->taxAmount = 21.00;
            $invoice->totalAmount = 121.00;
            $breakdownDetail = new BreakdownDetail();
            $breakdownDetail->taxRate = 21.0;
            $breakdownDetail->taxableBase = 100.00;
            $breakdownDetail->taxAmount = 21.00;
            $breakdownDetail->operationQualification = OperationQualificationType::SUBJECT_NO_EXEMPT_NO_REVERSE;
            $invoice->addBreakdownDetail($breakdownDetail);
            $chaining = new Chaining();
            $chaining->setAsFirstRecord();
            $invoice->setChaining($chaining);
            $computerSystem = new ComputerSystem();
            $computerSystem->systemName = 'Test ERP';
            $computerSystem->version = '1.0';
            $computerSystem->providerName = 'Test Software Provider';
            $computerSystem->systemId = '01';
            $computerSystem->installationNumber = '1';
            $computerSystem->onlyVerifactu = YesNoType::YES;
            $computerSystem->multipleObligations = YesNoType::NO;
            $provider = new LegalPerson();
            $provider->name = 'Test Software Provider SL';
            $provider->nif = 'B12345678';
            $computerSystem->setProviderId($provider);
            $invoice->setSystemInfo($computerSystem);
            $invoice->recordTimestamp = (new \DateTime())->format('Y-m-d\TH:i:sP');
            $invoice->hashType = HashType::SHA_256;
            $invoice->hash = hash('sha256', time());
    
            try {
                $io->writeln('Submitting invoice...');
                Verifactu::registerInvoice($invoice);
                $io->success('Done.');
            } catch (\Exception $e) {
                $io->error($e->getMessage());
                $io->writeln($e->getTraceAsString());
                return Command::FAILURE;
            }
    
            return Command::SUCCESS;
        }
    }
  3. Run the command:

    php bin/console verifactu:test
  4. See the first error (Validation). The command fails because Verifactu::registerInvoice() internally calls validate(), which throws an exception.

    InvoiceSubmission validation failed: Array
    (
    [xmlSignature] => Array
    (
    [0] => Must be a string.
    )
    [operationDate] => Array
    (
    [0] => Must be a string.
    )
    ...
    )

  5. Attempt a workaround. Modify the command to set dummy values to bypass the validation.

    // ... inside the command, before the try-catch block
    $invoice->operationDate = '2025-07-16';
    $invoice->xmlSignature = '';
    $invoice->invoiceAgreementNumber = '';
    $invoice->systemAgreementId = '';
  6. Run the command again.

  7. See the second error (Protected Property Access). The process now fails at the hash generation stage.

    In HashGeneratorService.php line 41:
    Cannot access protected property eseperio\verifactu\models\InvoiceSubmission::$invoiceId

Expected behavior

  1. The validate() method should not require fields that are generated later in the process (like xmlSignature) or are clearly optional. The validation logic should be separated: first validate user input, then, after internal processing, validate the final model if necessary.
  2. The HashGeneratorService should access the invoiceId property via a public getter method (e.g., $invoice->getInvoiceId()) to respect encapsulation.
  3. The basic example from the README.md should work out-of-the-box, successfully sending a request to the AEAT sandbox and returning a response, whether it's a success or a functional error from the AEAT services.

Desktop

  • OS: Linux
  • PHP Version: 8.3.23
  • Framework: Symfony 7.3.1
  • Library Version: dev-master

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions