Skip to content

There is a false-positive related to the Exception #9679

@l-you

Description

@l-you

Reproducible example: https://getrector.com/demo/e35f58d3-1734-41a0-a56f-f28fbf3350c7
Rector Version: 44a8dfcfbc50eab152582d466b3df5eceff009d7

Rector Output

1 file with changes
===================

1) src/Taxes/Fiscalization/ReceiptGeneratorService.php:80

    ---------- begin diff ----------
@@ @@
         try {
             $this->shiftService->maybeStartShift($jwt);
         } catch (ShiftStartException $e) {
-            throw new ReceiptCreateException(isClearLock: true, message: $e->getMessage(), previous: $e);
+            throw new ReceiptCreateException(isClearLock: true, message: $e->getMessage(), previous: $e, previous: $e);
         }
         $payload = new ReceiptSellPayload();
         $this->payloadService->fillDelivery($payload, $bill);
@@ @@
                     $text .= ': '.$detail[0]->getMsg();
                 }
             }
-            throw new ReceiptCreateException(isClearLock: true, message: $text, previous: $e);
+            throw new ReceiptCreateException(isClearLock: true, message: $text, previous: $e, previous: $e);
         } catch (Throwable $exception) {
-            throw new ReceiptCreateException(isClearLock: false, message: $exception->getMessage(), previous: $exception);
+            throw new ReceiptCreateException(isClearLock: false, message: $exception->getMessage(), previous: $exception, previous: $exception);
         }
         assert($result instanceof ReceiptModel);
    ----------- end diff -----------

Applied rules:
 * ThrowWithPreviousExceptionRector



 [OK] 1 file would have been changed (dry-run) by Rector

Original file

<?php

declare(strict_types=1);

namespace App\Taxes\Fiscalization;

use App\CheckboxUAClientInterface;
use App\Entity\PaymentGatewayFiscalizationCredentials;
use App\Entity\ShopPaymentBill;
use App\Entity\ShopPaymentBillFiscalizationError;
use App\Entity\ShopPaymentBillFiscalizationLock;
use App\Entity\ShopPaymentBillFiscalReceipt;
use App\Service\EntityManagerService;
use App\Taxes\Fiscalization\Exception\ReceiptCreateException;
use App\Taxes\Fiscalization\Exception\ShiftStartException;
use Grisaia\Time\Timestamp;
use RevoTale\CheckboxUA\Exception\CreateReceiptApiV1ReceiptsSellPostUnprocessableEntityException;
use RevoTale\CheckboxUA\Exception\UnexpectedStatusCodeException;
use RevoTale\CheckboxUA\Model\ReceiptModel;
use RevoTale\CheckboxUA\Model\ReceiptSellPayload;
use Throwable;

use function assert;

