Improved typing and phpdoc type annotations

This commit is contained in:
Jan Böhmer 2023-06-18 15:37:42 +02:00
parent 3817ba774d
commit b7c8ca2a48
39 changed files with 189 additions and 129 deletions

View file

@ -63,7 +63,7 @@ class CheckRequirementsCommand extends Command
}
protected function checkPHP(SymfonyStyle $io, $only_issues = false): void
protected function checkPHP(SymfonyStyle $io, bool $only_issues = false): void
{
//Check PHP versions
if ($io->isVerbose()) {
@ -98,7 +98,7 @@ class CheckRequirementsCommand extends Command
}
}
protected function checkPartDBConfig(SymfonyStyle $io, $only_issues = false): void
protected function checkPartDBConfig(SymfonyStyle $io, bool $only_issues = false): void
{
//Check if APP_ENV is set to prod
if ($io->isVerbose()) {
@ -112,7 +112,7 @@ class CheckRequirementsCommand extends Command
}
protected function checkPHPExtensions(SymfonyStyle $io, $only_issues = false): void
protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void
{
//Get all installed PHP extensions
$extensions = get_loaded_extensions();

View file

@ -244,7 +244,7 @@ abstract class BaseAdminController extends AbstractController
return true;
}
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null): Response
{
$new_entity = $entity instanceof AbstractNamedDBElement ? clone $entity : new $this->entity_class();

View file

@ -44,11 +44,18 @@ class HomepageController extends AbstractController
public function getBanner(): string
{
$banner = $this->getParameter('partdb.banner');
if (!is_string($banner)) {
throw new \RuntimeException('The parameter "partdb.banner" must be a string.');
}
if (empty($banner)) {
$banner_path = $this->kernel->getProjectDir()
.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md';
return file_get_contents($banner_path);
$tmp = file_get_contents($banner_path);
if (false === $tmp) {
throw new \RuntimeException('The banner file could not be read.');
}
$banner = $tmp;
}
return $banner;

View file

@ -66,7 +66,7 @@ class UserSettingsController extends AbstractController
}
#[Route(path: '/2fa_backup_codes', name: 'show_backup_codes')]
public function showBackupCodes()
public function showBackupCodes(): Response
{
$user = $this->getUser();
@ -74,7 +74,7 @@ class UserSettingsController extends AbstractController
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$user instanceof User) {
return new RuntimeException('This controller only works only for Part-DB User objects!');
throw new RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($user->isSamlUser()) {

View file

@ -29,6 +29,7 @@ use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use function Symfony\Component\Translation\t;
@ -40,7 +41,7 @@ class WebauthnKeyRegistrationController extends AbstractController
}
#[Route(path: '/webauthn/register', name: 'webauthn_register')]
public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em)
public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em): Response
{
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

View file

@ -48,7 +48,7 @@ class ErrorDataTable implements DataTableTypeInterface
});
}
public function configure(DataTable $dataTable, array $options)
public function configure(DataTable $dataTable, array $options): void
{
$optionsResolver = new OptionsResolver();
$this->configureOptions($optionsResolver);
@ -74,7 +74,10 @@ class ErrorDataTable implements DataTableTypeInterface
$dataTable->createAdapter(ArrayAdapter::class, $data);
}
public static function errorTable(DataTableFactory $dataTableFactory, Request $request, $errors): Response
/**
* @param string[]|string $errors
*/
public static function errorTable(DataTableFactory $dataTableFactory, Request $request, array|string $errors): Response
{
$error_table = $dataTableFactory->createFromType(self::class, ['errors' => $errors]);
$error_table->handleRequest($request);

View file

@ -49,7 +49,7 @@ class NumberConstraint extends AbstractConstraint
$this->value2 = $value2;
}
public function getOperator(): string
public function getOperator(): string|null
{
return $this->operator;
}

View file

@ -47,7 +47,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
}
public function configure(DataTable $dataTable, array $options)
public function configure(DataTable $dataTable, array $options): void
{
$dataTable
//->add('select', SelectColumn::class)

View file

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace App\Doctrine\Types;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
@ -58,7 +59,16 @@ class UTCDateTimeType extends DateTimeType
return parent::convertToDatabaseValue($value, $platform);
}
public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime
/**
* {@inheritDoc}
*
* @param T $value
*
* @return (T is null ? null : DateTimeInterface)
*
* @template T
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeInterface
{
if (!self::$utc_timezone instanceof \DateTimeZone) {
self::$utc_timezone = new DateTimeZone('UTC');

View file

@ -80,7 +80,7 @@ abstract class AttachmentContainingDBElement extends AbstractNamedDBElement impl
/**
* Gets all attachments associated with this element.
*
* @return Collection<Attachment>
* @phpstan-return Collection<int, AT>
*/
public function getAttachments(): Collection
{

View file

@ -91,8 +91,9 @@ class AttachmentType extends AbstractStructuralDBElement
/**
* Get all attachments ("Attachment" objects) with this type.
*
* @return Collection|Attachment[] all attachments with this type, as a one-dimensional array of Attachments
* @return Collection all attachments with this type, as a one-dimensional array of Attachments
* (sorted by their names)
* @phpstan-return Collection<int, Attachment>
*/
public function getAttachmentsForType(): Collection
{

View file

@ -249,7 +249,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
/**
* Returns the timestamp when the event that caused this log entry happened.
*/
public function getTimestamp(): \DateTimeInterface
public function getTimestamp(): \DateTimeInterface|null
{
return $this->timestamp;
}

View file

@ -144,7 +144,7 @@ class Part extends AttachmentContainingDBElement
}
#[Assert\Callback]
public function validate(ExecutionContextInterface $context, $payload)
public function validate(ExecutionContextInterface $context, $payload): void
{
//Ensure that the part name fullfills the regex of the category
if ($this->category instanceof Category) {

View file

@ -322,7 +322,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
}
#[Assert\Callback]
public function validate(ExecutionContextInterface $context, $payload)
public function validate(ExecutionContextInterface $context, $payload): void
{
//Ensure that the owner is not the anonymous user
if ($this->getOwner() && $this->getOwner()->isAnonymousUser()) {

View file

@ -63,8 +63,7 @@ trait InstockTrait
/**
* Get all part lots where this part is stored.
*
* @return PartLot[]|Collection
* @phpstan-return Collection<int, PartLot>
*/
public function getPartLots(): Collection
{

View file

@ -36,7 +36,7 @@ use Doctrine\ORM\Mapping as ORM;
trait OrderTrait
{
/**
* @var Orderdetail[]|Collection the details about how and where you can order this part
* @var Collection<int, Orderdetail> the details about how and where you can order this part
*/
#[Assert\Valid]
#[Groups(['extended', 'full', 'import'])]
@ -66,7 +66,7 @@ trait OrderTrait
/**
* Get the selected order orderdetails of this part.
*
* @return Orderdetail the selected order orderdetails
* @return Orderdetail|null the selected order orderdetails
*/
public function getOrderOrderdetails(): ?Orderdetail
{

View file

@ -30,7 +30,8 @@ trait ProjectTrait
/**
* Returns all ProjectBOMEntries that use this part.
* @return Collection<int, ProjectBOMEntry>|ProjectBOMEntry[]
*
* @phpstan-return Collection<int, ProjectBOMEntry>
*/
public function getProjectBomEntries(): Collection
{

View file

@ -205,8 +205,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
/**
* Get all pricedetails.
*
* @return Pricedetail[]|Collection all pricedetails as a one-dimensional array of Pricedetails objects,
* sorted by minimum discount quantity
* @return Collection<int, Pricedetail>
*/
public function getPricedetails(): Collection
{

View file

@ -183,9 +183,6 @@ class Project extends AbstractStructuralDBElement
return $this;
}
/**
* @return Collection<int, ProjectBOMEntry>|ProjectBOMEntry[]
*/
public function getBomEntries(): Collection
{
return $this->bom_entries;
@ -265,7 +262,7 @@ class Project extends AbstractStructuralDBElement
}
#[Assert\Callback]
public function validate(ExecutionContextInterface $context, $payload)
public function validate(ExecutionContextInterface $context, $payload): void
{
//If this project has subprojects, and these have builds part, they must be included in the BOM
foreach ($this->getChildren() as $child) {

View file

@ -141,7 +141,7 @@ class U2FKey implements LegacyU2FKeyInterface
/**
* Gets the user, this U2F key belongs to.
*/
public function getUser(): User
public function getUser(): User|null
{
return $this->user;
}

View file

@ -470,7 +470,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
/**
* Gets the datetime when the password reset token expires.
*/
public function getPwResetExpires(): \DateTimeInterface
public function getPwResetExpires(): \DateTimeInterface|null
{
return $this->pw_reset_expires;
}

View file

@ -162,9 +162,11 @@ class CollectionTypeExtension extends AbstractTypeExtension
/**
* Set the option of the form.
* This a bit hacky because we access private properties....
*
* @param FormConfigInterface $builder The form on which the option should be set
* @param string $option The option which should be changed
* @param mixed $value The new value
*/
public function setOption(FormConfigInterface $builder, string $option, $value): void
public function setOption(FormConfigInterface $builder, string $option, mixed $value): void
{
if (!$builder instanceof FormConfigBuilder) {
throw new \RuntimeException('This method only works with FormConfigBuilder instances.');
@ -173,10 +175,8 @@ class CollectionTypeExtension extends AbstractTypeExtension
//We have to use FormConfigBuilder::class here, because options is private and not available in subclasses
$reflection = new ReflectionClass(FormConfigBuilder::class);
$property = $reflection->getProperty('options');
$property->setAccessible(true);
$tmp = $property->getValue($builder);
$tmp[$option] = $value;
$property->setValue($builder, $tmp);
$property->setAccessible(false);
}
}

View file

@ -45,7 +45,7 @@ final class PermissionsMapper implements DataMapperInterface
* on the children of compound forms, defining their underlying model data.
*
* @param mixed $viewData View data of the compound form being initialized
* @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances
* @param Traversable $forms A list of {@link FormInterface} instances
*/
public function mapDataToForms($viewData, \Traversable $forms): void
{
@ -90,7 +90,7 @@ final class PermissionsMapper implements DataMapperInterface
* The model data can be an array or an object, so this second argument is always passed
* by reference.
*
* @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances
* @param Traversable $forms A list of {@link FormInterface} instances
* @param mixed $viewData The compound form's view data that get mapped
* its children model data
*/

View file

@ -149,7 +149,7 @@ final class SIUnitType extends AbstractType implements DataMapperInterface
* on the children of compound forms, defining their underlying model data.
*
* @param mixed $viewData View data of the compound form being initialized
* @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances
* @param Traversable $forms A list of {@link FormInterface} instances
*
* @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported
*/
@ -198,7 +198,7 @@ final class SIUnitType extends AbstractType implements DataMapperInterface
* The model data can be an array or an object, so this second argument is always passed
* by reference.
*
* @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances
* @param Traversable $forms A list of {@link FormInterface} instances
* @param mixed $viewData The compound form's view data that get mapped
* its children model data
*

View file

@ -45,7 +45,10 @@ class BigNumberNormalizer implements NormalizerInterface
return (string) $object;
}
public function getSupportedTypes(?string $format)
/**
* @return bool[]
*/
public function getSupportedTypes(?string $format): array
{
return [
BigNumber::class => true,

View file

@ -68,7 +68,12 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface
return $data instanceof Part;
}
public function normalize($object, string $format = null, array $context = []): array
/**
* @return (float|mixed)[]|\ArrayObject|null|scalar
*
* @psalm-return \ArrayObject|array{total_instock: float|mixed,...}|null|scalar
*/
public function normalize($object, string $format = null, array $context = [])
{
if (!$object instanceof Part) {
throw new \InvalidArgumentException('This normalizer only supports Part objects!');
@ -178,7 +183,10 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface
return $object;
}
public function getSupportedTypes(?string $format)
/**
* @return bool[]
*/
public function getSupportedTypes(?string $format): array
{
//Must be false, because we rely on is_array($data) in supportsDenormalization()
return [

View file

@ -42,7 +42,12 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface
return is_string($data) && is_subclass_of($type, AbstractStructuralDBElement::class);
}
public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement
/**
* @template T
* @phpstan-param class-string<T> $type
* @phpstan-return T|null
*/
public function denormalize($data, string $type, string $format = null, array $context = []): AbstractStructuralDBElement|null
{
//Retrieve the repository for the given type
/** @var StructuralDBElementRepository $repo */
@ -69,7 +74,10 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface
return end($elements);
}
public function getSupportedTypes(?string $format)
/**
* @return bool[]
*/
public function getSupportedTypes(?string $format): array
{
//Cachable value Must be false, because we do an is_string check on data in supportsDenormalization
return [

View file

@ -46,7 +46,10 @@ class StructuralElementNormalizer implements NormalizerInterface
return $data instanceof AbstractStructuralDBElement;
}
public function normalize($object, string $format = null, array $context = []): array
/**
* @return array<string, mixed>
*/
public function normalize($object, string $format = null, array $context = [])
{
if (!$object instanceof AbstractStructuralDBElement) {
throw new \InvalidArgumentException('This normalizer only supports AbstractStructural objects!');
@ -64,7 +67,10 @@ class StructuralElementNormalizer implements NormalizerInterface
return $data;
}
public function getSupportedTypes(?string $format)
/**
* @return bool[]
*/
public function getSupportedTypes(?string $format): array
{
return [
AbstractStructuralDBElement::class => true,

View file

@ -33,8 +33,8 @@ use Symfony\Component\Filesystem\Filesystem;
*/
class AttachmentPathResolver
{
protected ?string $media_path;
protected ?string $footprints_path;
protected string $media_path;
protected string $footprints_path;
protected ?string $models_path;
protected ?string $secure_path;
@ -54,11 +54,11 @@ class AttachmentPathResolver
*/
public function __construct(protected string $project_dir, string $media_path, string $secure_path, ?string $footprints_path, ?string $models_path)
{
//Determine the path for our ressources
$this->media_path = $this->parameterToAbsolutePath($media_path);
//Determine the path for our resources
$this->media_path = $this->parameterToAbsolutePath($media_path) ?? throw new \InvalidArgumentException('The media path must be set and valid!');
$this->secure_path = $this->parameterToAbsolutePath($secure_path) ?? throw new \InvalidArgumentException('The secure path must be set and valid!');
$this->footprints_path = $this->parameterToAbsolutePath($footprints_path) ;
$this->models_path = $this->parameterToAbsolutePath($models_path);
$this->secure_path = $this->parameterToAbsolutePath($secure_path);
$this->pathes = [$this->media_path, $this->media_path, $this->footprints_path, $this->models_path, $this->secure_path];
//Remove all disabled placeholders
@ -215,7 +215,7 @@ class AttachmentPathResolver
/**
* The string where the builtin footprints are stored.
*
* @return string|null The absolute path to the footprints folder. Null if built footprints were disabled.
* @return string|null The absolute path to the footprints' folder. Null if built footprints were disabled.
*/
public function getFootprintsPath(): ?string
{
@ -225,7 +225,7 @@ class AttachmentPathResolver
/**
* The string where the builtin 3D models are stored.
*
* @return string|null The absolute path to the models folder. Null if builtin models were disabled.
* @return string|null The absolute path to the models' folder. Null if builtin models were disabled.
*/
public function getModelsPath(): ?string
{

View file

@ -159,7 +159,7 @@ class EntityURLGenerator
public function viewURL(Attachment $entity): string
{
if ($entity->isExternal()) { //For external attachments, return the link to external path
return $entity->getURL();
return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!');
}
//return $this->urlGenerator->generate('attachment_view', ['id' => $entity->getID()]);
return $this->attachmentURLGenerator->getViewURL($entity) ?? '';
@ -169,7 +169,7 @@ class EntityURLGenerator
{
if ($entity instanceof Attachment) {
if ($entity->isExternal()) { //For external attachments, return the link to external path
return $entity->getURL();
return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!');
}
return $this->attachmentURLGenerator->getDownloadURL($entity);

View file

@ -61,6 +61,8 @@ final class LabelGenerator
/**
* @param object|object[] $elements An element or an array of elements for which labels should be generated
*
* @return null|string
*/
public function generateLabel(LabelOptions $options, object|array $elements): string
{
@ -83,7 +85,7 @@ final class LabelGenerator
$dompdf->loadHtml($this->labelHTMLGenerator->getLabelHTML($options, $elements));
$dompdf->render();
return $dompdf->output();
return $dompdf->output() ?? throw new \RuntimeException('Could not generate label!');
}
/**

View file

@ -80,16 +80,17 @@ final class LabelTextReplacer
* Replaces all placeholders in the input lines.
*
* @param string $lines The input lines that should be replaced
* @param object $target the object that should be used as source for the informations
* @param object $target the object that should be used as source for the information
*
* @return string the Lines with replaced informations
* @return string the Lines with replaced information
*/
public function replace(string $lines, object $target): string
{
$patterns = [
'/(\[\[[A-Z_0-9]+\]\])/' => fn($match) => $this->handlePlaceholder($match[0], $target),
'/(\[\[[A-Z_0-9]+\]\])/' => fn($match): string => $this->handlePlaceholder($match[0], $target),
];
return preg_replace_callback_array($patterns, $lines);
return preg_replace_callback_array($patterns, $lines) ?? throw new \RuntimeException('Could not replace placeholders!');
}
}

View file

@ -58,7 +58,7 @@ class HistoryHelper
* Returns an array containing all elements that are associated with the argument.
* The returned array contains the given element.
*
* @psalm-return array<AbstractParameter|array-key, mixed>
* @return AbstractDBElement[]
*/
public function getAssociatedElements(AbstractDBElement $element): array
{

View file

@ -57,7 +57,11 @@ class LogDiffFormatter
]);
}
private function diffNumeric($old_data, $new_data): string
/**
* @param numeric $old_data
* @param numeric $new_data
*/
private function diffNumeric(int|float|string $old_data, int|float|string $new_data): string
{
if ((!is_numeric($old_data)) || (!is_numeric($new_data))) {
throw new \InvalidArgumentException('The given data is not numeric.');

View file

@ -232,16 +232,17 @@ class TimeTravel
{
$reflection = new ReflectionClass($element::class);
$property = $reflection->getProperty($field);
$property->setAccessible(true);
return $property->getValue($element);
}
/**
* @param int|null|object $new_value
*/
protected function setField(AbstractDBElement $element, string $field, mixed $new_value): void
{
$reflection = new ReflectionClass($element::class);
$property = $reflection->getProperty($field);
$property->setAccessible(true);
$property->setValue($element, $new_value);
}

View file

@ -178,7 +178,7 @@ implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPart
*
* @throws AccessDeniedException
*/
private function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.'): void
private function denyAccessUnlessGranted(mixed $attributes, mixed $subject = null, string $message = 'Access Denied.'): void
{
if (!$this->security->isGranted($attributes, $subject)) {
$exception = new AccessDeniedException($message);

View file

@ -70,8 +70,12 @@ class NodesListBuilder
* The value is cached for performance reasons.
*
* @template T of AbstractStructuralDBElement
*
* @param T $element
* @return T[]
*
* @return AbstractStructuralDBElement[]
*
* @phpstan-return list<T>
*/
public function getChildrenFlatList(AbstractStructuralDBElement $element): array
{

View file

@ -43,12 +43,15 @@ class UserAvatarHelper
/**
* Returns the URL to the profile picture of the given user (in big size)
*
* @return string
*/
public function getAvatarURL(User $user): string
{
//Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture)
if ($user->getMasterPictureAttachment() instanceof Attachment) {
return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_md');
return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_md')
?? throw new RuntimeException('Could not generate thumbnail URL');
}
//If not check if gravatar is enabled (then use gravatar URL)
@ -64,7 +67,8 @@ class UserAvatarHelper
{
//Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture)
if ($user->getMasterPictureAttachment() instanceof Attachment) {
return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_xs');
return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_xs')
?? throw new RuntimeException('Could not generate thumbnail URL');;
}
//If not check if gravatar is enabled (then use gravatar URL)
@ -85,7 +89,8 @@ class UserAvatarHelper
{
//Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture)
if ($user->getMasterPictureAttachment() instanceof Attachment) {
return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_sm');
return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_sm')
?? throw new RuntimeException('Could not generate thumbnail URL');
}
//If not check if gravatar is enabled (then use gravatar URL)

View file

@ -58,6 +58,10 @@ final class TwigCoreExtension extends AbstractExtension
];
}
/**
* @param string $enum_class
* @phpstan-param class-string $enum_class
*/
public function getEnumCases(string $enum_class): array
{
if (!enum_exists($enum_class)) {
@ -75,12 +79,8 @@ final class TwigCoreExtension extends AbstractExtension
];
}
public function toArray($object)
public function toArray(object|array $object): array
{
if(! is_object($object) && ! is_array($object)) {
throw new \InvalidArgumentException('The given variable is not an object or array!');
}
//If it is already an array, we can just return it
if(is_array($object)) {
return $object;