diff --git a/Command/AutoClosingCommand.php b/Command/AutoClosingCommand.php index 3134e761..102feb7d 100644 --- a/Command/AutoClosingCommand.php +++ b/Command/AutoClosingCommand.php @@ -29,40 +29,18 @@ final class AutoClosingCommand extends Command { protected static $defaultName = 'ticket:autoclosing'; - /** - * @var TicketManagerInterface - */ - private $ticketManager; + private bool|string|int|float|\UnitEnum|array|null $locale = 'en'; - /** - * @var UserManagerInterface - */ - private $userManager; + private string $translationDomain = 'HackzillaTicketBundle'; - /** - * @var string - */ - private $locale = 'en'; + private readonly TranslatorInterface $translator; - /** - * @var string - */ - private $translationDomain = 'HackzillaTicketBundle'; - - /** - * @var TranslatorInterface - */ - private $translator; - - public function __construct(TicketManagerInterface $ticketManager, UserManagerInterface $userManager, LocaleAwareInterface $translator, ParameterBagInterface $parameterBag) + public function __construct(private readonly TicketManagerInterface $ticketManager, private readonly UserManagerInterface $userManager, LocaleAwareInterface $translator, ParameterBagInterface $parameterBag) { parent::__construct(); - $this->ticketManager = $ticketManager; - $this->userManager = $userManager; - - if (!is_a($translator, TranslatorInterface::class)) { - throw new \InvalidArgumentException(\get_class($translator).' Must implement TranslatorInterface and LocaleAwareInterface'); + if (!$translator instanceof TranslatorInterface) { + throw new \InvalidArgumentException($translator::class.' Must implement TranslatorInterface and LocaleAwareInterface'); } $this->translator = $translator; @@ -76,7 +54,7 @@ public function __construct(TicketManagerInterface $ticketManager, UserManagerIn /** * {@inheritdoc} */ - protected function configure() + protected function configure(): void { $this ->setDescription('Automatically close resolved tickets still opened') diff --git a/Command/TicketManagerCommand.php b/Command/TicketManagerCommand.php index 8004907e..e5ed1b72 100644 --- a/Command/TicketManagerCommand.php +++ b/Command/TicketManagerCommand.php @@ -26,28 +26,15 @@ final class TicketManagerCommand extends Command { protected static $defaultName = 'ticket:create'; - /** - * @var TicketManagerInterface - */ - private $ticketManager; - - /** - * @var UserManagerInterface - */ - private $userManager; - - public function __construct(TicketManagerInterface $ticketManager, UserManagerInterface $userManager) + public function __construct(private readonly TicketManagerInterface $ticketManager, private readonly UserManagerInterface $userManager) { parent::__construct(); - - $this->ticketManager = $ticketManager; - $this->userManager = $userManager; } /** * {@inheritdoc} */ - protected function configure() + protected function configure(): void { $this ->setDescription('Create a new Ticket') @@ -90,7 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->ticketManager->updateTicket($ticket, $message); $output->writeln( - "Ticket with subject '".$ticket->getSubject()."' has been created with ticketnumber #".$ticket->getId().'' + "Ticket with subject '".$ticket->getSubject()."' has been created with ticketnumber #".$ticket->getId() ); return Command::SUCCESS; diff --git a/Component/TicketFeatures.php b/Component/TicketFeatures.php index 49817318..ed618739 100644 --- a/Component/TicketFeatures.php +++ b/Component/TicketFeatures.php @@ -21,7 +21,7 @@ final class TicketFeatures * @param array $features * @param string $messageClass TicketMessage class */ - private $features = []; + private array $features; /** * @param string $messageClass TicketMessage class diff --git a/Controller/TicketAttachmentController.php b/Controller/TicketAttachmentController.php index 39eda556..6ceb5c1b 100644 --- a/Controller/TicketAttachmentController.php +++ b/Controller/TicketAttachmentController.php @@ -29,30 +29,14 @@ */ final class TicketAttachmentController extends AbstractController { - private DownloadHandler $downloadHandler; - - private TicketManager $ticketManager; - - private TranslatorInterface $translator; - - private UserManagerInterface $userManager; - - public function __construct( - DownloadHandler $downloadHandler, - TicketManager $ticketManager, - TranslatorInterface $translator, - UserManagerInterface $userManager - ) { - $this->downloadHandler = $downloadHandler; - $this->ticketManager = $ticketManager; - $this->translator = $translator; - $this->userManager = $userManager; + public function __construct(private readonly DownloadHandler $downloadHandler, private readonly TicketManager $ticketManager, private readonly TranslatorInterface $translator, private readonly UserManagerInterface $userManager) + { } /** * Download attachment on message. */ - public function downloadAction(int $ticketMessageId): Response + public function download(int $ticketMessageId): Response { $ticketMessage = $this->ticketManager->getMessageById($ticketMessageId); diff --git a/Controller/TicketController.php b/Controller/TicketController.php index 69aade75..7a02d194 100644 --- a/Controller/TicketController.php +++ b/Controller/TicketController.php @@ -26,53 +26,49 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\Translation\TranslatorInterface; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; /** * Ticket controller. */ final class TicketController extends AbstractController { - private EventDispatcherInterface $dispatcher; - - private PaginatorInterface $pagination; - - private TicketManager $ticketManager; - - private TranslatorInterface $translator; - - private UserManagerInterface $userManager; - private array $templates = []; public function __construct( - EventDispatcherInterface $dispatcher, - PaginatorInterface $pagination, + private readonly EventDispatcherInterface $dispatcher, + private readonly PaginatorInterface $pagination, ParameterBagInterface $bag, - TicketManager $ticketManager, - TranslatorInterface $translator, - UserManagerInterface $userManager + private readonly TicketManager $ticketManager, + private readonly TranslatorInterface $translator, + private readonly UserManagerInterface $userManager, + private readonly Environment $twig, + private readonly FormFactoryInterface $formFactory, ) { - $this->dispatcher = $dispatcher; - $this->pagination = $pagination; - $this->ticketManager = $ticketManager; - $this->translator = $translator; - $this->userManager = $userManager; $this->templates = $bag->get('hackzilla_ticket.templates'); } /** * Lists all Ticket entities. * - * @return \Symfony\Component\HttpFoundation\Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ - public function indexAction(Request $request) + public function index(Request $request): Response { - $userManager = $this->userManager; $ticketManager = $this->ticketManager; $ticketState = $request->get('state', $this->translator->trans('STATUS_OPEN', [], 'HackzillaTicketBundle')); @@ -85,11 +81,11 @@ public function indexAction(Request $request) $pagination = $this->pagination->paginate( $query->getQuery(), - (int) ($request->query->get('page', 1))/* page number */, + (int) $request->query->get('page', 1)/* page number */, 10/* limit per page */ ); - return $this->render( + return new Response($this->twig->render( $this->templates['index'], [ 'pagination' => $pagination, @@ -97,20 +93,22 @@ public function indexAction(Request $request) 'ticketPriority' => $ticketPriority, 'translationDomain' => 'HackzillaTicketBundle', ] - ); + )); } /** * Creates a new Ticket entity. * - * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ - public function createAction(Request $request) + public function create(Request $request): RedirectResponse|Response { $ticketManager = $this->ticketManager; $ticket = $ticketManager->createTicket(); - $form = $this->createForm(TicketType::class, $ticket); + $form = $this->formFactory->create(TicketType::class, $ticket); $form->handleRequest($request); if ($form->isValid()) { @@ -123,53 +121,57 @@ public function createAction(Request $request) $ticketManager->updateTicket($ticket, $message); $this->dispatchTicketEvent(TicketEvents::TICKET_CREATE, $ticket); - return $this->redirect($this->generateUrl('hackzilla_ticket_show', ['ticketId' => $ticket->getId()])); + return $this->redirectToRoute('hackzilla_ticket_show', ['ticketId' => $ticket->getId()]); } - return $this->render( + return new Response($this->twig->render( $this->templates['new'], [ 'entity' => $ticket, 'form' => $form->createView(), 'translationDomain' => 'HackzillaTicketBundle', ] - ); + )); } /** * Displays a form to create a new Ticket entity. + * + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ - public function newAction() + public function new(): Response { $ticketManager = $this->ticketManager; $entity = $ticketManager->createTicket(); - $form = $this->createForm(TicketType::class, $entity); + $form = $this->formFactory->create(TicketType::class, $entity); - return $this->render( + return new Response($this->twig->render( $this->templates['new'], [ 'entity' => $entity, 'form' => $form->createView(), 'translationDomain' => 'HackzillaTicketBundle', ] - ); + )); } /** * Finds and displays a TicketInterface entity. * - * @param int $ticketId - * - * @return \Symfony\Component\HttpFoundation\Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ - public function showAction($ticketId) + public function show(int $ticketId): RedirectResponse|Response { $ticketManager = $this->ticketManager; $ticket = $ticketManager->getTicketById($ticketId); - if (!$ticket) { - return $this->redirect($this->generateUrl('hackzilla_ticket')); + if (!$ticket instanceof TicketInterface) { + return $this->redirectToRoute('hackzilla_ticket'); } $currentUser = $this->userManager->getCurrentUser(); @@ -190,22 +192,22 @@ public function showAction($ticketId) $data['delete_form'] = $this->createDeleteForm($ticket->getId())->createView(); } - return $this->render($this->templates['show'], $data); + return new Response($this->twig->render($this->templates['show'], $data)); } /** * Finds and displays a TicketInterface entity. * - * @param int $ticketId - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ - public function replyAction(Request $request, $ticketId) + public function reply(Request $request, int $ticketId): RedirectResponse|Response { $ticketManager = $this->ticketManager; $ticket = $ticketManager->getTicketById($ticketId); - if (!$ticket) { + if (!$ticket instanceof TicketInterface) { throw $this->createNotFoundException($this->translator->trans('ERROR_FIND_TICKET_ENTITY', [], 'HackzillaTicketBundle')); } @@ -225,7 +227,7 @@ public function replyAction(Request $request, $ticketId) $ticketManager->updateTicket($ticket, $message); $this->dispatchTicketEvent(TicketEvents::TICKET_UPDATE, $ticket); - return $this->redirect($this->generateUrl('hackzilla_ticket_show', ['ticketId' => $ticket->getId()])); + return $this->redirectToRoute('hackzilla_ticket_show', ['ticketId' => $ticket->getId()]); } $data = ['ticket' => $ticket, 'form' => $form->createView(), 'translationDomain' => 'HackzillaTicketBundle']; @@ -234,23 +236,19 @@ public function replyAction(Request $request, $ticketId) $data['delete_form'] = $this->createDeleteForm($ticket->getId())->createView(); } - return $this->render($this->templates['show'], $data); + return new Response($this->twig->render($this->templates['show'], $data)); } /** * Deletes a Ticket entity. - * - * @param int $ticketId - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse */ - public function deleteAction(Request $request, $ticketId) + public function delete(Request $request, int $ticketId): RedirectResponse { $userManager = $this->userManager; $user = $userManager->getCurrentUser(); if (!\is_object($user) || !$userManager->hasRole($user, TicketRole::ADMIN)) { - throw new \Symfony\Component\HttpKernel\Exception\HttpException(403); + throw new HttpException(403); } $form = $this->createDeleteForm($ticketId); @@ -262,7 +260,7 @@ public function deleteAction(Request $request, $ticketId) $ticketManager = $this->ticketManager; $ticket = $ticketManager->getTicketById($ticketId); - if (!$ticket) { + if (!$ticket instanceof TicketInterface) { throw $this->createNotFoundException($this->translator->trans('ERROR_FIND_TICKET_ENTITY', [], 'HackzillaTicketBundle')); } @@ -271,7 +269,7 @@ public function deleteAction(Request $request, $ticketId) } } - return $this->redirect($this->generateUrl('hackzilla_ticket')); + return $this->redirectToRoute('hackzilla_ticket'); } private function dispatchTicketEvent(string $ticketEvent, TicketInterface $ticket): void @@ -285,7 +283,7 @@ private function dispatchTicketEvent(string $ticketEvent, TicketInterface $ticke * * @param mixed $id The entity id */ - private function createDeleteForm($id): FormInterface + private function createDeleteForm(mixed $id): FormInterface { return $this->createFormBuilder(['id' => $id]) ->add('id', HiddenType::class) @@ -295,14 +293,12 @@ private function createDeleteForm($id): FormInterface private function createMessageForm(TicketMessageInterface $message): FormInterface { - $form = $this->createForm( + return $this->formFactory->create( TicketMessageType::class, $message, [ 'ticket' => $message->getTicket(), ] ); - - return $form; } } diff --git a/DependencyInjection/HackzillaTicketExtension.php b/DependencyInjection/HackzillaTicketExtension.php index 74798a6d..267fca07 100644 --- a/DependencyInjection/HackzillaTicketExtension.php +++ b/DependencyInjection/HackzillaTicketExtension.php @@ -16,7 +16,7 @@ use Hackzilla\Bundle\TicketBundle\Manager\PermissionManager; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** @@ -28,13 +28,15 @@ final class HackzillaTicketExtension extends Extension { /** * {@inheritdoc} + * + * @throws \Exception */ - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\PhpFileLoader($container, new FileLocator(self::bundleDirectory().'/Resources/config')); + $loader = new PhpFileLoader($container, new FileLocator(self::bundleDirectory().'/Resources/config')); $loader->load('manager.php'); $loader->load('maker.php'); $loader->load('form_types.php'); @@ -51,7 +53,7 @@ public function load(array $configs, ContainerBuilder $container) $permissionClass = $config['permission_class'] ?? PermissionManager::class; if (!class_exists($permissionClass)) { - throw new \Exception(sprintf('Permission manager does not exist: %s', $permissionClass)); + throw new \Exception(\sprintf('Permission manager does not exist: %s', $permissionClass)); } $container->setParameter('hackzilla_ticket.manager.permission.class', $permissionClass); @@ -66,7 +68,7 @@ public function load(array $configs, ContainerBuilder $container) } } - public static function bundleDirectory() + public static function bundleDirectory(): bool|string { return realpath(__DIR__.'/..'); } diff --git a/Event/TicketEvent.php b/Event/TicketEvent.php index 5ef18489..0edaf471 100644 --- a/Event/TicketEvent.php +++ b/Event/TicketEvent.php @@ -18,11 +18,8 @@ final class TicketEvent extends Event { - protected $ticket; - - public function __construct(TicketInterface $ticket) + public function __construct(protected TicketInterface $ticket) { - $this->ticket = $ticket; } public function getTicket(): TicketInterface diff --git a/EventListener/FileSubscriber.php b/EventListener/FileSubscriber.php index db64ffa7..a3b182bb 100644 --- a/EventListener/FileSubscriber.php +++ b/EventListener/FileSubscriber.php @@ -24,10 +24,7 @@ */ final class FileSubscriber implements EventSubscriberInterface { - /** - * @return array - */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ VichEvent\Events::POST_UPLOAD => 'postUpload', @@ -38,7 +35,7 @@ public function postUpload(VichEvent\Event $event): void { /** @var MessageAttachmentInterface $object */ $object = $event->getObject(); - // Ignore any entity lifecycle events not relating to this bundles entities. + // Ignore any entity lifecycle events not relating to these bundles entities. if (!($object instanceof MessageAttachmentInterface)) { return; } diff --git a/Form/DataTransformer/StatusTransformer.php b/Form/DataTransformer/StatusTransformer.php index 66d80eae..51610cc6 100644 --- a/Form/DataTransformer/StatusTransformer.php +++ b/Form/DataTransformer/StatusTransformer.php @@ -19,23 +19,16 @@ final class StatusTransformer implements DataTransformerInterface { - private TicketInterface $ticket; - - public function __construct(TicketInterface $ticket) + public function __construct(private readonly TicketInterface $ticket) { - $this->ticket = $ticket; } /** * Transforms checkbox value into Ticket Message Status Closed. - * - * @param int $number - * - * @return true|null */ - public function transform($number) + public function transform($value): ?bool { - if (TicketMessageInterface::STATUS_CLOSED === $number) { + if (TicketMessageInterface::STATUS_CLOSED === $value) { return true; } @@ -44,14 +37,10 @@ public function transform($number) /** * Transforms Ticket Message Status Closed into checkbox value checked. - * - * @param bool $number - * - * @return int|null */ - public function reverseTransform($number) + public function reverseTransform($value): ?int { - if ($number) { + if ($value) { return TicketMessageInterface::STATUS_CLOSED; } diff --git a/Form/Type/PriorityType.php b/Form/Type/PriorityType.php index e5a19f6c..1a7d0a00 100644 --- a/Form/Type/PriorityType.php +++ b/Form/Type/PriorityType.php @@ -20,7 +20,7 @@ final class PriorityType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $choices = TicketMessageInterface::PRIORITIES; unset($choices[TicketMessageInterface::PRIORITY_INVALID]); diff --git a/Form/Type/StatusType.php b/Form/Type/StatusType.php index 52abf079..7eaff0a0 100644 --- a/Form/Type/StatusType.php +++ b/Form/Type/StatusType.php @@ -20,7 +20,7 @@ final class StatusType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $choices = TicketMessageInterface::STATUSES; unset($choices[TicketMessageInterface::STATUS_INVALID]); diff --git a/Form/Type/TicketMessageType.php b/Form/Type/TicketMessageType.php index ed16e3b4..0e4fbc76 100644 --- a/Form/Type/TicketMessageType.php +++ b/Form/Type/TicketMessageType.php @@ -27,20 +27,11 @@ final class TicketMessageType extends AbstractType { - protected $userManager; - - protected $features; - - protected $messageClass; - - public function __construct(UserManagerInterface $userManager, TicketFeatures $features, string $messageClass) + public function __construct(protected UserManagerInterface $userManager, protected TicketFeatures $features, protected string $messageClass) { - $this->userManager = $userManager; - $this->features = $features; - $this->messageClass = $messageClass; } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add( @@ -105,7 +96,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( [ diff --git a/Form/Type/TicketType.php b/Form/Type/TicketType.php index 7a977b95..149393c8 100644 --- a/Form/Type/TicketType.php +++ b/Form/Type/TicketType.php @@ -21,14 +21,11 @@ final class TicketType extends AbstractType { - protected $ticketClass; - - public function __construct(string $ticketClass) + public function __construct(protected string $ticketClass) { - $this->ticketClass = $ticketClass; } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add( @@ -53,7 +50,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) ; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults( [ diff --git a/HackzillaTicketBundle.php b/HackzillaTicketBundle.php index 4ea7d1a1..c45a8e03 100644 --- a/HackzillaTicketBundle.php +++ b/HackzillaTicketBundle.php @@ -13,8 +13,22 @@ namespace Hackzilla\Bundle\TicketBundle; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\HttpKernel\Bundle\Bundle; class HackzillaTicketBundle extends Bundle { + /** + * @throws \Exception + */ + public function build(ContainerBuilder $container): void + { + parent::build($container); + + // Chargement du fichier de services + $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/Resources/config')); + $loader->load('services.yaml'); + } } diff --git a/Maker/AbstractMaker.php b/Maker/AbstractMaker.php index b7e83548..0408e2f2 100644 --- a/Maker/AbstractMaker.php +++ b/Maker/AbstractMaker.php @@ -14,6 +14,10 @@ namespace Hackzilla\Bundle\TicketBundle\Maker; +use UnitEnum; +use Exception; +use ReflectionClass; +use ReflectionProperty; use Hackzilla\Bundle\TicketBundle\Maker\Util\ClassSourceManipulator; use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\DependencyBuilder; @@ -30,6 +34,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use function is_array; /** * A lot of this class is a duplication of the Symfony Maker component. @@ -37,17 +42,12 @@ */ abstract class AbstractMaker extends \Symfony\Bundle\MakerBundle\Maker\AbstractMaker { - private $fileManager; - private $doctrineHelper; - private $userClass; - private $ticketClass; - private $messageClass; + private readonly bool|string|int|float|UnitEnum|array|null $userClass; + private readonly bool|string|int|float|UnitEnum|array|null $ticketClass; + private readonly bool|string|int|float|UnitEnum|array|null $messageClass; - public function __construct(FileManager $fileManager, DoctrineHelper $doctrineHelper, ParameterBagInterface $bag) + public function __construct(private readonly FileManager $fileManager, private readonly DoctrineHelper $doctrineHelper, ParameterBagInterface $bag) { - $this->fileManager = $fileManager; - $this->doctrineHelper = $doctrineHelper; - $this->userClass = $bag->get('hackzilla_ticket.model.user.class'); $this->ticketClass = $bag->get('hackzilla_ticket.model.ticket.class'); $this->messageClass = $bag->get('hackzilla_ticket.model.message.class'); @@ -74,7 +74,7 @@ public function getMessageClass(): string * By default, all arguments will be asked interactively. If you want * to avoid that, use the $inputConfig->setArgumentAsNonInteractive() method. */ - public function configureCommand(Command $command, InputConfiguration $inputConfig) + public function configureCommand(Command $command, InputConfiguration $inputConfig): void { $command ->addOption('api-resource', 'a', InputOption::VALUE_NONE, 'Mark this class as an API Platform resource (expose a CRUD API for it)') @@ -98,8 +98,9 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma /** * Called after normal code generation: allows you to do anything. + * @throws Exception */ - public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { $entityClass = $this->entityClass(); @@ -114,10 +115,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen throw new RuntimeCommandException('To use Doctrine entity attributes you\'ll need PHP 8, doctrine/orm 2.9, doctrine/doctrine-bundle 2.4 and symfony/framework-bundle 5.2.'); } - if ( - !$this->doesEntityUseAnnotationMapping($entityClassDetails->getFullName()) - && !$this->doesEntityUseAttributeMapping($entityClassDetails->getFullName()) - ) { + if (!$this->doesEntityUseAttributeMapping($entityClassDetails->getFullName())) { throw new RuntimeCommandException(sprintf('Only annotation or attribute mapping is supported by this command, but the %s class uses a different format.', $entityClassDetails->getFullName())); } @@ -131,7 +129,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $fileManagerOperations = []; $fileManagerOperations[$entityPath] = $manipulator; - if (\is_array($newField)) { + if (is_array($newField)) { $annotationOptions = $newField; unset($annotationOptions['fieldName']); $manipulator->addEntityField($newField['fieldName'], $annotationOptions); @@ -165,7 +163,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen // The *other* class will receive the ManyToOne $otherManipulator->addManyToOneRelation($newField->getOwningRelation()); if (!$newField->getMapInverseRelation()) { - throw new \Exception('Somehow a OneToMany relationship is being created, but the inverse side will not be mapped?'); + throw new Exception('Somehow a OneToMany relationship is being created, but the inverse side will not be mapped?'); } $manipulator->addOneToManyRelation($newField->getInverseRelation()); } @@ -186,7 +184,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen break; default: - throw new \Exception('Invalid relation type'); + throw new Exception('Invalid relation type'); } // save the inverse side if it's being mapped @@ -195,7 +193,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen } $currentFields[] = $newFieldName; } else { - throw new \Exception('Invalid value'); + throw new Exception('Invalid value'); } foreach ($fileManagerOperations as $path => $manipulatorOrMessage) { @@ -218,20 +216,18 @@ abstract protected function traits(): array; abstract protected function interfaces(): array; + /** + * @throws Exception + */ private function createClassManipulator(string $path, ConsoleStyle $io, bool $overwrite, string $className, bool $originalClass = true): ClassSourceManipulator { $useAttributes = $this->doctrineHelper->doesClassUsesAttributes($className) && $this->doctrineHelper->isDoctrineSupportingAttributes(); - $useAnnotations = false ; - if (method_exists($this->doctrineHelper, 'isClassAnnotated')) { - $useAnnotations = $this->doctrineHelper->isClassAnnotated($className) ||!$useAttributes; + if (!$useAttributes) { + throw new Exception('No support for either Annotations or Attributes'); } - if (!$useAnnotations && !$useAttributes) { - throw new \Exception('No support for either Annotations or Attributes'); - } - - $manipulator = new ClassSourceManipulator($this->fileManager->getFileContents($path), $overwrite, $useAnnotations, $useAttributes); + $manipulator = new ClassSourceManipulator($this->fileManager->getFileContents($path), $overwrite, false, true); if ($originalClass) { foreach ($this->traits() as $trait) { @@ -262,31 +258,9 @@ private function getPropertyNames(string $class): array return []; } - $reflClass = new \ReflectionClass($class); - - return array_map(static function (\ReflectionProperty $prop) { - return $prop->getName(); - }, $reflClass->getProperties()); - } - - private function doesEntityUseAnnotationMapping(string $className): bool - { - if (!class_exists($className)) { - $otherClassMetadatas = $this->doctrineHelper->getMetadata(Str::getNamespace($className).'\\', true); - - // if we have no metadata, we should assume this is the first class being mapped - if (empty($otherClassMetadatas)) { - return false; - } - - $className = reset($otherClassMetadatas)->getName(); - } - - if (!method_exists($this->doctrineHelper, 'isClassAnnotated')) { - return false; - } + $reflClass = new ReflectionClass($class); - return $this->doctrineHelper->isClassAnnotated($className); + return array_map(static fn(ReflectionProperty $prop): string => $prop->getName(), $reflClass->getProperties()); } private function doesEntityUseAttributeMapping(string $className): bool diff --git a/Maker/MessageMaker.php b/Maker/MessageMaker.php index 16b005bb..b369f115 100644 --- a/Maker/MessageMaker.php +++ b/Maker/MessageMaker.php @@ -13,6 +13,7 @@ namespace Hackzilla\Bundle\TicketBundle\Maker; +use Exception; use Hackzilla\Bundle\TicketBundle\Model\MessageAttachmentInterface; use Hackzilla\Bundle\TicketBundle\Model\MessageAttachmentTrait; use Hackzilla\Bundle\TicketBundle\Model\TicketMessageInterface; @@ -28,7 +29,7 @@ final class MessageMaker extends AbstractMaker { - private $hasAttachment = false; + private bool $hasAttachment = false; public static function getCommandName(): string { @@ -40,20 +41,23 @@ public static function getCommandDescription(): string return MakeEntity::getCommandDescription(); } - public function configureCommand(Command $command, InputConfiguration $inputConfig) + public function configureCommand(Command $command, InputConfiguration $inputConfig): void { $command->addOption('attachment', null, InputOption::VALUE_NONE, 'Overwrite any existing getter/setter methods'); parent::configureCommand($command, $inputConfig); } - public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { $this->hasAttachment = $input->getOption('attachment'); - return parent::generate($input, $io, $generator); + parent::generate($input, $io, $generator); } + /** + * @throws Exception + */ protected function fields(): array { $userRelation = new EntityRelation(EntityRelation::MANY_TO_ONE, $this->getMessageClass(), $this->getUserClass()); diff --git a/Maker/TicketMaker.php b/Maker/TicketMaker.php index d6f8aa33..7c7d0924 100644 --- a/Maker/TicketMaker.php +++ b/Maker/TicketMaker.php @@ -13,6 +13,7 @@ namespace Hackzilla\Bundle\TicketBundle\Maker; +use Exception; use Hackzilla\Bundle\TicketBundle\Model\TicketInterface; use Hackzilla\Bundle\TicketBundle\Model\TicketTrait; use Symfony\Bundle\MakerBundle\Doctrine\EntityRelation; @@ -30,6 +31,9 @@ public static function getCommandDescription(): string return MakeEntity::getCommandDescription(); } + /** + * @throws Exception + */ protected function fields(): array { $lastUserRelation = new EntityRelation(EntityRelation::MANY_TO_ONE, $this->getTicketClass(), $this->getUserClass()); diff --git a/Maker/Util/ClassSourceManipulator.php b/Maker/Util/ClassSourceManipulator.php index 455a6ae3..f393d5b4 100644 --- a/Maker/Util/ClassSourceManipulator.php +++ b/Maker/Util/ClassSourceManipulator.php @@ -14,19 +14,57 @@ namespace Hackzilla\Bundle\TicketBundle\Maker\Util; +use PhpParser\Lexer\Emulative; +use PhpParser\Node; +use PhpParser\Parser\Php7; +use PhpParser\Node\Stmt\Expression; +use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Expr\New_; +use PhpParser\Node\Name; +use PhpParser\Node\Expr\Array_; +use PhpParser\Node\Stmt\TraitUse; +use LogicException; +use PhpParser\Builder\Method; +use PhpParser\Node\NullableType; +use PhpParser\Builder\Property; +use PhpParser\Node\Stmt\Use_; +use PhpParser\Node\Stmt\Class_; +use Exception; +use PhpParser\Node\Stmt; +use PHPStan\Php\PhpVersion; +use ReflectionClass; +use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Identifier; +use ReflectionException; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\NodeVisitor\CloningVisitor; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\NodeVisitor\FirstFindingVisitor; +use PhpParser\NodeVisitor\FindingVisitor; +use DateTimeInterface; +use DateTimeImmutable; +use DateInterval; +use PhpParser\Node\Stmt\ClassConst; +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\DNumber; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\ArrayItem; +use PhpParser\Node\Attribute; +use PhpParser\Node\Arg; +use ReflectionParameter; use Doctrine\Common\Collections\ArrayCollection; use PhpParser\Builder; use PhpParser\BuilderHelpers; use PhpParser\Comment\Doc; -use PhpParser\Lexer; -use PhpParser\Node; use PhpParser\NodeTraverser; -use PhpParser\NodeVisitor; -use PhpParser\Parser; use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\Doctrine\BaseCollectionRelation; use Symfony\Bundle\MakerBundle\Doctrine\BaseRelation; -use Symfony\Bundle\MakerBundle\Doctrine\BaseSingleRelation; use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToMany; use Symfony\Bundle\MakerBundle\Doctrine\RelationManyToOne; use Symfony\Bundle\MakerBundle\Doctrine\RelationOneToMany; @@ -34,6 +72,12 @@ use Symfony\Bundle\MakerBundle\Str; use Symfony\Bundle\MakerBundle\Util\ClassNameValue; use Symfony\Bundle\MakerBundle\Util\PrettyPrinter; +use function array_key_exists; +use function count; +use function gettype; +use function is_array; +use function is_bool; +use function is_int; /** * @internal @@ -48,36 +92,33 @@ final class ClassSourceManipulator private const CONTEXT_OUTSIDE_CLASS = 'outside_class'; private const CONTEXT_CLASS = 'class'; private const CONTEXT_CLASS_METHOD = 'class_method'; + private readonly Php7 $parser; + private readonly Emulative $lexer; + private readonly PrettyPrinter $printer; + private ?ConsoleStyle $io = null; - private $overwrite; - private $useAnnotations; - private $useAttributesForDoctrineMapping; - private $parser; - private $lexer; - private $printer; - /** @var ConsoleStyle|null */ - private $io; + private string $sourceCode; + private ?array $oldStmts; + private array $oldTokens; + private array $newStmts; - private $sourceCode; - private $oldStmts; - private $oldTokens; - private $newStmts; + private array $pendingComments = []; - private $pendingComments = []; - - public function __construct(string $sourceCode, bool $overwrite = false, bool $useAnnotations = true, bool $useAttributesForDoctrineMapping = false) + public function __construct(string $sourceCode, private readonly bool $overwrite = false, private readonly bool $useAnnotations = true, private readonly bool $useAttributesForDoctrineMapping = false) { - $this->overwrite = $overwrite; - $this->useAnnotations = $useAnnotations; - $this->useAttributesForDoctrineMapping = $useAttributesForDoctrineMapping; - $this->lexer = new Lexer\Emulative([ + $this->lexer = new Emulative(null); + + /* + $this->lexer = new Emulative([ 'usedAttributes' => [ 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', ], ]); - $this->parser = new Parser\Php7($this->lexer); + */ + + $this->parser = new Php7($this->lexer); $this->printer = new PrettyPrinter(); $this->setSourceCode($sourceCode); @@ -93,34 +134,38 @@ public function getSourceCode(): string return $this->sourceCode; } - public function addCreatedToContructor() + /** + * @throws Exception + */ + public function addCreatedToContructor(): void { $addCreatedAt = true; - if ($this->getConstructorNode()) { + if ($this->getConstructorNode() instanceof ClassMethod) { // We print the constructor to a string, then // look for "$this->propertyName = " $constructorString = $this->printer->prettyPrint([$this->getConstructorNode()]); - if (false !== strpos($constructorString, sprintf('$this->%s = ', 'createdAt'))) { + if (str_contains($constructorString, sprintf('$this->%s = ', 'createdAt'))) { $addCreatedAt = false; } } if ($addCreatedAt) { $this->addStatementToConstructor( - new Node\Stmt\Expression(new Node\Expr\Assign( - new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), 'createdAt'), - new Node\Expr\New_(new Node\Name('\DateTime')) + new Expression(new Assign( + new PropertyFetch(new Variable('this'), 'createdAt'), + new New_(new Name('\DateTime')) )) ); } } + /** + * @throws Exception + */ public function addEntityField(string $propertyName, array $columnOptions, array $comments = []): void { $typeHint = $this->getEntityTypeHint($columnOptions['type']); - $nullable = $columnOptions['nullable'] ?? false; - $isId = (bool) ($columnOptions['id'] ?? false); $attributes = []; if ($this->useAttributesForDoctrineMapping) { @@ -131,57 +176,75 @@ public function addEntityField(string $propertyName, array $columnOptions, array $defaultValue = null; if ('array' === $typeHint) { - $defaultValue = new Node\Expr\Array_([], ['kind' => Node\Expr\Array_::KIND_SHORT]); + $defaultValue = new Array_([], ['kind' => Array_::KIND_SHORT]); } $this->addProperty($propertyName, $comments, $defaultValue, $attributes); } + /** + * @throws Exception + */ public function addManyToOneRelation(RelationManyToOne $manyToOne): void { $this->addSingularRelation($manyToOne); } + /** + * @throws Exception + */ public function addOneToOneRelation(RelationOneToOne $oneToOne): void { $this->addSingularRelation($oneToOne); } + /** + * @throws Exception + */ public function addOneToManyRelation(RelationOneToMany $oneToMany): void { $this->addCollectionRelation($oneToMany); } + /** + * @throws Exception + */ public function addManyToManyRelation(RelationManyToMany $manyToMany): void { $this->addCollectionRelation($manyToMany); } + /** + * @throws Exception + */ public function addInterface(string $interfaceName): void { $this->addUseStatementIfNecessary($interfaceName); - foreach ($this->getClassNode()->implements as $node) { - if (implode('\\', $node->getAttribute('resolvedName')->parts) === $interfaceName) { - return; + if (!empty($this->getClassNode()->implements)) { + foreach ($this->getClassNode()->implements as $node) { + if (implode('\\', $node->getAttribute('resolvedName')->parts) === $interfaceName) { + return; + } } + + $this->getClassNode()->implements[] = new Name(Str::getShortClassName($interfaceName)); } - $this->getClassNode()->implements[] = new Node\Name(Str::getShortClassName($interfaceName)); $this->updateSourceCodeFromNewStmts(); } /** * @param string $trait the fully-qualified trait name + * + * @throws Exception */ public function addTrait(string $trait): void { $importedClassName = $this->addUseStatementIfNecessary($trait); - /** @var Node\Stmt\TraitUse[] $traitNodes */ - $traitNodes = $this->findAllNodes(static function ($node) { - return $node instanceof Node\Stmt\TraitUse; - }); + /** @var TraitUse[] $traitNodes */ + $traitNodes = $this->findAllNodes(static fn($node): bool => $node instanceof TraitUse); foreach ($traitNodes as $node) { if ($node->traits[0]->toString() === $importedClassName) { @@ -189,34 +252,39 @@ public function addTrait(string $trait): void } } - $traitNodes[] = new Node\Stmt\TraitUse([new Node\Name($importedClassName)]); + $traitNodes[] = new TraitUse([new Name($importedClassName)]); $classNode = $this->getClassNode(); - if (!empty($classNode->stmts) && 1 === \count($traitNodes)) { + if (!empty($classNode->stmts) && 1 === count($traitNodes)) { $traitNodes[] = $this->createBlankLineNode(self::CONTEXT_CLASS); } // avoid all the use traits in class for unshift all the new UseTrait // in the right order. - foreach ($classNode->stmts as $key => $node) { - if ($node instanceof Node\Stmt\TraitUse) { - unset($classNode->stmts[$key]); + if (!empty($classNode->stmts)) { + foreach ($classNode->stmts as $key => $node) { + if ($node instanceof TraitUse) { + unset($classNode->stmts[$key]); + } } - } - array_unshift($classNode->stmts, ...$traitNodes); + array_unshift($classNode->stmts, ...$traitNodes); + } $this->updateSourceCodeFromNewStmts(); } /** * @param Node[] $params + * @param string $methodBody + * + * @throws Exception */ public function addConstructor(array $params, string $methodBody): void { - if (null !== $this->getConstructorNode()) { - throw new \LogicException('Constructor already exists.'); + if ($this->getConstructorNode() instanceof ClassMethod) { + throw new LogicException('Constructor already exists.'); } $methodBuilder = $this->createMethodBuilder('__construct', null, false); @@ -230,9 +298,13 @@ public function addConstructor(array $params, string $methodBody): void } /** + * @param Method $methodBuilder * @param Node[] $params + * @param string|null $methodBody + * + * @throws Exception */ - public function addMethodBuilder(Builder\Method $methodBuilder, array $params = [], ?string $methodBody = null): void + public function addMethodBuilder(Method $methodBuilder, array $params = [], ?string $methodBody = null): void { $this->addMethodParams($methodBuilder, $params); @@ -243,15 +315,18 @@ public function addMethodBuilder(Builder\Method $methodBuilder, array $params = $this->addMethod($methodBuilder->getNode()); } - public function addMethodBody(Builder\Method $methodBuilder, string $methodBody): void + public function addMethodBody(Method $methodBuilder, string $methodBody): void { $nodes = $this->parser->parse($methodBody); $methodBuilder->addStmts($nodes); } - public function createMethodBuilder(string $methodName, $returnType, bool $isReturnTypeNullable, array $commentLines = []): Builder\Method + /** + * @throws Exception + */ + public function createMethodBuilder(string $methodName, $returnType, bool $isReturnTypeNullable, array $commentLines = []): Method { - $methodNodeBuilder = (new Builder\Method($methodName)) + $methodNodeBuilder = (new Method($methodName)) ->makePublic() ; @@ -259,26 +334,35 @@ public function createMethodBuilder(string $methodName, $returnType, bool $isRet if (class_exists($returnType) || interface_exists($returnType)) { $returnType = $this->addUseStatementIfNecessary($returnType); } - $methodNodeBuilder->setReturnType($isReturnTypeNullable ? new Node\NullableType($returnType) : $returnType); + $methodNodeBuilder->setReturnType($isReturnTypeNullable ? new NullableType($returnType) : $returnType); } - if ($commentLines) { + if ($commentLines !== []) { $methodNodeBuilder->setDocComment($this->createDocBlock($commentLines)); } return $methodNodeBuilder; } - public function createMethodLevelCommentNode(string $comment) + /** + * @throws Exception + */ + public function createMethodLevelCommentNode(string $comment): Stmt { return $this->createSingleLineCommentNode($comment, self::CONTEXT_CLASS_METHOD); } - public function createMethodLevelBlankLine() + /** + * @throws Exception + */ + public function createMethodLevelBlankLine(): Use_|Node|Stmt\Property|Variable { return $this->createBlankLineNode(self::CONTEXT_CLASS_METHOD); } + /** + * @throws Exception + */ public function addProperty(string $name, array $annotationLines = [], $defaultValue = null, array $attributes = []): void { if ($this->propertyExists($name)) { @@ -286,7 +370,7 @@ public function addProperty(string $name, array $annotationLines = [], $defaultV return; } - $newPropertyBuilder = (new Builder\Property($name))->makePrivate(); + $newPropertyBuilder = (new Property($name))->makePrivate(); if ($annotationLines && $this->useAnnotations) { $newPropertyBuilder->setDocComment($this->createDocBlock($annotationLines)); @@ -304,15 +388,18 @@ public function addProperty(string $name, array $annotationLines = [], $defaultV $this->addNodeAfterProperties($newPropertyNode); } + /** + * @throws Exception + */ public function addAnnotationToClass(string $annotationClass, array $options): void { $annotationClassAlias = $this->addUseStatementIfNecessary($annotationClass); $docComment = $this->getClassNode()->getDocComment(); $docLines = $docComment ? explode("\n", $docComment->getText()) : []; - if (0 === \count($docLines)) { + if ([] === $docLines) { $docLines = ['/**', ' */']; - } elseif (1 === \count($docLines)) { + } elseif (1 === count($docLines)) { // /** inline doc syntax */ // imperfect way to try to find where to split the lines $endOfOpening = strpos($docLines[0], '* '); @@ -322,7 +409,7 @@ public function addAnnotationToClass(string $annotationClass, array $options): v substr($docLines[0], 0, $endOfOpening + 1), ]; - if ($extraComments) { + if ($extraComments !== '' && $extraComments !== '0') { $newDocLines[] = ' * '.$extraComments; } @@ -332,7 +419,7 @@ public function addAnnotationToClass(string $annotationClass, array $options): v array_splice( $docLines, - \count($docLines) - 1, + count($docLines) - 1, 0, ' * '.$this->buildAnnotationLine('@'.$annotationClassAlias, $options) ); @@ -343,7 +430,10 @@ public function addAnnotationToClass(string $annotationClass, array $options): v } /** + * @param string $class + * * @return string The alias to use when referencing this class + * @throws Exception */ public function addUseStatementIfNecessary(string $class): string { @@ -357,64 +447,66 @@ public function addUseStatementIfNecessary(string $class): string $targetIndex = null; $addLineBreak = false; $lastUseStmtIndex = null; - foreach ($namespaceNode->stmts as $index => $stmt) { - if ($stmt instanceof Node\Stmt\Use_) { - // I believe this is an array to account for use statements with {} - foreach ($stmt->uses as $use) { - $alias = $use->alias ? $use->alias->name : $use->name->getLast(); - - // the use statement already exists? Don't add it again - if ($class === (string) $use->name) { - return $alias; + if (!empty($namespaceNode->stmts)) { + foreach ($namespaceNode->stmts as $index => $stmt) { + if ($stmt instanceof Use_) { + // I believe this is an array to account for use statements with {} + foreach ($stmt->uses as $use) { + $alias = $use->alias ? $use->alias->name : $use->name->getLast(); + + // the use statement already exists? Don't add it again + if ($class === (string)$use->name) { + return $alias; + } + + if ($alias === $shortClassName) { + // we have a conflicting alias! + // to be safe, use the fully-qualified class name + // everywhere and do not add another use statement + return '\\'.$class; + } } - if ($alias === $shortClassName) { - // we have a conflicting alias! - // to be safe, use the fully-qualified class name - // everywhere and do not add another use statement - return '\\'.$class; + // if $class is alphabetically before this use statement, place it before + // only set $targetIndex the first time you find it + if (null === $targetIndex && Str::areClassesAlphabetical($class, (string)$stmt->uses[0]->name)) { + $targetIndex = $index; } - } - // if $class is alphabetically before this use statement, place it before - // only set $targetIndex the first time you find it - if (null === $targetIndex && Str::areClassesAlphabetical($class, (string) $stmt->uses[0]->name)) { - $targetIndex = $index; - } + $lastUseStmtIndex = $index; + } elseif ($stmt instanceof Class_) { + if (null !== $targetIndex) { + // we already found where to place the use statement - $lastUseStmtIndex = $index; - } elseif ($stmt instanceof Node\Stmt\Class_) { - if (null !== $targetIndex) { - // we already found where to place the use statement + break; + } - break; - } + // we hit the class! If there were any use statements, + // then put this at the bottom of the use statement list + if (null !== $lastUseStmtIndex) { + $targetIndex = $lastUseStmtIndex + 1; + } else { + $targetIndex = $index; + $addLineBreak = true; + } - // we hit the class! If there were any use statements, - // then put this at the bottom of the use statement list - if (null !== $lastUseStmtIndex) { - $targetIndex = $lastUseStmtIndex + 1; - } else { - $targetIndex = $index; - $addLineBreak = true; + break; } + } - break; + if (null === $targetIndex) { + throw new Exception('Could not find a class!'); } - } - if (null === $targetIndex) { - throw new \Exception('Could not find a class!'); + $newUseNode = (new Builder\Use_($class, Use_::TYPE_NORMAL))->getNode(); + array_splice( + $namespaceNode->stmts, + $targetIndex, + 0, + $addLineBreak ? [$newUseNode, $this->createBlankLineNode(self::CONTEXT_OUTSIDE_CLASS)] : [$newUseNode] + ); } - $newUseNode = (new Builder\Use_($class, Node\Stmt\Use_::TYPE_NORMAL))->getNode(); - array_splice( - $namespaceNode->stmts, - $targetIndex, - 0, - $addLineBreak ? [$newUseNode, $this->createBlankLineNode(self::CONTEXT_OUTSIDE_CLASS)] : [$newUseNode] - ); - $this->updateSourceCodeFromNewStmts(); return $shortClassName; @@ -422,21 +514,19 @@ public function addUseStatementIfNecessary(string $class): string /** * @param string $annotationClass The annotation: e.g. "@ORM\Column" - * @param array $options Key-value pair of options for the annotation + * @param array $options Key-value pair of options for the annotation + * + * @throws Exception */ private function buildAnnotationLine(string $annotationClass, array $options): string { - $formattedOptions = array_map(function ($option, $value) { - if (\is_array($value)) { + $formattedOptions = array_map(function ($option, $value): string { + if (is_array($value)) { if (!isset($value[0])) { - return sprintf('%s={%s}', $option, implode(', ', array_map(function ($val, $key) { - return sprintf('"%s" = %s', $key, $this->quoteAnnotationValue($val)); - }, $value, array_keys($value)))); + return sprintf('%s={%s}', $option, implode(', ', array_map(fn($val, $key): string => sprintf('"%s" = %s', $key, $this->quoteAnnotationValue($val)), $value, array_keys($value)))); } - return sprintf('%s={%s}', $option, implode(', ', array_map(function ($val) { - return $this->quoteAnnotationValue($val); - }, $value))); + return sprintf('%s={%s}', $option, implode(', ', array_map(fn($val): int|string => $this->quoteAnnotationValue($val), $value))); } return sprintf('%s=%s', $option, $this->quoteAnnotationValue($value)); @@ -445,9 +535,12 @@ private function buildAnnotationLine(string $annotationClass, array $options): s return sprintf('%s(%s)', $annotationClass, implode(', ', $formattedOptions)); } - private function quoteAnnotationValue($value) + /** + * @throws Exception + */ + private function quoteAnnotationValue($value): int|string { - if (\is_bool($value)) { + if (is_bool($value)) { return $value ? 'true' : 'false'; } @@ -455,7 +548,7 @@ private function quoteAnnotationValue($value) return 'null'; } - if (\is_int($value) || '0' === $value) { + if (is_int($value) || '0' === $value) { return $value; } @@ -463,17 +556,20 @@ private function quoteAnnotationValue($value) return sprintf('%s::class', $value->getShortName()); } - if (\is_array($value)) { - throw new \Exception('Invalid value: loop before quoting.'); + if (is_array($value)) { + throw new Exception('Invalid value: loop before quoting.'); } return sprintf('"%s"', $value); } + /** + * @throws Exception + */ private function addSingularRelation(BaseRelation $relation): void { $typeHint = $this->addUseStatementIfNecessary($relation->getTargetClassName()); - if ($relation->getTargetClassName() == $this->getThisFullClassName()) { + if ($relation->getTargetClassName() === $this->getThisFullClassName()) { $typeHint = 'self'; } @@ -515,6 +611,9 @@ private function addSingularRelation(BaseRelation $relation): void $this->addProperty($relation->getPropertyName(), $annotations, null, $attributes); } + /** + * @throws Exception + */ private function addCollectionRelation(BaseCollectionRelation $relation): void { $typeHint = $relation->isSelfReferencing() ? 'self' : $this->addUseStatementIfNecessary($relation->getTargetClassName()); @@ -560,43 +659,47 @@ private function addCollectionRelation(BaseCollectionRelation $relation): void // logic to avoid re-adding the same ArrayCollection line $addArrayCollection = true; - if ($this->getConstructorNode()) { + if ($this->getConstructorNode() instanceof ClassMethod) { // We print the constructor to a string, then // look for "$this->propertyName = " $constructorString = $this->printer->prettyPrint([$this->getConstructorNode()]); - if (false !== strpos($constructorString, sprintf('$this->%s = ', $relation->getPropertyName()))) { + if (str_contains($constructorString, sprintf('$this->%s = ', $relation->getPropertyName()))) { $addArrayCollection = false; } } if ($addArrayCollection) { $this->addStatementToConstructor( - new Node\Stmt\Expression(new Node\Expr\Assign( - new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), $relation->getPropertyName()), - new Node\Expr\New_(new Node\Name($arrayCollectionTypeHint)) + new Expression(new Assign( + new PropertyFetch(new Variable('this'), $relation->getPropertyName()), + new New_(new Name($arrayCollectionTypeHint)) )) ); } - $argName = Str::pluralCamelCaseToSingular($relation->getPropertyName()); + Str::pluralCamelCaseToSingular($relation->getPropertyName()); } - private function addStatementToConstructor(Node\Stmt $stmt): void + /** + * @throws Exception + */ + private function addStatementToConstructor(Stmt $stmt): void { - if (!$this->getConstructorNode()) { - $constructorNode = (new Builder\Method('__construct'))->makePublic()->getNode(); + if (!$this->getConstructorNode() instanceof ClassMethod) { + $constructorNode = (new Method('__construct'))->makePublic()->getNode(); // add call to parent::__construct() if there is a need to try { - $ref = new \ReflectionClass($this->getThisFullClassName()); + $ref = new ReflectionClass($this->getThisFullClassName()); if ($ref->getParentClass() && $ref->getParentClass()->getConstructor()) { - $constructorNode->stmts[] = new Node\Stmt\Expression( - new Node\Expr\StaticCall(new Node\Name('parent'), new Node\Identifier('__construct')) + $constructorNode->stmts[] = new Expression( + new StaticCall(new Name('parent'), new Identifier('__construct')) ); } - } catch (\ReflectionException $e) { + } catch (ReflectionException) { + // nothing to do here } $this->addNodeAfterProperties($constructorNode); @@ -608,13 +711,15 @@ private function addStatementToConstructor(Node\Stmt $stmt): void } /** - * @throws \Exception + * @throws Exception */ - private function getConstructorNode(): ?Node\Stmt\ClassMethod + private function getConstructorNode(): ?ClassMethod { - foreach ($this->getClassNode()->stmts as $classNode) { - if ($classNode instanceof Node\Stmt\ClassMethod && '__construct' == $classNode->name) { - return $classNode; + if (!empty($this->getClassNode()->stmts)) { + foreach ($this->getClassNode()->stmts as $classNode) { + if ($classNode instanceof ClassMethod && '__construct' == $classNode->name) { + return $classNode; + } } } @@ -630,15 +735,15 @@ private function updateSourceCodeFromNewStmts(): void ); // replace the 3 "fake" items that may be in the code (allowing for different indentation) - $newCode = preg_replace('/(\ |\t)*private\ \$__EXTRA__LINE;/', '', $newCode); - $newCode = preg_replace('/use __EXTRA__LINE;/', '', $newCode); - $newCode = preg_replace('/(\ |\t)*\$__EXTRA__LINE;/', '', $newCode); + $newCode = preg_replace('/\s*private \$__EXTRA__LINE;/', '', $newCode); + $newCode = str_replace('use __EXTRA__LINE;', '', (string) $newCode); + $newCode = preg_replace('/\s*\$__EXTRA__LINE;/', '', (string) $newCode); // process comment lines foreach ($this->pendingComments as $i => $comment) { // sanity check $placeholder = sprintf('$__COMMENT__VAR_%d;', $i); - if (false === strpos($newCode, $placeholder)) { + if (!str_contains((string) $newCode, $placeholder)) { // this can happen if a comment is createSingleLineCommentNode() // is called, but then that generated code is ultimately not added continue; @@ -655,48 +760,50 @@ private function setSourceCode(string $sourceCode): void { $this->sourceCode = $sourceCode; $this->oldStmts = $this->parser->parse($sourceCode); - $this->oldTokens = $this->lexer->getTokens(); + if (method_exists($this->lexer, 'getTokens')) { + $this->oldTokens = $this->lexer->getTokens(); + } $traverser = new NodeTraverser(); - $traverser->addVisitor(new NodeVisitor\CloningVisitor()); - $traverser->addVisitor(new NodeVisitor\NameResolver(null, [ + $traverser->addVisitor(new CloningVisitor()); + $traverser->addVisitor(new NameResolver(null, [ 'replaceNodes' => false, ])); $this->newStmts = $traverser->traverse($this->oldStmts); } - private function getClassNode(): Node\Stmt\Class_ + /** + * @throws Exception + */ + private function getClassNode(): Node { - $node = $this->findFirstNode(static function ($node) { - return $node instanceof Node\Stmt\Class_; - }); + $node = $this->findFirstNode(static fn($node): bool => $node instanceof Class_); - if (!$node) { - throw new \Exception('Could not find class node'); + if (!$node instanceof Node) { + throw new Exception('Could not find class node'); } - /* @phpstan-ignore-next-line */ return $node; } - private function getNamespaceNode(): Node\Stmt\Namespace_ + /** + * @throws Exception + */ + private function getNamespaceNode(): Node { - $node = $this->findFirstNode(static function ($node) { - return $node instanceof Node\Stmt\Namespace_; - }); + $node = $this->findFirstNode(static fn($node): bool => $node instanceof Namespace_); - if (!$node) { - throw new \Exception('Could not find namespace node'); + if (!$node instanceof Node) { + throw new Exception('Could not find namespace node'); } - /* @phpstan-ignore-next-line */ return $node; } private function findFirstNode(callable $filterCallback): ?Node { $traverser = new NodeTraverser(); - $visitor = new NodeVisitor\FirstFindingVisitor($filterCallback); + $visitor = new FirstFindingVisitor($filterCallback); $traverser->addVisitor($visitor); $traverser->traverse($this->newStmts); @@ -706,7 +813,7 @@ private function findFirstNode(callable $filterCallback): ?Node private function findLastNode(callable $filterCallback, array $ast): ?Node { $traverser = new NodeTraverser(); - $visitor = new NodeVisitor\FindingVisitor($filterCallback); + $visitor = new FindingVisitor($filterCallback); $traverser->addVisitor($visitor); $traverser->traverse($ast); @@ -722,46 +829,46 @@ private function findLastNode(callable $filterCallback, array $ast): ?Node private function findAllNodes(callable $filterCallback): array { $traverser = new NodeTraverser(); - $visitor = new NodeVisitor\FindingVisitor($filterCallback); + $visitor = new FindingVisitor($filterCallback); $traverser->addVisitor($visitor); $traverser->traverse($this->newStmts); return $visitor->getFoundNodes(); } - private function createBlankLineNode(string $context) + /** + * @throws Exception + */ + private function createBlankLineNode(string $context): Use_|Node|Stmt\Property|Variable { - switch ($context) { - case self::CONTEXT_OUTSIDE_CLASS: - return (new Builder\Use_('__EXTRA__LINE', Node\Stmt\Use_::TYPE_NORMAL)) - ->getNode() - ; - case self::CONTEXT_CLASS: - return (new Builder\Property('__EXTRA__LINE')) - ->makePrivate() - ->getNode() - ; - case self::CONTEXT_CLASS_METHOD: - return new Node\Expr\Variable('__EXTRA__LINE'); - default: - throw new \Exception('Unknown context: '.$context); - } + return match ($context) { + self::CONTEXT_OUTSIDE_CLASS => (new Builder\Use_('__EXTRA__LINE', Use_::TYPE_NORMAL)) + ->getNode(), + self::CONTEXT_CLASS => (new Property('__EXTRA__LINE')) + ->makePrivate() + ->getNode(), + self::CONTEXT_CLASS_METHOD => new Variable('__EXTRA__LINE'), + default => throw new Exception('Unknown context: '.$context), + }; } - private function createSingleLineCommentNode(string $comment, string $context): Node\Stmt + /** + * @throws Exception + */ + private function createSingleLineCommentNode(string $comment, string $context): Stmt { $this->pendingComments[] = $comment; switch ($context) { case self::CONTEXT_OUTSIDE_CLASS: // just not needed yet - throw new \Exception('not supported'); + throw new Exception('not supported'); case self::CONTEXT_CLASS: // just not needed yet - throw new \Exception('not supported'); + throw new Exception('not supported'); case self::CONTEXT_CLASS_METHOD: - return BuilderHelpers::normalizeStmt(new Node\Expr\Variable(sprintf('__COMMENT__VAR_%d', \count($this->pendingComments) - 1))); + return BuilderHelpers::normalizeStmt(new Variable(sprintf('__COMMENT__VAR_%d', count($this->pendingComments) - 1))); default: - throw new \Exception('Unknown context: '.$context); + throw new Exception('Unknown context: '.$context); } } @@ -770,21 +877,23 @@ private function createDocBlock(array $commentLines): string $docBlock = "/**\n"; foreach ($commentLines as $commentLine) { if ($commentLine) { - $docBlock .= " * ${commentLine}\n"; + $docBlock .= " * $commentLine\n"; } else { // avoid the empty, extra space on blank lines $docBlock .= " *\n"; } } - $docBlock .= "\n */"; - return $docBlock; + return $docBlock . "\n */"; } - private function addMethod(Node\Stmt\ClassMethod $methodNode): void + /** + * @throws Exception + */ + private function addMethod(ClassMethod $methodNode): void { $classNode = $this->getClassNode(); - $methodName = $methodNode->name; + $methodName = $methodNode->name->toString(); $existingIndex = null; if ($this->methodExists($methodName)) { if (!$this->overwrite) { @@ -810,17 +919,19 @@ private function addMethod(Node\Stmt\ClassMethod $methodNode): void $newStatements[] = $methodNode; - if (null === $existingIndex) { - // add them to the end! + if (!empty($classNode->stmts)) { + if (null === $existingIndex) { + // add them to the end! - $classNode->stmts = array_merge($classNode->stmts, $newStatements); - } else { - array_splice( - $classNode->stmts, - $existingIndex, - 1, - $newStatements - ); + $classNode->stmts = array_merge($classNode->stmts, $newStatements); + } else { + array_splice( + $classNode->stmts, + $existingIndex, + 1, + $newStatements + ); + } } $this->updateSourceCodeFromNewStmts(); @@ -828,103 +939,80 @@ private function addMethod(Node\Stmt\ClassMethod $methodNode): void private function getEntityTypeHint($doctrineType): ?string { - switch ($doctrineType) { - case 'string': - case 'text': - case 'guid': - case 'bigint': - case 'decimal': - return 'string'; - - case 'array': - case 'simple_array': - case 'json': - case 'json_array': - return 'array'; - - case 'boolean': - return 'bool'; - - case 'integer': - case 'smallint': - return 'int'; - - case 'float': - return 'float'; - - case 'datetime': - case 'datetimetz': - case 'date': - case 'time': - return '\\'.\DateTimeInterface::class; - - case 'datetime_immutable': - case 'datetimetz_immutable': - case 'date_immutable': - case 'time_immutable': - return '\\'.\DateTimeImmutable::class; - - case 'dateinterval': - return '\\'.\DateInterval::class; - - case 'object': - case 'binary': - case 'blob': - default: - return null; - } + return match ($doctrineType) { + 'string', 'text', 'guid', 'bigint', 'decimal' => 'string', + 'array', 'simple_array', 'json', 'json_array' => 'array', + 'boolean' => 'bool', + 'integer', 'smallint' => 'int', + 'float' => 'float', + 'datetime', 'datetimetz', 'date', 'time' => '\\'.DateTimeInterface::class, + 'datetime_immutable', 'datetimetz_immutable', 'date_immutable', 'time_immutable' => '\\'.DateTimeImmutable::class, + 'dateinterval' => '\\'.DateInterval::class, + default => null, + }; } - private function isInSameNamespace($class): bool + /** + * @throws Exception + */ + private function isInSameNamespace(string $class): bool { $namespace = substr($class, 0, strrpos($class, '\\')); - return $this->getNamespaceNode()->name->toCodeString() === $namespace; + if (!empty($this->getNamespaceNode()->name)) { + return $this->getNamespaceNode()->name->toCodeString() === $namespace; + } + + return false; } + /** + * @throws Exception + */ private function getThisFullClassName(): string { - return (string) $this->getClassNode()->namespacedName; + if (!empty($this->getClassNode()->namespacedName)) { + return (string)$this->getClassNode()->namespacedName; + } + + return ''; } /** * Adds this new node where a new property should go. * * Useful for adding properties, or adding a constructor. + * @throws Exception */ private function addNodeAfterProperties(Node $newNode): void { $classNode = $this->getClassNode(); // try to add after last property - $targetNode = $this->findLastNode(static function ($node) { - return $node instanceof Node\Stmt\Property; - }, [$classNode]); + $targetNode = $this->findLastNode(static fn($node): bool => $node instanceof Node\Stmt\Property, [$classNode]); // otherwise, try to add after the last constant - if (!$targetNode) { - $targetNode = $this->findLastNode(static function ($node) { - return $node instanceof Node\Stmt\ClassConst; - }, [$classNode]); + if (!$targetNode instanceof Node) { + $targetNode = $this->findLastNode(static fn($node): bool => $node instanceof ClassConst, [$classNode]); } // otherwise, try to add after the last trait - if (!$targetNode) { - $targetNode = $this->findLastNode(static function ($node) { - return $node instanceof Node\Stmt\TraitUse; - }, [$classNode]); + if (!$targetNode instanceof Node) { + $targetNode = $this->findLastNode(static fn($node): bool => $node instanceof TraitUse, [$classNode]); } // add the new property after this node - if ($targetNode) { - $index = array_search($targetNode, $classNode->stmts, true); - - array_splice( - $classNode->stmts, - $index + 1, - 0, - [$this->createBlankLineNode(self::CONTEXT_CLASS), $newNode] - ); + if ($targetNode instanceof Node) { + if (!empty($classNode->stmts)) { + $index = array_search($targetNode, $classNode->stmts, true); + + array_splice( + $classNode->stmts, + $index + 1, + 0, + [$this->createBlankLineNode(self::CONTEXT_CLASS), $newNode] + ); + } $this->updateSourceCodeFromNewStmts(); @@ -935,32 +1023,46 @@ private function addNodeAfterProperties(Node $newNode): void // add an empty line, unless the class is totally empty if (!empty($classNode->stmts)) { array_unshift($classNode->stmts, $this->createBlankLineNode(self::CONTEXT_CLASS)); + array_unshift($classNode->stmts, $newNode); } - array_unshift($classNode->stmts, $newNode); + $this->updateSourceCodeFromNewStmts(); } + /** + * @throws Exception + */ private function methodExists(string $methodName): bool { return false !== $this->getMethodIndex($methodName); } - private function getMethodIndex(string $methodName) + /** + * @throws Exception + */ + private function getMethodIndex(string $methodName): bool|int|string { - foreach ($this->getClassNode()->stmts as $i => $node) { - if ($node instanceof Node\Stmt\ClassMethod && strtolower($node->name->toString()) === strtolower($methodName)) { - return $i; + if (!empty($this->getClassNode()->stmts)) { + foreach ($this->getClassNode()->stmts as $i => $node) { + if ($node instanceof ClassMethod && strtolower($node->name->toString()) === strtolower($methodName)) { + return $i; + } } } return false; } + /** + * @throws Exception + */ private function propertyExists(string $propertyName): bool { - foreach ($this->getClassNode()->stmts as $i => $node) { - if ($node instanceof Node\Stmt\Property && $node->props[0]->name->toString() === $propertyName) { - return true; + if (!empty($this->getClassNode()->stmts)) { + foreach ($this->getClassNode()->stmts as $node) { + if ($node instanceof Node\Stmt\Property && $node->props[0]->name->toString() === $propertyName) { + return true; + } } } @@ -969,12 +1071,12 @@ private function propertyExists(string $propertyName): bool private function writeNote(string $note): void { - if (null !== $this->io) { + if ($this->io instanceof ConsoleStyle) { $this->io->text($note); } } - private function addMethodParams(Builder\Method $methodBuilder, array $params): void + private function addMethodParams(Method $methodBuilder, array $params): void { foreach ($params as $param) { $methodBuilder->addParam($param); @@ -985,34 +1087,31 @@ private function addMethodParams(Builder\Method $methodBuilder, array $params): * builds a PHPParser Expr Node based on the value given in $value * throws an Exception when the given $value is not resolvable by this method. * - * @param mixed $value * - * @throws \Exception + * @throws Exception */ - private function buildNodeExprByValue($value): Node\Expr + private function buildNodeExprByValue(mixed $value): Expr { - switch (\gettype($value)) { + switch (gettype($value)) { case 'string': - $nodeValue = new Node\Scalar\String_($value); + $nodeValue = new String_($value); break; case 'integer': - $nodeValue = new Node\Scalar\LNumber($value); + $nodeValue = new LNumber($value); break; case 'double': - $nodeValue = new Node\Scalar\DNumber($value); + $nodeValue = new DNumber($value); break; case 'boolean': - $nodeValue = new Node\Expr\ConstFetch(new Node\Name($value ? 'true' : 'false')); + $nodeValue = new ConstFetch(new Name($value ? 'true' : 'false')); break; case 'array': $context = $this; - $arrayItems = array_map(static function ($key, $value) use ($context) { - return new Node\Expr\ArrayItem( - $context->buildNodeExprByValue($value), - !\is_int($key) ? $context->buildNodeExprByValue($key) : null - ); - }, array_keys($value), array_values($value)); - $nodeValue = new Node\Expr\Array_($arrayItems, ['kind' => Node\Expr\Array_::KIND_SHORT]); + $arrayItems = array_map(static fn($key, $value): ArrayItem => new ArrayItem( + $context->buildNodeExprByValue($value), + is_int($key) ? null : $context->buildNodeExprByValue($key) + ), array_keys($value), array_values($value)); + $nodeValue = new Array_($arrayItems, ['kind' => Array_::KIND_SHORT]); break; default: $nodeValue = null; @@ -1020,13 +1119,13 @@ private function buildNodeExprByValue($value): Node\Expr if (null === $nodeValue) { if ($value instanceof ClassNameValue) { - $nodeValue = new Node\Expr\ConstFetch( - new Node\Name( + $nodeValue = new ConstFetch( + new Name( sprintf('%s::class', $value->isSelf() ? 'self' : $value->getShortName()) ) ); } else { - throw new \Exception(sprintf('Cannot build a node expr for value of type "%s"', \gettype($value))); + throw new Exception(sprintf('Cannot build a node expr for value of type "%s"', gettype($value))); } } @@ -1037,19 +1136,20 @@ private function buildNodeExprByValue($value): Node\Expr * builds an PHPParser attribute node. * * @param string $attributeClass the attribute class which should be used for the attribute - * @param array $options the named arguments for the attribute ($key = argument name, $value = argument value) + * @param array $options the named arguments for the attribute ($key = argument name, $value = argument value) + * + * @return Attribute + * @throws Exception */ - private function buildAttributeNode(string $attributeClass, array $options): Node\Attribute + private function buildAttributeNode(string $attributeClass, array $options): Attribute { $options = $this->sortOptionsByClassConstructorParameters($options, $attributeClass); $context = $this; - $nodeArguments = array_map(static function ($option, $value) use ($context) { - return new Node\Arg($context->buildNodeExprByValue($value), false, false, [], new Node\Identifier($option)); - }, array_keys($options), array_values($options)); + $nodeArguments = array_map(static fn($option, $value): Arg => new Arg($context->buildNodeExprByValue($value), false, false, [], new Identifier($option)), array_keys($options), array_values($options)); - return new Node\Attribute( - new Node\Name($attributeClass), + return new Attribute( + new Name($attributeClass), $nodeArguments ); } @@ -1059,20 +1159,19 @@ private function buildAttributeNode(string $attributeClass, array $options): Nod * this prevents code inspections warnings for IDEs like intellij/phpstorm. * * option keys that are not found in the constructor will be added at the end of the sorted array + * @throws ReflectionException */ private function sortOptionsByClassConstructorParameters(array $options, string $classString): array { - if ('ORM\\' === substr($classString, 0, 4)) { + if (str_starts_with($classString, 'ORM\\')) { $classString = sprintf('Doctrine\\ORM\\Mapping\\%s', substr($classString, 4)); } - $constructorParameterNames = array_map(static function (\ReflectionParameter $reflectionParameter) { - return $reflectionParameter->getName(); - }, (new \ReflectionClass($classString))->getConstructor()->getParameters()); + $constructorParameterNames = array_map(static fn(ReflectionParameter $reflectionParameter): string => $reflectionParameter->getName(), (new ReflectionClass($classString))->getConstructor()->getParameters()); $sorted = []; foreach ($constructorParameterNames as $name) { - if (\array_key_exists($name, $options)) { + if (array_key_exists($name, $options)) { $sorted[$name] = $options[$name]; unset($options[$name]); } diff --git a/Manager/PermissionManager.php b/Manager/PermissionManager.php index 865f671a..cd90ce43 100644 --- a/Manager/PermissionManager.php +++ b/Manager/PermissionManager.php @@ -24,10 +24,8 @@ class PermissionManager implements PermissionManagerInterface /** * used in TicketManager::getTicketListQuery(). - * - * @param object $query */ - public function addUserPermissionsCondition($query, ?UserInterface $user) + public function addUserPermissionsCondition(object $query, ?UserInterface $user): object { if (\is_object($user)) { if (!$this->getUserManager()->hasRole($user, TicketRole::ADMIN)) { @@ -49,13 +47,11 @@ public function addUserPermissionsCondition($query, ?UserInterface $user) /** * used by UserManager::hasPermission(). - * - * @param ?UserInterface $user */ public function hasPermission(?UserInterface $user, TicketInterface $ticket): void { - if (!\is_object($user) || (!$this->getUserManager()->hasRole($user, TicketRole::ADMIN) && - (null === $ticket->getUserCreated() || $ticket->getUserCreated()->getId() != $user->getId())) + if (!\is_object($user) || (!$this->getUserManager()->hasRole($user, TicketRole::ADMIN) + && (!$ticket->getUserCreated() instanceof UserInterface || $ticket->getUserCreated()->getId() != $user->getId())) ) { throw new AccessDeniedHttpException(); } diff --git a/Manager/PermissionManagerInterface.php b/Manager/PermissionManagerInterface.php index 9215339a..96156846 100644 --- a/Manager/PermissionManagerInterface.php +++ b/Manager/PermissionManagerInterface.php @@ -20,15 +20,11 @@ interface PermissionManagerInterface { /** * used in TicketManager::getTicketListQuery(). - * - * @param object $query */ - public function addUserPermissionsCondition($query, UserInterface $user); + public function addUserPermissionsCondition(object $query, UserInterface $user); /** * used by UserManager::hasPermission(). - * - * @param ?UserInterface $user */ public function hasPermission(?UserInterface $user, TicketInterface $ticket): void; } diff --git a/Manager/TicketManager.php b/Manager/TicketManager.php index 895f339d..cbb9830e 100644 --- a/Manager/TicketManager.php +++ b/Manager/TicketManager.php @@ -13,8 +13,9 @@ namespace Hackzilla\Bundle\TicketBundle\Manager; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ObjectManager; use Hackzilla\Bundle\TicketBundle\Model\TicketInterface; use Hackzilla\Bundle\TicketBundle\Model\TicketMessageInterface; use Psr\Log\LoggerInterface; @@ -25,50 +26,44 @@ final class TicketManager implements TicketManagerInterface use PermissionManagerTrait; use UserManagerTrait; - private $translator; + private ?TranslatorInterface $translator = null; - private $translationDomain = 'HackzillaTicketBundle'; + private string $translationDomain = 'HackzillaTicketBundle'; - private $objectManager; + private ?EntityManagerInterface $objectManager = null; - private $ticketRepository; + private EntityRepository $ticketRepository; - private $messageRepository; - - private $ticketClass; - - private $ticketMessageClass; + private EntityRepository $messageRepository; /** * TicketManager constructor. */ - public function __construct(string $ticketClass, string $ticketMessageClass) + public function __construct(private readonly string $ticketClass, private readonly string $ticketMessageClass) { - $this->ticketClass = $ticketClass; - $this->ticketMessageClass = $ticketMessageClass; } public function setLogger(LoggerInterface $logger): self { if (!class_exists($this->ticketClass)) { - $logger->error(sprintf('Ticket entity %s doesn\'t exist', $this->ticketClass)); + $logger->error(\sprintf('Ticket entity %s doesn\'t exist', $this->ticketClass)); } if (!class_exists($this->ticketMessageClass)) { - $logger->error(sprintf('Message entity %s doesn\'t exist', $this->ticketMessageClass)); + $logger->error(\sprintf('Message entity %s doesn\'t exist', $this->ticketMessageClass)); } return $this; } - public function setObjectManager(ObjectManager $objectManager): self + public function setObjectManager(EntityManagerInterface $objectManager): self { $this->objectManager = $objectManager; - if ($this->ticketClass) { + if ('' !== $this->ticketClass && '0' !== $this->ticketClass) { $this->ticketRepository = $objectManager->getRepository($this->ticketClass); } - if ($this->ticketMessageClass) { + if ('' !== $this->ticketMessageClass && '0' !== $this->ticketMessageClass) { $this->messageRepository = $objectManager->getRepository($this->ticketMessageClass); } @@ -87,10 +82,8 @@ public function setTranslator(TranslatorInterface $translator): self /** * Create a new instance of Ticket entity. - * - * @return TicketInterface */ - public function createTicket() + public function createTicket(): TicketInterface { /* @var TicketInterface $ticket */ $ticket = new $this->ticketClass(); @@ -102,17 +95,13 @@ public function createTicket() /** * Create a new instance of TicketMessage Entity. - * - * @param TicketInterface $ticket - * - * @return TicketMessageInterface */ - public function createMessage(?TicketInterface $ticket = null) + public function createMessage(?TicketInterface $ticket = null): TicketMessageInterface { /* @var TicketMessageInterface $ticket */ $message = new $this->ticketMessageClass(); - if ($ticket) { + if ($ticket instanceof TicketInterface) { $message->setPriority($ticket->getPriority()); $message->setStatus($ticket->getStatus()); $message->setTicket($ticket); @@ -123,15 +112,12 @@ public function createMessage(?TicketInterface $ticket = null) return $message; } - /** - * {@inheritdoc} - */ public function updateTicket(TicketInterface $ticket, ?TicketMessageInterface $message = null): void { if (null === $ticket->getId()) { $this->objectManager->persist($ticket); } - if (null !== $message) { + if ($message instanceof TicketMessageInterface) { $message->setTicket($ticket); $this->objectManager->persist($message); $ticket->setPriority($message->getPriority()); @@ -142,7 +128,7 @@ public function updateTicket(TicketInterface $ticket, ?TicketMessageInterface $m /** * Delete a ticket from the database. */ - public function deleteTicket(TicketInterface $ticket) + public function deleteTicket(TicketInterface $ticket): void { $this->objectManager->remove($ticket); $this->objectManager->flush(); @@ -153,7 +139,7 @@ public function deleteTicket(TicketInterface $ticket) * * @return TicketInterface[] */ - public function findTickets() + public function findTickets(): array { return $this->ticketRepository->findAll(); } @@ -162,8 +148,6 @@ public function findTickets() * Find ticket in the database. * * @param int $ticketId - * - * @return ?TicketInterface */ public function getTicketById($ticketId): ?TicketInterface { @@ -174,8 +158,6 @@ public function getTicketById($ticketId): ?TicketInterface * Find message in the database. * * @param int $ticketMessageId - * - * @return TicketMessageInterface */ public function getMessageById($ticketMessageId): ?TicketMessageInterface { @@ -187,14 +169,11 @@ public function getMessageById($ticketMessageId): ?TicketMessageInterface * * @return array|TicketInterface[] */ - public function findTicketsBy(array $criteria) + public function findTicketsBy(array $criteria): array { return $this->ticketRepository->findBy($criteria); } - /** - * {@inheritdoc} - */ public function getTicketListQuery($ticketStatus, $ticketPriority = null): QueryBuilder { $query = $this->ticketRepository->createQueryBuilder('t') @@ -202,22 +181,14 @@ public function getTicketListQuery($ticketStatus, $ticketPriority = null): Query ->orderBy('t.lastMessage', 'DESC') ; - switch ($ticketStatus) { - case TicketMessageInterface::STATUS_CLOSED: - $query - ->andWhere('t.status = :status') - ->setParameter('status', TicketMessageInterface::STATUS_CLOSED) - ; - - break; - - case TicketMessageInterface::STATUS_OPEN: - default: - $query - ->andWhere('t.status != :status') - ->setParameter('status', TicketMessageInterface::STATUS_CLOSED) - ; - } + match ($ticketStatus) { + TicketMessageInterface::STATUS_CLOSED => $query + ->andWhere('t.status = :status') + ->setParameter('status', TicketMessageInterface::STATUS_CLOSED), + default => $query + ->andWhere('t.status != :status') + ->setParameter('status', TicketMessageInterface::STATUS_CLOSED), + }; if ($ticketPriority) { $query @@ -234,11 +205,9 @@ public function getTicketListQuery($ticketStatus, $ticketPriority = null): Query } /** - * @param int $days - * - * @return mixed + * @throws \DateMalformedIntervalStringException */ - public function getResolvedTicketOlderThan($days) + public function getResolvedTicketOlderThan(int $days): mixed { $closeBeforeDate = new \DateTime(); $closeBeforeDate->sub(new \DateInterval('P'.$days.'D')); @@ -256,12 +225,8 @@ public function getResolvedTicketOlderThan($days) /** * Lookup status code. - * - * @param string $statusStr - * - * @return int */ - public function getTicketStatus($statusStr) + public function getTicketStatus(string $statusStr): int|string|bool { static $statuses = false; @@ -278,12 +243,8 @@ public function getTicketStatus($statusStr) /** * Lookup priority code. - * - * @param string $priorityStr - * - * @return int */ - public function getTicketPriority($priorityStr) + public function getTicketPriority(?string $priorityStr): int|string|bool { static $priorities = false; @@ -295,6 +256,6 @@ public function getTicketPriority($priorityStr) } } - return array_search($priorityStr, $priorities, true); + return array_search($priorityStr ?? '', $priorities, true); } } diff --git a/Manager/TicketManagerInterface.php b/Manager/TicketManagerInterface.php index 4619c8f6..8ced27cd 100644 --- a/Manager/TicketManagerInterface.php +++ b/Manager/TicketManagerInterface.php @@ -37,28 +37,15 @@ public function findTicketsBy(array $criteria); public function getTicketListQuery($ticketStatus, $ticketPriority = null): QueryBuilder; - /** - * @param int $days - * - * @return mixed - */ - public function getResolvedTicketOlderThan($days); + public function getResolvedTicketOlderThan(int $days): mixed; /** * Lookup status code. - * - * @param string $statusStr - * - * @return int */ - public function getTicketStatus($statusStr); + public function getTicketStatus(string $statusStr): int|string|bool; /** * Lookup priority code. - * - * @param string $priorityStr - * - * @return int */ - public function getTicketPriority($priorityStr); + public function getTicketPriority(string $priorityStr): int|string|bool; } diff --git a/Manager/UserManager.php b/Manager/UserManager.php index e76f88f1..a54c9245 100644 --- a/Manager/UserManager.php +++ b/Manager/UserManager.php @@ -17,52 +17,37 @@ use Hackzilla\Bundle\TicketBundle\Model\TicketInterface; use Hackzilla\Bundle\TicketBundle\Model\UserInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; final class UserManager implements UserManagerInterface { use PermissionManagerTrait; - /** - * @var TokenStorageInterface - */ - private $tokenStorage; - - /** - * @var AuthorizationCheckerInterface - */ - private $authorizationChecker; - - /** - * @var ObjectRepository - */ - private $userRepository; + private ObjectRepository $userRepository; public function __construct( - TokenStorageInterface $tokenStorage, + private readonly TokenStorageInterface $tokenStorage, ObjectRepository $userRepository, - AuthorizationCheckerInterface $authorizationChecker, + private readonly AuthorizationCheckerInterface $authorizationChecker, ) { - $this->tokenStorage = $tokenStorage; - if (!is_subclass_of($userRepository->getClassName(), UserInterface::class)) { - throw new \InvalidArgumentException(sprintf('Argument 2 passed to "%s()" MUST be an object repository for a class implementing "%s".', __METHOD__, UserInterface::class)); + throw new \InvalidArgumentException(\sprintf('Argument 2 passed to "%s()" MUST be an object repository for a class implementing "%s".', __METHOD__, UserInterface::class)); } $this->userRepository = $userRepository; - $this->authorizationChecker = $authorizationChecker; } public function getCurrentUser(): ?UserInterface { - if (null === $this->tokenStorage->getToken()) { + if (!$this->tokenStorage->getToken() instanceof TokenInterface) { return null; } $user = $this->tokenStorage->getToken()->getUser(); - if (null !== $user && !$user instanceof UserInterface) { - throw new \LogicException(sprintf('The object representing the authenticated user MUST implement "%s".', UserInterface::class)); + if ($user instanceof \Symfony\Component\Security\Core\User\UserInterface && !$user instanceof UserInterface) { + throw new \LogicException(\sprintf('The object representing the authenticated user MUST implement "%s".', UserInterface::class)); } return $user; @@ -88,14 +73,11 @@ public function hasRole(?UserInterface $user, string $role): bool return $this->authorizationChecker->isGranted($role); } - /** - * @param ?UserInterface $user - */ public function hasPermission(?UserInterface $user, TicketInterface $ticket): bool { try { $this->getPermissionManager()->hasPermission($user, $ticket); - } catch (\Exception $exception) { + } catch (\Exception) { return false; } diff --git a/Model/MessageAttachmentInterface.php b/Model/MessageAttachmentInterface.php index 9058918b..1e49476a 100644 --- a/Model/MessageAttachmentInterface.php +++ b/Model/MessageAttachmentInterface.php @@ -18,48 +18,34 @@ interface MessageAttachmentInterface extends TicketMessageInterface { /** - * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $file - * * @return $this */ - public function setAttachmentFile(?File $file = null); + public function setAttachmentFile(?File $file = null): self; - /** - * @return File - */ - public function getAttachmentFile(); + public function getAttachmentFile(): ?File; /** * @return $this */ - public function setAttachmentName(string $name); + public function setAttachmentName(string $name): self; - /** - * @return string - */ - public function getAttachmentName(); + public function getAttachmentName(): ?string; /** * @param int $size Size in bytes * * @return $this */ - public function setAttachmentSize(int $size); + public function setAttachmentSize(int $size): self; - /** - * @return string - */ - public function getAttachmentSize(); + public function getAttachmentSize(): ?int; /** * @param string $mimeType Attachment mime type * * @return $this */ - public function setAttachmentMimeType(string $mimeType); + public function setAttachmentMimeType(string $mimeType): self; - /** - * @return string - */ - public function getAttachmentMimeType(); + public function getAttachmentMimeType(): ?string; } diff --git a/Model/MessageAttachmentTrait.php b/Model/MessageAttachmentTrait.php index 089f9fb5..0d32bb42 100644 --- a/Model/MessageAttachmentTrait.php +++ b/Model/MessageAttachmentTrait.php @@ -22,15 +22,13 @@ trait MessageAttachmentTrait * NOTE: This field is not persisted to database! * * @Vich\UploadableField(mapping="ticket_message_attachment", fileNameProperty="attachmentName", originalName="attachmentFile", size="attachmentSize") - * - * @var File|null */ - protected $attachmentFile; + protected ?File $attachmentFile; /** * {@inheritdoc} */ - public function setAttachmentFile(?File $file = null) + public function setAttachmentFile(?File $file = null): self { $this->attachmentFile = $file; @@ -40,7 +38,7 @@ public function setAttachmentFile(?File $file = null) /** * {@inheritdoc} */ - public function getAttachmentFile() + public function getAttachmentFile(): ?File { return $this->attachmentFile; } @@ -48,7 +46,7 @@ public function getAttachmentFile() /** * {@inheritdoc} */ - public function setAttachmentName($name) + public function setAttachmentName($name): self { $this->attachmentName = $name; @@ -58,7 +56,7 @@ public function setAttachmentName($name) /** * {@inheritdoc} */ - public function getAttachmentName() + public function getAttachmentName(): ?string { return $this->attachmentName; } @@ -66,7 +64,7 @@ public function getAttachmentName() /** * {@inheritdoc} */ - public function setAttachmentSize($size) + public function setAttachmentSize($size): self { $this->attachmentSize = $size; @@ -76,7 +74,7 @@ public function setAttachmentSize($size) /** * {@inheritdoc} */ - public function getAttachmentSize() + public function getAttachmentSize(): ?int { return $this->attachmentSize; } @@ -84,7 +82,7 @@ public function getAttachmentSize() /** * {@inheritdoc} */ - public function setAttachmentMimeType($mimeType) + public function setAttachmentMimeType($mimeType): self { $this->attachmentMimeType = $mimeType; @@ -94,7 +92,7 @@ public function setAttachmentMimeType($mimeType) /** * {@inheritdoc} */ - public function getAttachmentMimeType() + public function getAttachmentMimeType(): ?string { return $this->attachmentMimeType; } diff --git a/Model/TicketInterface.php b/Model/TicketInterface.php index 922e164a..bdd0cc53 100644 --- a/Model/TicketInterface.php +++ b/Model/TicketInterface.php @@ -27,14 +27,14 @@ public function getId(); * * @return $this */ - public function setStatus(int $status); + public function setStatus(int $status): self; /** * Set ticket status by string. * * @return $this */ - public function setStatusString(string $status); + public function setStatusString(string $status): self; /** * Get ticket status. @@ -51,14 +51,14 @@ public function getStatusString(): ?string; * * @return $this */ - public function setPriority(int $priority); + public function setPriority(int $priority): self; /** * Set ticket priority string. * * @return $this */ - public function setPriorityString(string $priority); + public function setPriorityString(string $priority): self; /** * Get priority. @@ -73,27 +73,21 @@ public function getPriorityString(): ?string; /** * Set userCreated. * - * @param ?UserInterface $userCreated - * * @return $this */ - public function setUserCreated(?UserInterface $userCreated); + public function setUserCreated(?UserInterface $userCreated): self; /** * Get userCreated. - * - * @return ?UserInterface */ public function getUserCreated(): ?UserInterface; /** * Set lastUser. * - * @param ?UserInterface $lastUser - * * @return $this */ - public function setLastUser(?UserInterface $lastUser); + public function setLastUser(?UserInterface $lastUser): self; /** * Get lastUser . @@ -105,7 +99,7 @@ public function getLastUser(): ?UserInterface; * * @return $this */ - public function setLastMessage(\DateTimeInterface $lastMessage); + public function setLastMessage(\DateTimeInterface $lastMessage): self; /** * Get lastMessage. @@ -117,7 +111,7 @@ public function getLastMessage(): ?\DateTimeInterface; * * @return $this */ - public function setCreatedAt(\DateTimeInterface $createdAt); + public function setCreatedAt(\DateTimeInterface $createdAt): self; /** * Get createdAt. @@ -129,7 +123,7 @@ public function getCreatedAt(): ?\DateTimeInterface; * * @return $this */ - public function setSubject(string $subject); + public function setSubject(string $subject): self; /** * Get ticket subject. @@ -141,7 +135,7 @@ public function getSubject(): ?string; * * @return $this */ - public function addMessage(TicketMessageInterface $message); + public function addMessage(TicketMessageInterface $message): self; /** * Remove message. diff --git a/Model/TicketMessageInterface.php b/Model/TicketMessageInterface.php index 6bba7245..79a8b395 100644 --- a/Model/TicketMessageInterface.php +++ b/Model/TicketMessageInterface.php @@ -64,14 +64,14 @@ public function getId(); * * @return $this */ - public function setStatus(int $status); + public function setStatus(int $status): self; /** * Set status string. * * @return $this */ - public function setStatusString(string $status); + public function setStatusString(string $status): self; /** * Get status. @@ -88,14 +88,14 @@ public function getStatusString(): ?string; * * @return $this */ - public function setPriority(int $priority); + public function setPriority(int $priority): self; /** * Set priority string. * * @return $this */ - public function setPriorityString(string $priority); + public function setPriorityString(string $priority): self; /** * Get priority. @@ -110,25 +110,21 @@ public function getPriorityString(): ?string; /** * Set user. * - * @param ?UserInterface $user - * * @return $this */ - public function setUser(?UserInterface $user); + public function setUser(?UserInterface $user): self; /** * Get user. - * - * @return ?UserInterface */ - public function getUser(); + public function getUser(): ?UserInterface; /** * Set message. * * @return $this */ - public function setMessage(string $message); + public function setMessage(string $message): self; /** * Get message. @@ -140,21 +136,19 @@ public function getMessage(): ?string; * * @return $this */ - public function setCreatedAt(\DateTime $createdAt); + public function setCreatedAt(\DateTime $createdAt): self; /** * Get createdAt. - * - * @return \DateTime */ - public function getCreatedAt(); + public function getCreatedAt(): \DateTime; /** * Set ticket. * * @return $this */ - public function setTicket(?TicketInterface $ticket = null); + public function setTicket(?TicketInterface $ticket = null): self; /** * Get ticket. diff --git a/Model/TicketMessageTrait.php b/Model/TicketMessageTrait.php index 07d11e28..373aba66 100644 --- a/Model/TicketMessageTrait.php +++ b/Model/TicketMessageTrait.php @@ -13,6 +13,9 @@ namespace Hackzilla\Bundle\TicketBundle\Model; +use Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity\TicketMessage; +use Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity\TicketMessageWithAttachment; + /** * Ticket Message Trait. */ @@ -21,9 +24,9 @@ trait TicketMessageTrait /** * Set status. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setStatus(int $status) + public function setStatus(int $status): self { $this->status = $status; @@ -33,9 +36,9 @@ public function setStatus(int $status) /** * Set status string. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setStatusString(string $status) + public function setStatusString(string $status): self { $status = array_search(strtolower($status), TicketMessageInterface::STATUSES, true); @@ -59,7 +62,7 @@ public function getStatus(): ?int */ public function getStatusString(): ?string { - if (!empty(TicketMessageInterface::STATUSES[$this->status])) { + if (isset(TicketMessageInterface::STATUSES[$this->status]) && ('' !== TicketMessageInterface::STATUSES[$this->status] && '0' !== TicketMessageInterface::STATUSES[$this->status])) { return TicketMessageInterface::STATUSES[$this->status]; } @@ -69,9 +72,9 @@ public function getStatusString(): ?string /** * Set priority. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setPriority(int $priority) + public function setPriority(int $priority): self { $this->priority = $priority; @@ -81,9 +84,9 @@ public function setPriority(int $priority) /** * Set priority string. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setPriorityString(string $priority) + public function setPriorityString(string $priority): self { $priority = array_search(strtolower($priority), TicketMessageInterface::PRIORITIES, true); @@ -107,7 +110,7 @@ public function getPriority(): ?int */ public function getPriorityString(): ?string { - if (!empty(TicketMessageInterface::PRIORITIES[$this->priority])) { + if (isset(TicketMessageInterface::PRIORITIES[$this->priority]) && ('' !== TicketMessageInterface::PRIORITIES[$this->priority] && '0' !== TicketMessageInterface::PRIORITIES[$this->priority])) { return TicketMessageInterface::PRIORITIES[$this->priority]; } @@ -117,11 +120,9 @@ public function getPriorityString(): ?string /** * Set user. * - * @param ?UserInterface $user - * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setUser($user) + public function setUser(?UserInterface $user): self { $this->user = $user; @@ -139,9 +140,9 @@ public function getUser(): ?UserInterface /** * Set message. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setMessage(string $message) + public function setMessage(string $message): self { $this->message = $message; @@ -159,9 +160,9 @@ public function getMessage(): ?string /** * Set createdAt. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setCreatedAt(\DateTime $createdAt) + public function setCreatedAt(\DateTime $createdAt): self { $this->createdAt = $createdAt; @@ -170,10 +171,8 @@ public function setCreatedAt(\DateTime $createdAt) /** * Get createdAt. - * - * @return \DateTime */ - public function getCreatedAt() + public function getCreatedAt(): \DateTime { return $this->createdAt; } @@ -181,15 +180,15 @@ public function getCreatedAt() /** * Set ticket. * - * @return $this + * @return TicketMessage|TicketMessageTrait|TicketMessageWithAttachment */ - public function setTicket(?TicketInterface $ticket = null) + public function setTicket(?TicketInterface $ticket = null): self { $this->ticket = $ticket; $user = $this->getUser(); // if null, then new ticket - if (null === $ticket->getUserCreated()) { + if (!$ticket->getUserCreated() instanceof UserInterface) { $ticket->setUserCreated($user); } diff --git a/Model/TicketTrait.php b/Model/TicketTrait.php index e6793de4..28caf9a7 100644 --- a/Model/TicketTrait.php +++ b/Model/TicketTrait.php @@ -13,8 +13,8 @@ namespace Hackzilla\Bundle\TicketBundle\Model; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity\Ticket; /** * Ticket Trait. @@ -24,9 +24,9 @@ trait TicketTrait /** * Set status. * - * @return $this + * @return Ticket|TicketTrait */ - public function setStatus(int $status) + public function setStatus(int $status): self { $this->status = $status; @@ -36,9 +36,9 @@ public function setStatus(int $status) /** * Set status string. * - * @return $this + * @return Ticket|TicketTrait */ - public function setStatusString(string $status) + public function setStatusString(string $status): self { $status = array_search(strtolower($status), TicketMessageInterface::STATUSES, true); @@ -72,9 +72,9 @@ public function getStatusString(): ?string /** * Set priority. * - * @return $this + * @return Ticket|TicketTrait */ - public function setPriority(int $priority) + public function setPriority(int $priority): self { $this->priority = $priority; @@ -84,9 +84,9 @@ public function setPriority(int $priority) /** * Set priority string. * - * @return $this + * @return Ticket|TicketTrait */ - public function setPriorityString(string $priority) + public function setPriorityString(string $priority): self { $priority = array_search(strtolower($priority), TicketMessageInterface::PRIORITIES, true); @@ -120,11 +120,9 @@ public function getPriorityString(): ?string /** * Set userCreated. * - * @param ?UserInterface $userCreated - * - * @return $this + * @return Ticket|TicketTrait */ - public function setUserCreated(?UserInterface $userCreated) + public function setUserCreated(?UserInterface $userCreated): self { $this->userCreated = $userCreated; @@ -133,8 +131,6 @@ public function setUserCreated(?UserInterface $userCreated) /** * Get userCreated. - * - * @return ?UserInterface */ public function getUserCreated(): ?UserInterface { @@ -144,11 +140,9 @@ public function getUserCreated(): ?UserInterface /** * Set lastUser. * - * @param ?UserInterface $lastUser - * - * @return $this + * @return Ticket|TicketTrait */ - public function setLastUser(?UserInterface $lastUser) + public function setLastUser(?UserInterface $lastUser): self { $this->lastUser = $lastUser; @@ -157,8 +151,6 @@ public function setLastUser(?UserInterface $lastUser) /** * Get lastUser. - * - * @return ?UserInterface */ public function getLastUser(): ?UserInterface { @@ -168,9 +160,9 @@ public function getLastUser(): ?UserInterface /** * Set lastMessage. * - * @return $this + * @return Ticket|TicketTrait */ - public function setLastMessage(\DateTimeInterface $lastMessage) + public function setLastMessage(\DateTimeInterface $lastMessage): self { $this->lastMessage = $lastMessage; @@ -188,9 +180,9 @@ public function getLastMessage(): ?\DateTimeInterface /** * Set createdAt. * - * @return $this + * @return Ticket|TicketTrait */ - public function setCreatedAt(\DateTimeInterface $createdAt) + public function setCreatedAt(\DateTimeInterface $createdAt): self { $this->createdAt = $createdAt; @@ -208,9 +200,9 @@ public function getCreatedAt(): ?\DateTimeInterface /** * Set subject. * - * @return $this + * @return Ticket|TicketTrait */ - public function setSubject(string $subject) + public function setSubject(string $subject): self { $this->subject = $subject; @@ -228,9 +220,9 @@ public function getSubject(): ?string /** * Add message. * - * @return $this + * @return Ticket|TicketTrait */ - public function addMessage(TicketMessageInterface $message) + public function addMessage(TicketMessageInterface $message): self { $this->messages[] = $message; @@ -240,9 +232,9 @@ public function addMessage(TicketMessageInterface $message) /** * Remove message. * - * @return $this + * @return Ticket|TicketTrait */ - public function removeMessage(TicketMessageInterface $message) + public function removeMessage(TicketMessageInterface $message): self { $this->messages->removeElement($message); @@ -254,10 +246,6 @@ public function removeMessage(TicketMessageInterface $message) */ public function getMessages(): Collection { - if (null === $this->messages) { - $this->messages = new ArrayCollection(); - } - return $this->messages; } } diff --git a/Model/UserInterface.php b/Model/UserInterface.php index dca43d26..2e57379a 100644 --- a/Model/UserInterface.php +++ b/Model/UserInterface.php @@ -13,11 +13,12 @@ namespace Hackzilla\Bundle\TicketBundle\Model; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface as BaseUserInterface; /* @phpstan-ignore-next-line */ -if (\Symfony\Component\HttpKernel\Kernel::MAJOR_VERSION < 5) { +if (Kernel::MAJOR_VERSION < 5) { interface UserInterface extends BaseUserInterface { public function __toString(): string; diff --git a/Resources/config/commands.php b/Resources/config/commands.php index 11aeaa37..57e1780f 100644 --- a/Resources/config/commands.php +++ b/Resources/config/commands.php @@ -24,9 +24,6 @@ $containerConfigurator->services() ->set('hackzilla_ticket.command.autoclosing', AutoClosingCommand::class) - ->tag('console.command', [ - 'command' => 'ticket:autoclosing', - ]) ->args([ new ReferenceConfigurator(TicketManagerInterface::class), new ReferenceConfigurator(UserManagerInterface::class), @@ -35,9 +32,6 @@ ]) ->set('hackzilla_ticket.command.create', TicketManagerCommand::class) - ->tag('console.command', [ - 'command' => 'ticket:create', - ]) ->args([ new ReferenceConfigurator(TicketManagerInterface::class), new ReferenceConfigurator(UserManagerInterface::class), diff --git a/Resources/config/controllers.php b/Resources/config/controllers.php index 771fdd23..323a4e9b 100644 --- a/Resources/config/controllers.php +++ b/Resources/config/controllers.php @@ -17,12 +17,15 @@ use Hackzilla\Bundle\TicketBundle\Manager\UserManagerInterface; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; +use Symfony\Component\Form\FormFactoryInterface; +use Twig\Environment; +use Vich\UploaderBundle\Handler\DownloadHandler; return static function (ContainerConfigurator $container): void { // Use "service" function for creating references to services when dropping support for Symfony 4.4 // Use "param" function for creating references to parameters when dropping support for Symfony 5.1 - if (class_exists('\\Vich\\UploaderBundle\\Handler\\DownloadHandler')) { + if (class_exists(DownloadHandler::class)) { $container->services() ->set(TicketAttachmentController::class) ->args([ @@ -46,6 +49,8 @@ new ReferenceConfigurator(TicketManagerInterface::class), new ReferenceConfigurator('translator'), new ReferenceConfigurator(UserManagerInterface::class), + new ReferenceConfigurator(Environment::class), + new ReferenceConfigurator(FormFactoryInterface::class), ]) ->call('setContainer', [new ReferenceConfigurator('service_container')]) ->tag('controller.service_arguments') diff --git a/Resources/config/event_listener.php b/Resources/config/event_listener.php index b8547771..37e2737c 100644 --- a/Resources/config/event_listener.php +++ b/Resources/config/event_listener.php @@ -20,6 +20,5 @@ $containerConfigurator->services() ->set('hackzilla_ticket.file_upload_subscriber', FileSubscriber::class) - ->tag('kernel.event_subscriber') ; }; diff --git a/Resources/config/maker.php b/Resources/config/maker.php index 813e9ce6..1d7ef626 100644 --- a/Resources/config/maker.php +++ b/Resources/config/maker.php @@ -11,6 +11,8 @@ * file that was distributed with this source code. */ +use Hackzilla\Bundle\TicketBundle\Maker\MessageMaker; +use Hackzilla\Bundle\TicketBundle\Maker\TicketMaker; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; @@ -19,7 +21,7 @@ // Use "param" function for creating references to parameters when dropping support for Symfony 5.1 $containerConfigurator->services() - ->set('hackzilla_ticket.maker.ticket', \Hackzilla\Bundle\TicketBundle\Maker\TicketMaker::class) + ->set('hackzilla_ticket.maker.ticket', TicketMaker::class) ->args([ new ReferenceConfigurator('maker.file_manager'), new ReferenceConfigurator('maker.doctrine_helper'), @@ -27,7 +29,7 @@ ]) ->tag('maker.command') - ->set('hackzilla_ticket.maker.message', \Hackzilla\Bundle\TicketBundle\Maker\MessageMaker::class) + ->set('hackzilla_ticket.maker.message', MessageMaker::class) ->args([ new ReferenceConfigurator('maker.file_manager'), new ReferenceConfigurator('maker.doctrine_helper'), diff --git a/Resources/config/routing/hackzilla_ticket.xml b/Resources/config/routing/hackzilla_ticket.xml index 632b5bbe..b9500393 100644 --- a/Resources/config/routing/hackzilla_ticket.xml +++ b/Resources/config/routing/hackzilla_ticket.xml @@ -1,10 +1,10 @@ - - - - - - - + + + + + + + diff --git a/Resources/config/routing/ticket.yml b/Resources/config/routing/ticket.yml index 631b4808..12233b40 100644 --- a/Resources/config/routing/ticket.yml +++ b/Resources/config/routing/ticket.yml @@ -1,30 +1,30 @@ # NEXT_MAJOR: remove this file and add upgrade note. hackzilla_ticket: path: / - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::indexAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::index' hackzilla_ticket_show: path: /{ticketId}/show - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::showAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::show' hackzilla_ticket_new: path: /new - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::newAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::new' hackzilla_ticket_create: path: /create - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::createAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::create' methods: post hackzilla_ticket_delete: path: /{ticketId}/delete - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::deleteAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::delete' methods: post|delete hackzilla_ticket_reply: path: /{ticketId}/reply - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::replyAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketController::reply' hackzilla_ticket_attachment: path: /attachment/{ticketMessageId}/download - controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketAttachmentController::downloadAction' + controller: 'Hackzilla\Bundle\TicketBundle\Controller\TicketAttachmentController::download' diff --git a/Resources/config/services.yaml b/Resources/config/services.yaml new file mode 100644 index 00000000..d376ff65 --- /dev/null +++ b/Resources/config/services.yaml @@ -0,0 +1,10 @@ +services: + Hackzilla\Bundle\TicketBundle\TwigExtension\TicketGlobalExtension: + arguments: + $templates: '%hackzilla_ticket.templates%' + tags: ['twig.extension'] + + Hackzilla\Bundle\TicketBundle\TwigExtension\TicketFeatureExtension: + arguments: + $ticketFeatures: '@hackzilla_ticket.features' # Injection de service existant (à adapter si différent) + tags: ['twig.extension'] diff --git a/Resources/config/twig.php b/Resources/config/twig.php index 349fff88..f9de4ac7 100644 --- a/Resources/config/twig.php +++ b/Resources/config/twig.php @@ -22,13 +22,11 @@ $containerConfigurator->services() ->set('hackzilla_ticket.component.twig_extension.ticket_features', TicketFeatureExtension::class) - ->tag('twig.extension') ->args([ new ReferenceConfigurator('hackzilla_ticket.features'), ]) ->set('hackzilla_ticket.component.twig_extension.ticket_global', TicketGlobalExtension::class) - ->tag('twig.extension') ->args([ '%hackzilla_ticket.templates%', ]) diff --git a/Resources/views/Ticket/index.html.twig b/Resources/views/Ticket/index.html.twig index 424231ec..d533060c 100644 --- a/Resources/views/Ticket/index.html.twig +++ b/Resources/views/Ticket/index.html.twig @@ -67,7 +67,7 @@ {% endblock %} diff --git a/Tests/Component/TicketFeaturesTest.php b/Tests/Component/TicketFeaturesTest.php index 269f6171..65b30cb6 100644 --- a/Tests/Component/TicketFeaturesTest.php +++ b/Tests/Component/TicketFeaturesTest.php @@ -22,28 +22,21 @@ final class TicketFeaturesTest extends WebTestCase { /** * @dataProvider constructProvider - * - * @param string $class */ - public function testConstruct(array $features, $class) + public function testConstruct(array $features, string $class): void { $this->assertInstanceOf(TicketFeatures::class, new TicketFeatures($features, $class)); } - public function constructProvider() + public function constructProvider(): \Iterator { - return [ - [[], \stdClass::class], - ]; + yield [[], \stdClass::class]; } /** * @dataProvider featureAttachmentProvider - * - * @param string $class - * @param bool|null $compare */ - public function testFeatureAttachment(array $features, $class, $compare) + public function testFeatureAttachment(array $features, string $class, bool $compare): void { $obj = new TicketFeatures($features, $class); @@ -51,12 +44,10 @@ public function testFeatureAttachment(array $features, $class, $compare) $this->assertSame($obj->hasFeature('attachment'), $compare); } - public function featureAttachmentProvider() + public function featureAttachmentProvider(): \Iterator { - return [ - [[], TicketMessage::class, false], - [['attachment' => true], TicketMessage::class, false], - [['attachment' => true], TicketMessageWithAttachment::class, true], - ]; + yield [[], TicketMessage::class, false]; + yield [['attachment' => true], TicketMessage::class, false]; + yield [['attachment' => true], TicketMessageWithAttachment::class, true]; } } diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 0a491945..a753e120 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -18,7 +18,7 @@ final class ConfigurationTest extends WebTestCase { - private $object; + private ?Configuration $object = null; protected function setUp(): void { @@ -30,7 +30,7 @@ protected function tearDown(): void $this->object = null; } - public function testObjectCreated() + public function testObjectCreated(): void { $this->assertInstanceOf(Configuration::class, $this->object); } diff --git a/Tests/DependencyInjection/HackzillaTicketExtensionTest.php b/Tests/DependencyInjection/HackzillaTicketExtensionTest.php index a281c6e2..ff5b1172 100644 --- a/Tests/DependencyInjection/HackzillaTicketExtensionTest.php +++ b/Tests/DependencyInjection/HackzillaTicketExtensionTest.php @@ -18,7 +18,7 @@ class HackzillaTicketExtensionTest extends WebTestCase { - private $object; + private ?HackzillaTicketExtension $object = null; protected function setUp(): void { diff --git a/Tests/Fixtures/Entity/Ticket.php b/Tests/Fixtures/Entity/Ticket.php index e2efd960..9ee17b8c 100644 --- a/Tests/Fixtures/Entity/Ticket.php +++ b/Tests/Fixtures/Entity/Ticket.php @@ -14,6 +14,8 @@ namespace Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Hackzilla\Bundle\TicketBundle\Model\TicketInterface; use Hackzilla\Bundle\TicketBundle\Model\TicketTrait; @@ -22,39 +24,42 @@ * @author Javier Spagnoletti * @author Daniel Platt */ -#[ORM\Entity()] +#[ORM\Entity] class Ticket implements TicketInterface { use TicketTrait; #[ORM\Id] #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - private $id; + #[ORM\Column(type: Types::INTEGER)] + private ?int $id = null; - #[ORM\Column(type: 'datetime', nullable: false)] - private $lastMessage; + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)] + private \DateTimeInterface $lastMessage; - #[ORM\Column(type: 'text', nullable: false)] - private $subject; + #[ORM\Column(type: Types::TEXT, nullable: false)] + private string $subject; - #[ORM\Column(type: 'integer', nullable: false)] - private $status; + #[ORM\Column(type: Types::INTEGER, nullable: false)] + private int $status; - #[ORM\Column(type: 'integer', nullable: false)] - private $priority; + #[ORM\Column(type: Types::INTEGER, nullable: false)] + private int $priority; - #[ORM\Column(type: 'datetime', nullable: false)] - private $createdAt; + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)] + private \DateTimeInterface $createdAt; - #[ORM\OneToMany(mappedBy: 'ticket', targetEntity: TicketMessage::class)] - private $messages; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: TicketMessage::class, mappedBy: 'ticket')] + private Collection $messages; #[ORM\ManyToOne(targetEntity: User::class)] - private $userCreated; + private ?User $userCreated = null; #[ORM\ManyToOne(targetEntity: User::class)] - private $lastUser; + private ?User $lastUser = null; public function __construct() { diff --git a/Tests/Fixtures/Entity/TicketMessage.php b/Tests/Fixtures/Entity/TicketMessage.php index b498f783..4ba28832 100644 --- a/Tests/Fixtures/Entity/TicketMessage.php +++ b/Tests/Fixtures/Entity/TicketMessage.php @@ -13,6 +13,7 @@ namespace Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Hackzilla\Bundle\TicketBundle\Model\TicketMessageInterface; use Hackzilla\Bundle\TicketBundle\Model\TicketMessageTrait; @@ -21,34 +22,34 @@ * @author Javier Spagnoletti * @author Daniel Platt */ -#[ORM\Entity()] +#[ORM\Entity] class TicketMessage implements TicketMessageInterface { use TicketMessageTrait; #[ORM\Id] #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - private $id; + #[ORM\Column(type: Types::INTEGER)] + private ?int $id = null; - #[ORM\Column(type: 'text', nullable: true)] - private $message; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $message = null; - #[ORM\Column(type: 'integer', nullable: false)] - private $status; + #[ORM\Column(type: Types::INTEGER, nullable: false)] + private int $status; - #[ORM\Column(type: 'integer', nullable: false)] - private $priority; + #[ORM\Column(type: Types::INTEGER, nullable: false)] + private int $priority; - #[ORM\Column(type: 'datetime', nullable: false)] - private $createdAt; + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)] + private \DateTimeInterface $createdAt; #[ORM\ManyToOne(targetEntity: Ticket::class, inversedBy: 'messages')] #[ORM\JoinColumn(nullable: false)] - private $ticket; + private ?Ticket $ticket = null; #[ORM\ManyToOne(targetEntity: User::class)] - private $user; + private ?User $user = null; public function __construct() { diff --git a/Tests/Fixtures/Entity/TicketMessageWithAttachment.php b/Tests/Fixtures/Entity/TicketMessageWithAttachment.php index ca27d62a..82a1823e 100644 --- a/Tests/Fixtures/Entity/TicketMessageWithAttachment.php +++ b/Tests/Fixtures/Entity/TicketMessageWithAttachment.php @@ -13,6 +13,7 @@ namespace Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Hackzilla\Bundle\TicketBundle\Model\MessageAttachmentInterface; use Hackzilla\Bundle\TicketBundle\Model\MessageAttachmentTrait; @@ -23,7 +24,7 @@ * @author Javier Spagnoletti * @author Daniel Platt */ -#[ORM\Entity()] +#[ORM\Entity] class TicketMessageWithAttachment implements TicketMessageInterface, MessageAttachmentInterface { use MessageAttachmentTrait; @@ -31,36 +32,36 @@ class TicketMessageWithAttachment implements TicketMessageInterface, MessageAtta #[ORM\Id] #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - private $id; + #[ORM\Column(type: Types::INTEGER)] + private ?int $id = null; - #[ORM\Column(type: 'text', nullable: true)] - private $message; + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $message = null; - #[ORM\Column(type: 'integer', nullable: false)] - private $status; + #[ORM\Column(type: Types::INTEGER, nullable: false)] + private int $status; - #[ORM\Column(type: 'integer', nullable: false)] - private $priority; + #[ORM\Column(type: Types::INTEGER, nullable: false)] + private int $priority; - #[ORM\Column(type: 'datetime', nullable: false)] - private $createdAt; + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)] + private \DateTimeInterface $createdAt; - #[ORM\Column(type: 'string', length: 255, nullable: true)] - private $attachmentName; + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + private ?string $attachmentName = null; - #[ORM\Column(type: 'integer', nullable: true)] - private $attachmentSize; + #[ORM\Column(type: Types::INTEGER, nullable: true)] + private ?int $attachmentSize = null; - #[ORM\Column(type: 'string', length: 255, nullable: true)] - private $attachmentMimeType; + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] + private ?string $attachmentMimeType = null; #[ORM\ManyToOne(targetEntity: Ticket::class, inversedBy: 'messages')] #[ORM\JoinColumn(nullable: false)] - private $ticket; + private ?Ticket $ticket = null; #[ORM\ManyToOne(targetEntity: User::class)] - private $user; + private ?User $user = null; public function __construct() { diff --git a/Tests/Fixtures/Entity/User.php b/Tests/Fixtures/Entity/User.php index 10eebc5e..d20f6986 100644 --- a/Tests/Fixtures/Entity/User.php +++ b/Tests/Fixtures/Entity/User.php @@ -13,6 +13,7 @@ namespace Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Hackzilla\Bundle\TicketBundle\Model\UserInterface as TicketBundleUserInterface; @@ -20,23 +21,23 @@ * @author Javier Spagnoletti * @author Daniel Platt */ -#[ORM\Entity()] +#[ORM\Entity] #[ORM\Table(name: '`user`')] class User implements TicketBundleUserInterface { #[ORM\Id] #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - private $id; + #[ORM\Column(type: Types::INTEGER)] + private ?int $id = null; - #[ORM\Column(type: 'string', length: 180, unique: true)] - private $email; + #[ORM\Column(type: Types::STRING, length: 180, unique: true)] + private ?string $email = null; - #[ORM\Column(type: 'json')] - private $roles = []; + #[ORM\Column(type: Types::JSON)] + private array $roles = []; - #[ORM\Column(type: 'string')] - private $password; + #[ORM\Column(type: Types::STRING)] + private ?string $password = null; public function __toString(): string { @@ -126,7 +127,7 @@ public function getSalt(): ?string /** * @see UserInterface */ - public function eraseCredentials() + public function eraseCredentials(): void { // If you store any temporary, sensitive data on the user, clear it here // $this->plainPassword = null; diff --git a/Tests/Form/DataTransformer/StatusTransformerTest.php b/Tests/Form/DataTransformer/StatusTransformerTest.php index 800d9fd0..03977809 100644 --- a/Tests/Form/DataTransformer/StatusTransformerTest.php +++ b/Tests/Form/DataTransformer/StatusTransformerTest.php @@ -20,7 +20,7 @@ class StatusTransformerTest extends WebTestCase { - private $object; + private ?StatusTransformer $object = null; protected function setUp(): void { diff --git a/Tests/Form/Type/PriorityTypeTest.php b/Tests/Form/Type/PriorityTypeTest.php index 767d424b..959d5781 100644 --- a/Tests/Form/Type/PriorityTypeTest.php +++ b/Tests/Form/Type/PriorityTypeTest.php @@ -18,7 +18,7 @@ class PriorityTypeTest extends TypeTestCase { - private $object; + private ?PriorityType $object = null; protected function setUp(): void { diff --git a/Tests/Form/Type/StatusTypeTest.php b/Tests/Form/Type/StatusTypeTest.php index ba009677..814016b6 100644 --- a/Tests/Form/Type/StatusTypeTest.php +++ b/Tests/Form/Type/StatusTypeTest.php @@ -18,7 +18,7 @@ class StatusTypeTest extends TypeTestCase { - private $object; + private ?StatusType $object = null; protected function setUp(): void { diff --git a/Tests/Form/Type/TicketMessageTypeTest.php b/Tests/Form/Type/TicketMessageTypeTest.php index 7fcfca94..3070720d 100644 --- a/Tests/Form/Type/TicketMessageTypeTest.php +++ b/Tests/Form/Type/TicketMessageTypeTest.php @@ -18,21 +18,22 @@ use Hackzilla\Bundle\TicketBundle\Manager\UserManagerInterface; use Hackzilla\Bundle\TicketBundle\Model\TicketMessageInterface; use Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity\TicketMessage; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Form\PreloadedExtension; use Symfony\Component\Form\Test\TypeTestCase; class TicketMessageTypeTest extends TypeTestCase { - private $user; + private MockObject $user; protected function setUp(): void { - $this->user = $this->getMockBuilder(UserManagerInterface::class)->getMock(); + $this->user = $this->createMock(UserManagerInterface::class); parent::setUp(); } - public function testSubmitValidData() + public function testSubmitValidData(): void { $formData = [ 'priority' => TicketMessageInterface::PRIORITY_HIGH, @@ -67,7 +68,7 @@ public function testSubmitValidData() } } - protected function getExtensions() + protected function getExtensions(): array { $ticketMessageType = new TicketMessageType($this->user, new TicketFeatures([], ''), TicketMessage::class); diff --git a/Tests/Form/Type/TicketTypeTest.php b/Tests/Form/Type/TicketTypeTest.php index 255fc444..958ea5af 100644 --- a/Tests/Form/Type/TicketTypeTest.php +++ b/Tests/Form/Type/TicketTypeTest.php @@ -19,12 +19,13 @@ use Hackzilla\Bundle\TicketBundle\Manager\UserManagerInterface; use Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity\Ticket; use Hackzilla\Bundle\TicketBundle\Tests\Fixtures\Entity\TicketMessage; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Form\PreloadedExtension; use Symfony\Component\Form\Test\TypeTestCase; class TicketTypeTest extends TypeTestCase { - private $user; + private MockObject $user; protected function setUp(): void { @@ -33,7 +34,7 @@ protected function setUp(): void parent::setUp(); } - public function testSubmitValidData() + public function testSubmitValidData(): void { $formData = []; @@ -62,7 +63,7 @@ public function testSubmitValidData() } } - protected function getExtensions() + protected function getExtensions(): array { $ticketType = new TicketType(Ticket::class); $ticketMessageType = new TicketMessageType($this->user, new TicketFeatures([], ''), TicketMessage::class); diff --git a/Tests/Functional/Command/ApplicationTest.php b/Tests/Functional/Command/ApplicationTest.php index ec5e536d..081d6f0d 100644 --- a/Tests/Functional/Command/ApplicationTest.php +++ b/Tests/Functional/Command/ApplicationTest.php @@ -25,22 +25,17 @@ final class ApplicationTest extends WebTestCase { /** * @dataProvider getCommands - * - * @param string $expectedClass - * @param string $commandName */ - public function testCommandRegistration($expectedClass, $commandName) + public function testCommandRegistration(string $expectedClass, string $commandName): void { - $application = new Application(static::$kernel); + $application = new Application(self::$kernel); $this->assertInstanceOf($expectedClass, $application->find($commandName)); } - public function getCommands() + public function getCommands(): \Iterator { - return [ - [AutoClosingCommand::class, 'ticket:autoclosing'], - [TicketManagerCommand::class, 'ticket:create'], - ]; + yield [AutoClosingCommand::class, 'ticket:autoclosing']; + yield [TicketManagerCommand::class, 'ticket:create']; } } diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index b8c0e00b..11046b12 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -29,30 +29,27 @@ class FunctionalTest extends WebTestCase /** * @dataProvider getParameters */ - public function testConfiguredParameter($parameter, $value): void + public function testConfiguredParameter(string $parameter, string|array $value): void { $this->assertTrue(static::$kernel->getContainer()->hasParameter($parameter)); $this->assertSame($value, static::$kernel->getContainer()->getParameter($parameter)); } - public function getParameters(): array + public function getParameters(): \Iterator { - $messageCLass = !class_exists(VichUploaderBundle::class) ? TicketMessage::class : TicketMessageWithAttachment::class; - - return [ - ['hackzilla_ticket.model.user.class', User::class], - ['hackzilla_ticket.model.ticket.class', Ticket::class], - ['hackzilla_ticket.model.message.class', $messageCLass], - ['hackzilla_ticket.features', ['attachment' => true]], - ['hackzilla_ticket.templates', [ - 'index' => '@HackzillaTicket/Ticket/index.html.twig', - 'new' => '@HackzillaTicket/Ticket/new.html.twig', - 'prototype' => '@HackzillaTicket/Ticket/prototype.html.twig', - 'show' => '@HackzillaTicket/Ticket/show.html.twig', - 'show_attachment' => '@HackzillaTicket/Ticket/show_attachment.html.twig', - 'macros' => '@HackzillaTicket/Macros/macros.html.twig', - ]], - ]; + $messageCLass = class_exists(VichUploaderBundle::class) ? TicketMessageWithAttachment::class : TicketMessage::class; + yield ['hackzilla_ticket.model.user.class', User::class]; + yield ['hackzilla_ticket.model.ticket.class', Ticket::class]; + yield ['hackzilla_ticket.model.message.class', $messageCLass]; + yield ['hackzilla_ticket.features', ['attachment' => true]]; + yield ['hackzilla_ticket.templates', [ + 'index' => '@HackzillaTicket/Ticket/index.html.twig', + 'new' => '@HackzillaTicket/Ticket/new.html.twig', + 'prototype' => '@HackzillaTicket/Ticket/prototype.html.twig', + 'show' => '@HackzillaTicket/Ticket/show.html.twig', + 'show_attachment' => '@HackzillaTicket/Ticket/show_attachment.html.twig', + 'macros' => '@HackzillaTicket/Macros/macros.html.twig', + ]]; } public function testConfiguredTicketManager(): void diff --git a/Tests/Functional/RoutingTest.php b/Tests/Functional/RoutingTest.php index 85b0cc3a..3a48326b 100644 --- a/Tests/Functional/RoutingTest.php +++ b/Tests/Functional/RoutingTest.php @@ -25,7 +25,7 @@ final class RoutingTest extends WebTestCase */ public function testRoutes(string $name, string $path, array $methods): void { - $client = static::createClient(); + $client = self::createClient(); $router = $client->getContainer()->get('router'); $route = $router->getRouteCollection()->get($name); diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index 02c623b2..14ff7b31 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -28,59 +28,29 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; -use Symfony\Component\Routing\RouteCollectionBuilder; use Vich\UploaderBundle\VichUploaderBundle; -if (\Symfony\Component\HttpKernel\Kernel::MAJOR_VERSION >= 5) { - trait ConfigureRoutes +trait ConfigureRoutes +{ + protected function configureRoutes(RoutingConfigurator $routes): void { - protected function configureRoutes(RoutingConfigurator $routes): void - { - $routes->import(__DIR__.'/routes.yaml', 'yaml'); - } + $routes->import(__DIR__.'/routes.yaml', 'yaml'); } +} - trait KernelDirectories - { - public function getCacheDir(): string - { - return $this->getBaseDir().'cache'; - } - - /** - * {@inheritdoc} - */ - public function getLogDir(): string - { - return $this->getBaseDir().'log'; - } - } -} else { - trait ConfigureRoutes +trait KernelDirectories +{ + public function getCacheDir(): string { - /** - * {@inheritdoc} - */ - protected function configureRoutes(RouteCollectionBuilder $routes) - { - $routes->import(__DIR__.'/routes.yaml', '/', 'yaml'); - } + return $this->getBaseDir().'cache'; } - trait KernelDirectories + /** + * {@inheritdoc} + */ + public function getLogDir(): string { - public function getCacheDir() - { - return $this->getBaseDir().'cache'; - } - - /** - * {@inheritdoc} - */ - public function getLogDir() - { - return $this->getBaseDir().'log'; - } + return $this->getBaseDir().'log'; } } @@ -96,7 +66,7 @@ final class TestKernel extends Kernel KernelDirectories::getLogDir insteadof MicroKernelTrait; } - private $useVichUploaderBundle = false; + private bool $useVichUploaderBundle; public function __construct() { @@ -127,10 +97,7 @@ public function registerBundles(): array return $bundles; } - /** - * {@inheritdoc} - */ - protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void { // FrameworkBundle config $frameworkConfig = [ @@ -152,12 +119,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'test' => true, ]; - if (version_compare(self::VERSION, '5.1', '>=') && version_compare(self::VERSION, '6.0', '<')) { - $frameworkConfig['session'] = ['storage_factory_id' => 'session.storage.factory.native']; - $frameworkConfig['router'] = ['utf8' => true]; - } - - $c->loadFromExtension('framework', $frameworkConfig); + $container->loadFromExtension('framework', $frameworkConfig); // SecurityBundle config $mainFirewallConfig = [ @@ -166,11 +128,6 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'provider' => 'in_memory', ], ]; - - // "logout_on_user_change" configuration was marked as mandatory since version 3.4 and deprecated as of 4.1. - if (version_compare(self::VERSION, '3.4', '>=') && version_compare(self::VERSION, '4.1', '<')) { - $mainFirewallConfig['logout_on_user_change'] = true; - } $securityConfig = [ 'providers' => [ 'in_memory' => [ @@ -182,14 +139,10 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load ], ]; - if (version_compare(self::VERSION, '5.3', '>=') && version_compare(self::VERSION, '7.0', '<')) { - $securityConfig['enable_authenticator_manager'] = true; - } - - $c->loadFromExtension('security', $securityConfig); + $container->loadFromExtension('security', $securityConfig); // DoctrineBundle config - $c->loadFromExtension('doctrine', [ + $container->loadFromExtension('doctrine', [ 'dbal' => [ 'connections' => [ 'default' => [ @@ -218,10 +171,8 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'autoescape' => 'name', ]; // "default_path" configuration is available since version 3.4. - if (version_compare(self::VERSION, '3.4', '>=')) { - $twigConfig['default_path'] = __DIR__.'/Resources/views'; - } - $c->loadFromExtension('twig', $twigConfig); + $twigConfig['default_path'] = __DIR__.'/Resources/views'; + $container->loadFromExtension('twig', $twigConfig); // HackzillaBundle config $bundleConfig = [ @@ -234,17 +185,17 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $bundleConfig['message_class'] = TicketMessageWithAttachment::class; } - $c->loadFromExtension('hackzilla_ticket', $bundleConfig); + $container->loadFromExtension('hackzilla_ticket', $bundleConfig); if ($this->useVichUploaderBundle) { // VichUploaderBundle config - $c->loadFromExtension('vich_uploader', [ + $container->loadFromExtension('vich_uploader', [ 'db_driver' => 'orm', ]); } } - private function getBaseDir() + private function getBaseDir(): string { return sys_get_temp_dir().'/hackzilla-ticket-bundle/var/'.(int) $this->useVichUploaderBundle.'/'; } diff --git a/Tests/Functional/WebTestCase.php b/Tests/Functional/WebTestCase.php index 2f846fcf..c5067aad 100644 --- a/Tests/Functional/WebTestCase.php +++ b/Tests/Functional/WebTestCase.php @@ -16,24 +16,11 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Component\HttpKernel\KernelInterface; -if (\Symfony\Component\HttpKernel\Kernel::MAJOR_VERSION >= 6) { - trait CreateKernel - { - protected static function createKernel(array $options = []): KernelInterface - { - return new TestKernel(); - } - } -} else { - trait CreateKernel +trait CreateKernel +{ + protected static function createKernel(array $options = []): KernelInterface { - /** - * @return KernelInterface - */ - protected static function createKernel(array $options = []) - { - return new TestKernel(); - } + return new TestKernel(); } } diff --git a/Tests/Manager/UserManagerTest.php b/Tests/Manager/UserManagerTest.php index 3fa06dcf..7b037325 100644 --- a/Tests/Manager/UserManagerTest.php +++ b/Tests/Manager/UserManagerTest.php @@ -24,17 +24,15 @@ class UserManagerTest extends WebTestCase { - private $object; - - private $tokenStorage; + private ?UserManager $object = null; protected function setUp(): void { self::bootKernel(); - $this->tokenStorage = new TokenStorage(); + $tokenStorage = new TokenStorage(); $this->object = new UserManager( - $this->tokenStorage, + $tokenStorage, $this->getMockUserRepository(), $this->getAuthorizationChecker(), ); @@ -57,7 +55,7 @@ private function getMockUserRepository(): EntityRepository return new EntityRepository($em, new ClassMetadata(User::class)); } - private function getAuthorizationChecker() + private function getAuthorizationChecker(): AuthorizationChecker { return $this->createMock(AuthorizationChecker::class); } diff --git a/Tests/TwigExtension/TicketFeaturesTest.php b/Tests/TwigExtension/TicketFeaturesTest.php index 4ed12946..2e7d96d7 100644 --- a/Tests/TwigExtension/TicketFeaturesTest.php +++ b/Tests/TwigExtension/TicketFeaturesTest.php @@ -19,7 +19,7 @@ class TicketFeaturesTest extends WebTestCase { - private $object; + private ?TicketFeatureExtension $object = null; protected function setUp(): void { diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 35202bcb..dff9154e 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -16,5 +16,5 @@ } elseif (is_file($autoloadFile = __DIR__.'/../vendor/autoload.php')) { $loader = require $autoloadFile; } else { - throw new \LogicException('Run "composer install --dev" to create autoloader.'); + throw new LogicException('Run "composer install --dev" to create autoloader.'); } diff --git a/TwigExtension/TicketFeatureExtension.php b/TwigExtension/TicketFeatureExtension.php index b78475ad..57f903a1 100644 --- a/TwigExtension/TicketFeatureExtension.php +++ b/TwigExtension/TicketFeatureExtension.php @@ -19,17 +19,14 @@ final class TicketFeatureExtension extends AbstractExtension { - private $ticketFeatures; - - public function __construct(TicketFeatures $ticketFeatures) + public function __construct(private readonly TicketFeatures $ticketFeatures) { - $this->ticketFeatures = $ticketFeatures; } public function getFunctions(): array { return [ - new TwigFunction('hasTicketFeature', [$this, 'hasFeature']), + new TwigFunction('hasTicketFeature', $this->hasFeature(...)), ]; } @@ -38,10 +35,7 @@ public function hasFeature(string $feature): ?bool return $this->ticketFeatures->hasFeature($feature); } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return 'ticketFeature'; } diff --git a/TwigExtension/TicketGlobalExtension.php b/TwigExtension/TicketGlobalExtension.php index b8b5a378..6833f8da 100644 --- a/TwigExtension/TicketGlobalExtension.php +++ b/TwigExtension/TicketGlobalExtension.php @@ -18,11 +18,8 @@ final class TicketGlobalExtension extends AbstractExtension implements GlobalsInterface { - protected $templates = []; - - public function __construct(array $templates) + public function __construct(protected array $templates) { - $this->templates = $templates; } public function getGlobals(): array @@ -41,10 +38,7 @@ public function getGlobals(): array ]; } - /** - * @return string - */ - public function getName() + public function getName(): string { return 'ticketGlobal'; } diff --git a/composer.json b/composer.json index 4939121d..63e8967b 100644 --- a/composer.json +++ b/composer.json @@ -25,25 +25,26 @@ } ], "require": { - "php": "^8.0", - "doctrine/doctrine-bundle": "^2.0", - "doctrine/persistence": "^1.3 || ^2.1", - "knplabs/knp-paginator-bundle": "^4.4 || ^5.1", - "symfony/asset": "^4.4 || ^5.1 || ^6.0", - "symfony/config": "^4.4 || ^5.1 || ^6.0", - "symfony/console": "^4.4 || ^5.1 || ^6.0", - "symfony/dependency-injection": "^4.1 || ^5.0 || ^6.0", - "symfony/event-dispatcher": "^4.4 || ^5.1 || ^6.0", - "symfony/form": "^4.4 || ^5.1 || ^6.0", - "symfony/framework-bundle": "^4.4 || ^5.1 || ^6.0", - "symfony/http-foundation": "^4.4 || ^5.1 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.1 || ^6.0", - "symfony/options-resolver": "^4.4 || ^5.1 || ^6.0", - "symfony/security-bundle": "^4.4 || ^5.1 || ^6.0", - "symfony/translation": "^4.4 || ^5.1 || ^6.0", - "symfony/twig-bundle": "^4.4 || ^5.1 || ^6.0", - "symfony/validator": "^4.4 || ^5.1 || ^6.0", - "symfony/yaml": "^4.4 || ^5.1 || ^6.0", + "php": "^8.1", + "doctrine/doctrine-bundle": ">=2.11", + "doctrine/orm": "^3.3", + "doctrine/persistence": "^3.1", + "knplabs/knp-paginator-bundle": "^6.6", + "symfony/asset": "^7.0", + "symfony/config": "^7.0", + "symfony/console": "^7.0", + "symfony/dependency-injection": "^7.0", + "symfony/event-dispatcher": "^7.0", + "symfony/form": "^7.0", + "symfony/framework-bundle": "^7.0", + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/options-resolver": "^7.0", + "symfony/security-bundle": "^7.0", + "symfony/translation": "^7.0", + "symfony/twig-bundle": "^7.0", + "symfony/validator": "^7.0", + "symfony/yaml": "^7.0", "twig/twig": "^2.9 || ^3.0" }, "require-dev": { @@ -51,7 +52,9 @@ "friendsofphp/php-cs-fixer": "^3.0", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^9.5", - "symfony/phpunit-bridge": "^4.4 || ^5.1 || ^6.0", + "rector/rector": "^1.2", + "symfony/maker-bundle": "^1.61", + "symfony/phpunit-bridge": "^7.0", "symfony/test-pack": "^1.0" }, "suggest": { diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..6cece1e3 --- /dev/null +++ b/rector.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Rector\Config\RectorConfig; +use Rector\Doctrine\Set\DoctrineSetList; +use Rector\PHPUnit\Set\PHPUnitSetList; +use Rector\Set\ValueObject\LevelSetList; +use Rector\Set\ValueObject\SetList; +use Rector\Symfony\Set\SymfonySetList; + +return RectorConfig::configure() + ->withPaths([__DIR__]) + ->withSkip([__DIR__.'/vendor']) + // uncomment to reach your current PHP version +// ->withTypeCoverageLevel(0) + ->withPhpSets(php83: true) + ->withPreparedSets(deadCode: true, codeQuality: true, typeDeclarations: true, doctrineCodeQuality: true, symfonyCodeQuality: true, symfonyConfigs: true, twig: true, phpunit: true) + ->withImportNames(importNames: true, removeUnusedImports: true) + ->withSets([ + LevelSetList::UP_TO_PHP_74, + DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, + SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, + SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, + SetList::TYPE_DECLARATION, + PHPUnitSetList::PHPUNIT_CODE_QUALITY, + ]) +;