final readonly class ReceiptGeneratorService
{
   public function __construct(
       private ShiftService $shiftService,
       private JWTService $jwtService,
       private EntityManagerService $manager,
       private CheckboxPayloadAdapterService $payloadService,
       private ReceiptFiscalizationLockService $lockService,
       private CheckboxUAClientInterface $clientService,
   ) {
   }

   /**
    * @throws ReceiptCreateException
    * @throws Throwable
    */
   public function createReceipt(ShopPaymentBill $bill): ReceiptModel
   {
       if ($this->lockService->isLocked($bill)) {
           throw new ReceiptCreateException(isClearLock: false, message: 'Bill processing is locked.');
       }
       $billLock = $this->lockService->lockBill($bill); // Prevent duplicate api calls
       $rateLimit = 2; // Rate limit defined by Checkbox

       try {
           $result = $this->handleReceipt($bill);
           sleep($rateLimit);
           $this->lockService->removeLock($billLock);

           return $result;
       } catch (ReceiptCreateException $exception) {
           $this->handleError(bill: $bill, exception: $exception, lock: $billLock);
           sleep($rateLimit);
           throw $exception;
       } catch (Throwable $exception) {
           sleep($rateLimit);
           throw $exception;
       }
   }

   /**
    * @throws ReceiptCreateException
    */
   private function handleReceipt(ShopPaymentBill $bill): ReceiptModel
   {
       $this->manager->registry->getManager()->refresh($bill);
       $this->validate($bill);
       $org = $bill->getPaymentGateway()->getOrganization();
       if (null === $org) {
           throw new ReceiptCreateException(isClearLock: true, message: 'No organization related');
       }
       $jwt = $this->jwtService->resolveCredentials($org);
       if (null === $jwt) {
           throw new ReceiptCreateException(isClearLock: true, message: 'JWT does not exist');
       }
       try {
           $this->shiftService->maybeStartShift($jwt);
       } catch (ShiftStartException $e) {
           throw new ReceiptCreateException(isClearLock: true, message: $e->getMessage(), previous: $e);
       }
       $payload = new ReceiptSellPayload();
       $this->payloadService->fillDelivery($payload, $bill);
       $this->payloadService->fillPayload($bill, $payload);
       $this->validate($bill); // Double validate in case if bad locks

       $result = $this->fetchCreateReceipt(credentials: $jwt, payload: $payload);
       $receipt = new ShopPaymentBillFiscalReceipt(
           bill: $bill, fiscalReceiptId: $result->getId()
       );
       $this->manager->persistAndFlush($receipt);

       return $result;
   }

   private function handleError(ShopPaymentBill $bill, ReceiptCreateException $exception, ShopPaymentBillFiscalizationLock $lock): void
   {
       $errorEntity = new ShopPaymentBillFiscalizationError(bill: $bill, message: $exception->getMessage(), context: $exception->getTraceAsString());
       $this->manager->persist($errorEntity);
       if ($exception->isClearLock()) {
           $this->lockService->removeLock($lock);
       }
       $this->manager->flush();
   }

   /**
    * @throws ReceiptCreateException
    */
   private function validate(ShopPaymentBill $bill): void
   {
       if (null !== $bill->getFiscalReceipt() || null !== $this->manager->registry->getRepository(ShopPaymentBillFiscalReceipt::class)->findOneBy([
           'bill' => $bill,
       ])) {
           throw new ReceiptCreateException(isClearLock: true, message: 'Receipt already exists for bill');
       }

       if (!$bill->getAmountPaid()->isEqual($bill->getAmountToPay())) {
           throw new ReceiptCreateException(isClearLock: true, message: 'Bill is not paid');
       }
       if ($bill->getCreatedAt()->earlierThan(
           Timestamp::fromUnix(ShopPaymentBillFiscalReceipt::FISCALIZE_TIME_FROM)
       )) {
           throw new ReceiptCreateException(isClearLock: true, message: 'Bill is too old to fiscalize');
       }
   }

   /**
    * @throws ReceiptCreateException
    */
   private function fetchCreateReceipt(PaymentGatewayFiscalizationCredentials $credentials, ReceiptSellPayload $payload): ReceiptModel
   {
       $client = $this->clientService->createClient($credentials->getJwtToken());
       try {
           $result = $client->createReceiptApiV1ReceiptsSellPost($payload);
       } catch (CreateReceiptApiV1ReceiptsSellPostUnprocessableEntityException|UnexpectedStatusCodeException $e) {
           $text = 'Receipt api error: '.$e->getMessage();
           if ($e instanceof CreateReceiptApiV1ReceiptsSellPostUnprocessableEntityException) {
               $detail = $e->getHTTPValidationError()->getDetail();
               if ([] !== $detail) {
                   $text .= ': '.$detail[0]->getMsg();
               }
           }
           throw new ReceiptCreateException(isClearLock: true, message: $text, previous: $e);
       } catch (Throwable $exception) {
           throw new ReceiptCreateException(isClearLock: false, message: $exception->getMessage(), previous: $exception);
       }
       assert($result instanceof ReceiptModel);

       return $result;
   }
}

Rector config

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Symfony\Bridge\Symfony\Routing\SymfonyRoutesProvider;
use Rector\Symfony\CodeQuality\Rector\Class_\ControllerMethodInjectionToConstructorRector;
use Rector\Symfony\Contract\Bridge\Symfony\Routing\SymfonyRoutesProviderInterface;

return RectorConfig::configure()
    ->withSymfonyContainerXml(__DIR__.'/var/cache/dev/App_KernelDevDebugContainer.xml')
    ->registerService(SymfonyRoutesProvider::class, SymfonyRoutesProviderInterface::class)
    ->withPaths([__DIR__.'/src', __DIR__.'/tests'])
    ->withSkip([
        Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector::class,

        ControllerMethodInjectionToConstructorRector::class, // System error: "Cannot assign PhpParser\Node\Expr\PropertyFetch to property PhpParser\Node\ClosureUse::$var of type PhpParser\Node\Expr\Variable"
    ])
    ->withPreparedSets(
        deadCode: true,
        codeQuality: true, typeDeclarations: true,
        doctrineCodeQuality: true,
        symfonyCodeQuality: true,
    );

Originally posted by @l-you in #9678

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions