diff --git a/site/app/Admin/Presenters/TrainingsPresenter.php b/site/app/Admin/Presenters/TrainingsPresenter.php index 39ccaf681..99672608c 100644 --- a/site/app/Admin/Presenters/TrainingsPresenter.php +++ b/site/app/Admin/Presenters/TrainingsPresenter.php @@ -43,7 +43,7 @@ class TrainingsPresenter extends BasePresenter /** @var list */ private array $applications = []; - /** @var int[] */ + /** @var list */ private array $applicationIdsAllowedFiles = []; private ?TrainingApplication $application = null; diff --git a/site/app/Application/Bootstrap.php b/site/app/Application/Bootstrap.php index 67bc50d30..7644cac36 100644 --- a/site/app/Application/Bootstrap.php +++ b/site/app/Application/Bootstrap.php @@ -25,7 +25,7 @@ public static function boot(): Container { return self::createConfigurator( ServerEnv::tryGetString('ENVIRONMENT') === self::MODE_DEVELOPMENT, - self::SITE_DIR . '/config/extra-' . ServerEnv::tryGetString('SERVER_NAME') . '.neon', + self::SITE_DIR . '/config/extra-' . ServerEnv::getString('SERVER_NAME') . '.neon', )->createContainer(); } @@ -112,12 +112,13 @@ private static function getCliArgs(string $argsProvider): CliArgs $args[] = self::DEBUG; $args[] = self::COLORS; $cliArgsParser = new Parser("\n " . implode("\n ", $args)); + $cliArgsError = null; try { $cliArgsParsed = $cliArgsParser->parse(); } catch (Exception $e) { $cliArgsError = $e->getMessage(); } - return new CliArgs($cliArgsParsed ?? [], $cliArgsError ?? null); + return new CliArgs($cliArgsParsed ?? [], $cliArgsError); } } diff --git a/site/app/Application/Error.php b/site/app/Application/Error.php index cf5816fd6..8a8030fde 100644 --- a/site/app/Application/Error.php +++ b/site/app/Application/Error.php @@ -3,6 +3,7 @@ namespace MichalSpacekCz\Application; +use MichalSpacekCz\ShouldNotHappenException; use Nette\Application\BadRequestException; use Nette\Application\Helpers; use Nette\Application\Request; @@ -27,6 +28,12 @@ public function response(Request $request): Response if ($e instanceof BadRequestException) { [$module, , $sep] = Helpers::splitName($request->getPresenterName()); + if (!is_string($module)) { + throw new ShouldNotHappenException(sprintf('Module should be a string, %s provided', get_debug_type($module))); + } + if (!is_string($sep)) { + throw new ShouldNotHappenException(sprintf('Separator should be a string, %s provided', get_debug_type($sep))); + } return new ForwardResponse($request->setPresenterName($module . $sep . 'Error')); } diff --git a/site/app/Application/Locale/Locales.php b/site/app/Application/Locale/Locales.php index b0b78a4c1..d54e79394 100644 --- a/site/app/Application/Locale/Locales.php +++ b/site/app/Application/Locale/Locales.php @@ -3,7 +3,7 @@ namespace MichalSpacekCz\Application\Locale; -use Nette\Database\Explorer; +use MichalSpacekCz\Database\TypedDatabase; class Locales { @@ -13,7 +13,7 @@ class Locales public function __construct( - private readonly Explorer $database, + private readonly TypedDatabase $database, ) { } @@ -24,7 +24,7 @@ public function __construct( public function getAllLocales(): array { if ($this->locales === null) { - $this->locales = $this->database->fetchPairs('SELECT id_locale, locale FROM locales ORDER BY id_locale'); + $this->locales = $this->database->fetchPairsIntString('SELECT id_locale, locale FROM locales ORDER BY id_locale'); } return $this->locales; } diff --git a/site/app/Articles/Articles.php b/site/app/Articles/Articles.php index ac3d762c1..68334e471 100644 --- a/site/app/Articles/Articles.php +++ b/site/app/Articles/Articles.php @@ -8,9 +8,9 @@ use DateTime; use MichalSpacekCz\Articles\Blog\BlogPost; use MichalSpacekCz\Articles\Blog\BlogPostFactory; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\DateTime\Exceptions\InvalidTimezoneException; use MichalSpacekCz\Formatter\TexyFormatter; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Tags\Tags; use MichalSpacekCz\Utils\Exceptions\JsonItemNotStringException; use MichalSpacekCz\Utils\Exceptions\JsonItemsNotArrayException; @@ -24,6 +24,7 @@ class Articles public function __construct( private readonly Explorer $database, + private readonly TypedDatabase $typedDatabase, private readonly TexyFormatter $texyFormatter, private readonly BlogPostFactory $blogPostFactory, private readonly Tags $tags, @@ -235,11 +236,9 @@ public function getNearestPublishDate(): ?DateTime ORDER BY date LIMIT 1'; $now = new DateTime(); - $result = $this->database->fetchField($query, $now, $now, $this->translator->getDefaultLocale()); + $result = $this->typedDatabase->fetchFieldDateTimeNullable($query, $now, $now, $this->translator->getDefaultLocale()); if (!$result) { return null; - } elseif (!$result instanceof DateTime) { - throw new ShouldNotHappenException(sprintf("Nearest published date is a %s not a DateTime object", get_debug_type($result))); } return $result; } @@ -260,11 +259,9 @@ public function getNearestPublishDateByTags(array $tags): ?DateTime AND l.locale = ? ORDER BY bp.published LIMIT 1'; - $result = $this->database->fetchField($query, $this->tags->serialize($tags), new DateTime(), $this->translator->getDefaultLocale()); + $result = $this->typedDatabase->fetchFieldDateTimeNullable($query, $this->tags->serialize($tags), new DateTime(), $this->translator->getDefaultLocale()); if (!$result) { return null; - } elseif (!$result instanceof DateTime) { - throw new ShouldNotHappenException(sprintf("Nearest published date is a %s not a DateTime object", get_debug_type($result))); } return $result; } diff --git a/site/app/CompanyInfo/CompanyRegisterRegisterUz.php b/site/app/CompanyInfo/CompanyRegisterRegisterUz.php index 9045c774a..9273b418b 100644 --- a/site/app/CompanyInfo/CompanyRegisterRegisterUz.php +++ b/site/app/CompanyInfo/CompanyRegisterRegisterUz.php @@ -60,7 +60,7 @@ public function getDetails(string $companyId): CompanyInfoDetails IResponse::S200_OK, 'OK', $unit->ico, - (isset($unit->dic) ? strtoupper(self::COUNTRY_CODE) . $unit->dic : ''), + isset($unit->dic) && is_string($unit->dic) ? strtoupper(self::COUNTRY_CODE) . $unit->dic : '', $unit->nazovUJ, $unit->ulica, $unit->mesto, diff --git a/site/app/Database/Exceptions/TypedDatabaseException.php b/site/app/Database/Exceptions/TypedDatabaseException.php new file mode 100644 index 000000000..fc13de930 --- /dev/null +++ b/site/app/Database/Exceptions/TypedDatabaseException.php @@ -0,0 +1,10 @@ + $params + * @return array + */ + public function fetchPairsStringString(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array + { + $values = []; + /** + * @var mixed $value + */ + foreach ($this->database->fetchPairs($sql, ...$params) as $key => $value) { + if (!is_string($key)) { + throw new TypedDatabaseTypeException('string', $key); + } elseif (!is_string($value)) { + throw new TypedDatabaseTypeException('string', $value); + } + $values[$key] = $value; + } + return $values; + } + + + /** + * @param literal-string $sql + * @param array $params + * @return array + */ + public function fetchPairsIntString(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array + { + $values = []; + /** + * @var mixed $value + */ + foreach ($this->database->fetchPairs($sql, ...$params) as $key => $value) { + if (!is_int($key)) { + throw new TypedDatabaseTypeException('int', $key); + } elseif (!is_string($value)) { + throw new TypedDatabaseTypeException('string', $value); + } + $values[$key] = $value; + } + return $values; + } + + + /** + * @param literal-string $sql + * @param array $params + * @return list + */ + public function fetchPairsListDateTime(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): array + { + $values = []; + foreach ($this->database->fetchPairs($sql, ...$params) as $value) { + if (!$value instanceof DateTime) { + throw new TypedDatabaseTypeException(DateTime::class, $value); + } + $values[] = $value; + } + return $values; + } + + + /** + * @param literal-string $sql + * @param array $params + */ + public function fetchFieldString(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): string + { + $field = $this->database->fetchField($sql, ...$params); + if (!is_string($field)) { + throw new TypedDatabaseTypeException('string', $field); + } + return $field; + } + + + /** + * @param literal-string $sql + * @param array $params + */ + public function fetchFieldStringNullable(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?string + { + $field = $this->database->fetchField($sql, ...$params); + if (!is_string($field) && !is_null($field)) { + throw new TypedDatabaseTypeException('string|null', $field); + } + return $field; + } + + + /** + * @param literal-string $sql + * @param array $params + */ + public function fetchFieldInt(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): int + { + $field = $this->database->fetchField($sql, ...$params); + if (!is_int($field)) { + throw new TypedDatabaseTypeException('int', $field); + } + return $field; + } + + + /** + * @param literal-string $sql + * @param array $params + */ + public function fetchFieldIntNullable(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?int + { + $field = $this->database->fetchField($sql, ...$params); + if (!is_int($field) && !is_null($field)) { + throw new TypedDatabaseTypeException('int|null', $field); + } + return $field; + } + + + /** + * @param literal-string $sql + * @param array $params + */ + public function fetchFieldDateTime(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): DateTime + { + $field = $this->database->fetchField($sql, ...$params); + if (!$field instanceof DateTime) { + throw new TypedDatabaseTypeException(DateTime::class, $field); + } + return $field; + } + + + /** + * @param literal-string $sql + * @param array $params + */ + public function fetchFieldDateTimeNullable(#[Language('SQL')] string $sql, #[Language('GenericSQL')] ...$params): ?DateTime + { + $field = $this->database->fetchField($sql, ...$params); + if (!$field instanceof DateTime && !is_null($field)) { + throw new TypedDatabaseTypeException(DateTime::class . '|null', $field); + } + return $field; + } + +} diff --git a/site/app/DateTime/DateTime.php b/site/app/DateTime/DateTime.php index 4b07e2a57..fac137c83 100644 --- a/site/app/DateTime/DateTime.php +++ b/site/app/DateTime/DateTime.php @@ -15,6 +15,11 @@ class DateTime */ public const string DATE_RFC3339_MICROSECONDS = 'Y-m-d\TH:i:s.uP'; + /** + * Same as in \Nette\Database\Drivers\MySqlDriver::formatDateTime() but without the quotes. + */ + public const string DATE_MYSQL = 'Y-m-d H:i:s'; + public function getDaysFromString(string $interval): int { diff --git a/site/app/Form/TrainingFileFormFactory.php b/site/app/Form/TrainingFileFormFactory.php index f4ef3170a..39f030955 100644 --- a/site/app/Form/TrainingFileFormFactory.php +++ b/site/app/Form/TrainingFileFormFactory.php @@ -19,7 +19,7 @@ public function __construct( /** * @param callable(Html|string, string): void $onSuccess - * @param array $applicationIdsAllowedFiles + * @param list $applicationIdsAllowedFiles */ public function create(callable $onSuccess, DateTimeInterface $trainingStart, array $applicationIdsAllowedFiles): UiForm { diff --git a/site/app/Http/Redirections.php b/site/app/Http/Redirections.php index ac874a65e..067ac4496 100644 --- a/site/app/Http/Redirections.php +++ b/site/app/Http/Redirections.php @@ -3,27 +3,24 @@ namespace MichalSpacekCz\Http; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Http\Exceptions\HttpRedirectDestinationUrlMalformedException; -use MichalSpacekCz\ShouldNotHappenException; -use Nette\Database\Explorer; use Nette\Http\UrlScript; readonly class Redirections { public function __construct( - private Explorer $database, + private TypedDatabase $database, ) { } public function getDestination(UrlScript $sourceUrl): ?string { - $destination = $this->database->fetchField('SELECT destination FROM redirections WHERE source = ?', $sourceUrl->getPath()); - if (!$destination) { + $destination = $this->database->fetchFieldStringNullable('SELECT destination FROM redirections WHERE source = ?', $sourceUrl->getPath()); + if ($destination === null) { return null; - } elseif (!is_string($destination)) { - throw new ShouldNotHappenException(sprintf("Redirect destination for '%s' is a %s not a string", $sourceUrl->getPath(), get_debug_type($destination))); } $destinationUrl = parse_url($destination); if ($destinationUrl === false) { diff --git a/site/app/Pulse/Passwords/Algorithms/PasswordHashingAlgorithms.php b/site/app/Pulse/Passwords/Algorithms/PasswordHashingAlgorithms.php index 889f5ef63..0c54a52be 100644 --- a/site/app/Pulse/Passwords/Algorithms/PasswordHashingAlgorithms.php +++ b/site/app/Pulse/Passwords/Algorithms/PasswordHashingAlgorithms.php @@ -3,6 +3,7 @@ namespace MichalSpacekCz\Pulse\Passwords\Algorithms; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Pulse\Passwords\Rating; use Nette\Database\Explorer; @@ -11,6 +12,7 @@ public function __construct( private Explorer $database, + private TypedDatabase $typedDatabase, private Rating $rating, ) { } @@ -60,7 +62,7 @@ public function addAlgorithm(string $name, string $alias, bool $salted, bool $st */ public function getSlowHashes(): array { - return $this->database->fetchPairs( + return $this->typedDatabase->fetchPairsStringString( 'SELECT alias, algo FROM password_algos WHERE alias IN (?) ORDER BY algo', $this->rating->getSlowHashes(), ); diff --git a/site/app/Pulse/Passwords/Disclosures/PasswordHashingDisclosures.php b/site/app/Pulse/Passwords/Disclosures/PasswordHashingDisclosures.php index e2a3dc5d2..6d9094863 100644 --- a/site/app/Pulse/Passwords/Disclosures/PasswordHashingDisclosures.php +++ b/site/app/Pulse/Passwords/Disclosures/PasswordHashingDisclosures.php @@ -4,8 +4,8 @@ namespace MichalSpacekCz\Pulse\Passwords\Disclosures; use DateTime; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Pulse\Passwords\Rating; -use MichalSpacekCz\ShouldNotHappenException; use Nette\Database\Explorer; readonly class PasswordHashingDisclosures @@ -13,6 +13,7 @@ public function __construct( private Explorer $database, + private TypedDatabase $typedDatabase, private Rating $rating, ) { } @@ -37,7 +38,7 @@ public function getDisclosureTypes(): array */ public function getVisibleDisclosures(): array { - return $this->database->fetchPairs( + return $this->typedDatabase->fetchPairsStringString( 'SELECT alias, type FROM password_disclosure_types WHERE alias IN (?) ORDER BY type', $this->rating->getVisibleDisclosures(), ); @@ -49,7 +50,7 @@ public function getVisibleDisclosures(): array */ public function getInvisibleDisclosures(): array { - return $this->database->fetchPairs( + return $this->typedDatabase->fetchPairsStringString( 'SELECT alias, type FROM password_disclosure_types WHERE alias IN (?) ORDER BY type', $this->rating->getInvisibleDisclosures(), ); @@ -58,11 +59,9 @@ public function getInvisibleDisclosures(): array public function getDisclosureId(string $url, string $archive): ?int { - $id = $this->database->fetchField('SELECT id FROM password_disclosures WHERE url = ? AND archive = ?', $url, $archive); - if (!$id) { + $id = $this->typedDatabase->fetchFieldIntNullable('SELECT id FROM password_disclosures WHERE url = ? AND archive = ?', $url, $archive); + if ($id === null) { return null; - } elseif (!is_int($id)) { - throw new ShouldNotHappenException(sprintf("Disclosure id for URL '%s' and archive '%s' is a %s not an integer", $url, $archive, get_debug_type($id))); } return $id; } diff --git a/site/app/Pulse/Passwords/Passwords.php b/site/app/Pulse/Passwords/Passwords.php index 1751406c2..7c95b30e8 100644 --- a/site/app/Pulse/Passwords/Passwords.php +++ b/site/app/Pulse/Passwords/Passwords.php @@ -4,13 +4,13 @@ namespace MichalSpacekCz\Pulse\Passwords; use DateTime; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Pulse\Companies; use MichalSpacekCz\Pulse\Passwords\Algorithms\PasswordHashingAlgorithms; use MichalSpacekCz\Pulse\Passwords\Disclosures\PasswordHashingDisclosures; use MichalSpacekCz\Pulse\Passwords\Storage\StorageRegistry; use MichalSpacekCz\Pulse\Passwords\Storage\StorageRegistryFactory; use MichalSpacekCz\Pulse\Sites; -use MichalSpacekCz\ShouldNotHappenException; use Nette\Database\Explorer; use Nette\Utils\ArrayHash; @@ -19,6 +19,7 @@ public function __construct( private Explorer $database, + private TypedDatabase $typedDatabase, private Companies $companies, private Sites $sites, private PasswordsSorting $sorting, @@ -241,7 +242,7 @@ public function getStoragesByCompanyId(int $companyId): StorageRegistry */ private function getStorageId(int $companyId, int $algoId, string $siteId, string $from, bool $fromConfirmed, ?string $attributes, ?string $note): ?int { - $result = $this->database->fetchField( + $result = $this->typedDatabase->fetchFieldIntNullable( 'SELECT id FROM password_storages WHERE ?', [ 'key_companies' => ($siteId === Sites::ALL ? $companyId : null), @@ -254,10 +255,8 @@ private function getStorageId(int $companyId, int $algoId, string $siteId, strin ], ); - if (!$result) { + if ($result === null) { return null; - } elseif (!is_int($result)) { - throw new ShouldNotHappenException(sprintf("Storage id for company id '%s' and site id '%s' is a %s not an integer", $companyId, $algoId, get_debug_type($result))); } return $result; } diff --git a/site/app/Talks/Slides/TalkSlides.php b/site/app/Talks/Slides/TalkSlides.php index 0e6f07986..23d8f12f0 100644 --- a/site/app/Talks/Slides/TalkSlides.php +++ b/site/app/Talks/Slides/TalkSlides.php @@ -4,12 +4,12 @@ namespace MichalSpacekCz\Talks\Slides; use MichalSpacekCz\Application\WindowsSubsystemForLinux; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Formatter\TexyFormatter; use MichalSpacekCz\Media\Exceptions\ContentTypeException; use MichalSpacekCz\Media\Exceptions\MissingContentTypeException; use MichalSpacekCz\Media\Resources\TalkMediaResources; use MichalSpacekCz\Media\SupportedImageFileFormats; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Talks\Exceptions\DuplicatedSlideException; use MichalSpacekCz\Talks\Exceptions\SlideImageUploadFailedException; use MichalSpacekCz\Talks\Exceptions\TalkSlideDoesNotExistException; @@ -39,6 +39,7 @@ class TalkSlides public function __construct( private readonly Explorer $database, + private readonly TypedDatabase $typedDatabase, private readonly TexyFormatter $texyFormatter, private readonly TalkMediaResources $talkMediaResources, private readonly SupportedImageFileFormats $supportedImageFileFormats, @@ -57,15 +58,13 @@ public function getSlideNo(int $talkId, ?string $slide): ?int if ($slide === null) { return null; } - $slideNo = $this->database->fetchField('SELECT number FROM talk_slides WHERE key_talk = ? AND alias = ?', $talkId, $slide); - if (!$slideNo) { + $slideNo = $this->typedDatabase->fetchFieldIntNullable('SELECT number FROM talk_slides WHERE key_talk = ? AND alias = ?', $talkId, $slide); + if ($slideNo === null) { if (ctype_digit($slide)) { $slideNo = (int)$slide; // To keep deprecated but already existing numerical links (/talk-title/123) working } else { throw new TalkSlideDoesNotExistException($talkId, $slide); } - } elseif (!is_int($slideNo)) { - throw new ShouldNotHappenException(sprintf("Slide number for slide '%s' of '%s' is a %s not an integer", $slide, $talkId, get_debug_type($slideNo))); } return $slideNo; } diff --git a/site/app/Talks/TalkLocaleUrls.php b/site/app/Talks/TalkLocaleUrls.php index 3c24a4b63..036bfd876 100644 --- a/site/app/Talks/TalkLocaleUrls.php +++ b/site/app/Talks/TalkLocaleUrls.php @@ -3,13 +3,13 @@ namespace MichalSpacekCz\Talks; -use Nette\Database\Explorer; +use MichalSpacekCz\Database\TypedDatabase; readonly class TalkLocaleUrls { public function __construct( - private Explorer $database, + private TypedDatabase $database, ) { } @@ -22,7 +22,7 @@ public function get(Talk $talk): array if ($talk->getTranslationGroupId() === null) { return []; } - return $this->database->fetchPairs( + return $this->database->fetchPairsStringString( 'SELECT l.locale, t.action FROM talks t JOIN locales l ON t.key_locale = l.id_locale WHERE t.key_translation_group = ?', $talk->getTranslationGroupId(), ); diff --git a/site/app/Talks/Talks.php b/site/app/Talks/Talks.php index 68f36462e..1606ef88a 100644 --- a/site/app/Talks/Talks.php +++ b/site/app/Talks/Talks.php @@ -5,6 +5,7 @@ use DateTime; use Exception; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Formatter\TexyFormatter; use MichalSpacekCz\Media\Exceptions\ContentTypeException; use MichalSpacekCz\Talks\Exceptions\TalkDateTimeException; @@ -17,6 +18,7 @@ public function __construct( private Explorer $database, + private TypedDatabase $typedDatabase, private TexyFormatter $texyFormatter, private TalkFactory $talkFactory, ) { @@ -79,7 +81,7 @@ public function getAll(?int $limit = null): array */ public function getApproxCount(): int { - $count = $this->database->fetchField('SELECT COUNT(*) FROM talks WHERE date <= NOW()'); + $count = $this->typedDatabase->fetchFieldInt('SELECT COUNT(*) FROM talks WHERE date <= NOW()'); return (int)($count / 10) * 10; } diff --git a/site/app/Test/Database/Database.php b/site/app/Test/Database/Database.php index 3a8104f69..ac963aa89 100644 --- a/site/app/Test/Database/Database.php +++ b/site/app/Test/Database/Database.php @@ -34,7 +34,7 @@ class Database extends Explorer private int $fetchFieldResultsPosition = 0; - /** @var array */ + /** @var array */ private array $fetchPairsResult = []; /** @var list */ @@ -186,7 +186,7 @@ public function fetchField(string $sql, ...$params): mixed /** - * @param array $fetchPairsResult + * @param array $fetchPairsResult */ public function setFetchPairsResult(array $fetchPairsResult): void { @@ -197,7 +197,7 @@ public function setFetchPairsResult(array $fetchPairsResult): void /** * @param literal-string $sql * @param string ...$params - * @return array + * @return array */ #[Override] public function fetchPairs(string $sql, ...$params): array diff --git a/site/app/Training/ApplicationForm/TrainingApplicationFormSuccess.php b/site/app/Training/ApplicationForm/TrainingApplicationFormSuccess.php index 6fa106739..a853595ad 100644 --- a/site/app/Training/ApplicationForm/TrainingApplicationFormSuccess.php +++ b/site/app/Training/ApplicationForm/TrainingApplicationFormSuccess.php @@ -14,7 +14,6 @@ use MichalSpacekCz\Training\Exceptions\SpammyApplicationException; use MichalSpacekCz\Training\Exceptions\TrainingDateNotAvailableException; use MichalSpacekCz\Training\Exceptions\TrainingDateNotUpcomingException; -use MichalSpacekCz\Training\Exceptions\TrainingStatusIdNotIntException; use MichalSpacekCz\Training\Mails\TrainingMails; use Nette\Application\Application as NetteApplication; use Nette\Application\UI\Presenter; @@ -45,7 +44,6 @@ public function __construct( * @param array $dates * @throws HaliteAlert * @throws SodiumException - * @throws TrainingStatusIdNotIntException * @throws WrongTemplateClassException */ public function success( diff --git a/site/app/Training/ApplicationStatuses/TrainingApplicationStatuses.php b/site/app/Training/ApplicationStatuses/TrainingApplicationStatuses.php index fe34eb660..b34b08f3e 100644 --- a/site/app/Training/ApplicationStatuses/TrainingApplicationStatuses.php +++ b/site/app/Training/ApplicationStatuses/TrainingApplicationStatuses.php @@ -6,9 +6,9 @@ use DateTime; use DateTimeInterface; use Exception; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Training\Exceptions\CannotUpdateTrainingApplicationStatusException; use MichalSpacekCz\Training\Exceptions\TrainingApplicationDoesNotExistException; -use MichalSpacekCz\Training\Exceptions\TrainingStatusIdNotIntException; use Nette\Database\Explorer; use Tracy\Debugger; @@ -30,25 +30,19 @@ class TrainingApplicationStatuses public function __construct( private readonly Explorer $database, + private readonly TypedDatabase $typedDatabase, private readonly TrainingApplicationStatusHistory $statusHistory, ) { } - /** - * @throws TrainingStatusIdNotIntException - */ public function getStatusId(TrainingApplicationStatus $status): int { if (!isset($this->statusIds[$status->value])) { - $statusId = $this->database->fetchField( + $this->statusIds[$status->value] = $this->typedDatabase->fetchFieldInt( 'SELECT id_status FROM training_application_status WHERE status = ?', $status->value, ); - if (!is_int($statusId)) { - throw new TrainingStatusIdNotIntException($status, $statusId); - } - $this->statusIds[$status->value] = $statusId; } return $this->statusIds[$status->value]; } @@ -110,7 +104,7 @@ public function getChildrenStatuses(TrainingApplicationStatus $parent): array { if (!isset($this->childrenStatuses[$parent->value])) { $this->childrenStatuses[$parent->value] = []; - $statuses = $this->database->fetchPairs( + $statuses = $this->typedDatabase->fetchPairsIntString( 'SELECT st.id_status, st.status @@ -134,7 +128,7 @@ public function getChildrenStatuses(TrainingApplicationStatus $parent): array public function getParentStatuses(TrainingApplicationStatus $child): array { if (!isset($this->parentStatuses[$child->value])) { - $statuses = $this->database->fetchPairs( + $statuses = $this->typedDatabase->fetchPairsIntString( 'SELECT sf.id_status, sf.status diff --git a/site/app/Training/Applications/TrainingApplicationSources.php b/site/app/Training/Applications/TrainingApplicationSources.php index 587aa4090..eeb42bfc8 100644 --- a/site/app/Training/Applications/TrainingApplicationSources.php +++ b/site/app/Training/Applications/TrainingApplicationSources.php @@ -3,16 +3,15 @@ namespace MichalSpacekCz\Training\Applications; -use MichalSpacekCz\ShouldNotHappenException; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Training\Resolver\Vrana; -use Nette\Database\Explorer; use Nette\Utils\Strings; readonly class TrainingApplicationSources { public function __construct( - private Explorer $database, + private TypedDatabase $database, private Vrana $vranaResolver, ) { } @@ -23,7 +22,7 @@ public function __construct( */ public function getAll(): array { - return $this->database->fetchPairs( + return $this->database->fetchPairsStringString( 'SELECT alias, name @@ -35,11 +34,7 @@ public function getAll(): array public function getSourceId(string $source): int { - $id = $this->database->fetchField('SELECT id_source FROM training_application_sources WHERE alias = ?', $source); - if (!is_int($id)) { - throw new ShouldNotHappenException(sprintf("Source id for source '%s' is a %s not an integer", $source, get_debug_type($id))); - } - return $id; + return $this->database->fetchFieldInt('SELECT id_source FROM training_application_sources WHERE alias = ?', $source); } diff --git a/site/app/Training/Applications/TrainingApplicationStorage.php b/site/app/Training/Applications/TrainingApplicationStorage.php index 1ebdd47a7..9655d493a 100644 --- a/site/app/Training/Applications/TrainingApplicationStorage.php +++ b/site/app/Training/Applications/TrainingApplicationStorage.php @@ -8,7 +8,6 @@ use MichalSpacekCz\Training\ApplicationStatuses\TrainingApplicationStatuses; use MichalSpacekCz\Training\Dates\TrainingDate; use MichalSpacekCz\Training\Exceptions\CannotUpdateTrainingApplicationStatusException; -use MichalSpacekCz\Training\Exceptions\TrainingStatusIdNotIntException; use MichalSpacekCz\Training\Price; use MichalSpacekCz\Training\Prices; use Nette\Database\Explorer; @@ -35,7 +34,6 @@ public function __construct( /** * @throws CannotUpdateTrainingApplicationStatusException - * @throws TrainingStatusIdNotIntException * @throws SodiumException * @throws HaliteAlert */ @@ -75,7 +73,6 @@ public function addInvitation( /** * @throws CannotUpdateTrainingApplicationStatusException - * @throws TrainingStatusIdNotIntException * @throws SodiumException * @throws HaliteAlert */ @@ -118,7 +115,6 @@ public function addApplication( * * @return int application id * @throws CannotUpdateTrainingApplicationStatusException - * @throws TrainingStatusIdNotIntException * @throws SodiumException * @throws HaliteAlert */ @@ -147,7 +143,6 @@ public function addPreliminaryInvitation(int $trainingId, string $name, string $ /** * @throws CannotUpdateTrainingApplicationStatusException - * @throws TrainingStatusIdNotIntException * @throws SodiumException * @throws HaliteAlert */ diff --git a/site/app/Training/Applications/TrainingApplications.php b/site/app/Training/Applications/TrainingApplications.php index 0e9899204..9ba34f374 100644 --- a/site/app/Training/Applications/TrainingApplications.php +++ b/site/app/Training/Applications/TrainingApplications.php @@ -5,7 +5,7 @@ use Contributte\Translation\Translator; use DateTime; -use MichalSpacekCz\ShouldNotHappenException; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Training\ApplicationStatuses\TrainingApplicationStatus; use MichalSpacekCz\Training\ApplicationStatuses\TrainingApplicationStatuses; use MichalSpacekCz\Training\Exceptions\TrainingApplicationDoesNotExistException; @@ -22,6 +22,7 @@ class TrainingApplications public function __construct( private readonly Explorer $database, + private readonly TypedDatabase $typedDatabase, private readonly TrainingApplicationStatuses $trainingApplicationStatuses, private readonly TrainingApplicationFactory $trainingApplicationFactory, private readonly Translator $translator, @@ -212,7 +213,7 @@ public function getValidUnpaidByDate(int $dateId): array public function getValidUnpaidCount(): int { - $count = $this->database->fetchField( + return $this->typedDatabase->fetchFieldInt( 'SELECT COUNT(1) FROM @@ -223,10 +224,6 @@ public function getValidUnpaidCount(): int AND paid IS NULL', array_keys($this->trainingApplicationStatuses->getDiscardedStatuses()), ); - if (!is_int($count)) { - throw new ShouldNotHappenException(sprintf("Count is a %s not an integer", get_debug_type($count))); - } - return $count; } diff --git a/site/app/Training/Exceptions/TrainingStatusIdNotIntException.php b/site/app/Training/Exceptions/TrainingStatusIdNotIntException.php deleted file mode 100644 index e9e3e8efe..000000000 --- a/site/app/Training/Exceptions/TrainingStatusIdNotIntException.php +++ /dev/null @@ -1,18 +0,0 @@ -value, get_debug_type($id)), previous: $previous); - } - -} diff --git a/site/app/Training/Files/TrainingFiles.php b/site/app/Training/Files/TrainingFiles.php index 4cebde6b5..411363eac 100644 --- a/site/app/Training/Files/TrainingFiles.php +++ b/site/app/Training/Files/TrainingFiles.php @@ -5,6 +5,7 @@ use DateTime; use DateTimeInterface; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Training\Applications\TrainingApplication; use MichalSpacekCz\Training\ApplicationStatuses\TrainingApplicationStatuses; use Nette\Database\Explorer; @@ -16,6 +17,7 @@ public function __construct( private Explorer $database, + private readonly TypedDatabase $typedDatabase, private TrainingApplicationStatuses $trainingApplicationStatuses, private TrainingFileFactory $trainingFileFactory, private TrainingFilesStorage $trainingFilesStorage, @@ -81,7 +83,7 @@ public function getFile(int $applicationId, string $token, string $filename): ?T /** - * @param int[] $applicationIds + * @param list $applicationIds */ public function addFile(DateTimeInterface $start, FileUpload $file, array $applicationIds): string { @@ -132,7 +134,7 @@ public function deleteFiles(array $dateIds): void $dateIds, ); - foreach ($this->database->fetchPairs('SELECT start FROM training_dates WHERE id_date IN (?)', $dateIds) as $date) { + foreach ($this->typedDatabase->fetchPairsListDateTime('SELECT start FROM training_dates WHERE id_date IN (?)', $dateIds) as $date) { FileSystem::delete($this->trainingFilesStorage->getFilesDir($date)); } } diff --git a/site/app/Training/TrainingLocales.php b/site/app/Training/TrainingLocales.php index c119e804e..e674fe182 100644 --- a/site/app/Training/TrainingLocales.php +++ b/site/app/Training/TrainingLocales.php @@ -4,13 +4,13 @@ namespace MichalSpacekCz\Training; use MichalSpacekCz\Application\Locale\LocaleLinkGenerator; -use Nette\Database\Explorer; +use MichalSpacekCz\Database\TypedDatabase; readonly class TrainingLocales { public function __construct( - private Explorer $database, + private TypedDatabase $database, private LocaleLinkGenerator $localeLinkGenerator, ) { } @@ -24,7 +24,7 @@ public function __construct( */ public function getLocaleActions(string $action): array { - return $this->database->fetchPairs( + return $this->database->fetchPairsStringString( 'SELECT l.language, a.action diff --git a/site/app/Training/Trainings/Trainings.php b/site/app/Training/Trainings/Trainings.php index ad6b8690d..4b2284de8 100644 --- a/site/app/Training/Trainings/Trainings.php +++ b/site/app/Training/Trainings/Trainings.php @@ -4,7 +4,7 @@ namespace MichalSpacekCz\Training\Trainings; use Contributte\Translation\Translator; -use MichalSpacekCz\ShouldNotHappenException; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Training\Exceptions\TrainingDoesNotExistException; use Nette\Database\Explorer; @@ -17,6 +17,7 @@ class Trainings public function __construct( private readonly Explorer $database, + private readonly TypedDatabase $typedDatabase, private readonly Translator $translator, private readonly TrainingFactory $trainingFactory, ) { @@ -220,9 +221,9 @@ public function getNamesIncludingCustomDiscontinued(): array */ public function getCooperations(): array { - return $this->database->fetchPairs( + return $this->typedDatabase->fetchPairsIntString( 'SELECT - c.id_cooperation AS id, + c.id_cooperation, c.name FROM training_cooperations c ORDER BY @@ -233,7 +234,7 @@ public function getCooperations(): array public function getActionById(int $id): string { - $action = $this->database->fetchField( + return $this->typedDatabase->fetchFieldString( 'SELECT a.action FROM trainings t @@ -246,10 +247,6 @@ public function getActionById(int $id): string $id, $this->translator->getDefaultLocale(), ); - if (!is_string($action)) { - throw new ShouldNotHappenException(sprintf("Action for id '%s' is a %s not a string", $id, get_debug_type($action))); - } - return $action; } diff --git a/site/app/User/Manager.php b/site/app/User/Manager.php index 8a63ffcb9..3fc55dd28 100644 --- a/site/app/User/Manager.php +++ b/site/app/User/Manager.php @@ -5,6 +5,7 @@ use DateTimeInterface; use Exception; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Http\Cookies\CookieName; use MichalSpacekCz\Http\Cookies\Cookies; use MichalSpacekCz\User\Exceptions\IdentityException; @@ -44,6 +45,7 @@ public function __construct( private Explorer $database, + private TypedDatabase $typedDatabase, private IRequest $httpRequest, private Cookies $cookies, private Passwords $passwords, @@ -172,7 +174,7 @@ private function updatePassword(int $userId, string $newPassword): void public function isForbidden(): bool { - $forbidden = $this->database->fetchField( + $forbidden = $this->typedDatabase->fetchFieldIntNullable( 'SELECT 1 FROM diff --git a/site/app/Www/Presenters/ErrorPresenter.php b/site/app/Www/Presenters/ErrorPresenter.php index 3493afb87..b94048c6f 100644 --- a/site/app/Www/Presenters/ErrorPresenter.php +++ b/site/app/Www/Presenters/ErrorPresenter.php @@ -9,6 +9,7 @@ use MichalSpacekCz\Application\Locale\LocaleLink; use MichalSpacekCz\Application\Locale\LocaleLinkGenerator; use MichalSpacekCz\EasterEgg\FourOhFourButFound; +use MichalSpacekCz\ShouldNotHappenException; use Nette\Application\BadRequestException; use Nette\Application\UI\InvalidLinkException; use Nette\Http\IResponse; @@ -102,7 +103,11 @@ protected function getLocaleLinkDefault(): array protected function getLocaleLinkAction(): string { $requestParam = $this->appRequest->getOriginalRequest($this->getRequest()); - return $requestParam->getPresenterName() . ':' . $requestParam->getParameter(self::ActionKey); + $action = $requestParam->getParameter(self::ActionKey); + if (!is_string($action)) { + throw new ShouldNotHappenException(sprintf('Action should be a string, %s provided', get_debug_type($action))); + } + return $requestParam->getPresenterName() . ':' . $action; } diff --git a/site/composer.json b/site/composer.json index b6157f7b2..3aefc36f5 100644 --- a/site/composer.json +++ b/site/composer.json @@ -56,6 +56,7 @@ }, "require-dev": { "efabrica/phpstan-latte": "^0", + "jetbrains/phpstorm-attributes": "^1.0", "nette/tester": "^2.4.3", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", diff --git a/site/composer.lock b/site/composer.lock index 4624858b5..da700fc6f 100644 --- a/site/composer.lock +++ b/site/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2d3af6517f9ee795fa03e2a3bc68a8d0", + "content-hash": "c9462caaf3d34ab07ee2fb743b25cbca", "packages": [ { "name": "contributte/translation", @@ -3132,6 +3132,48 @@ }, "time": "2023-11-26T22:53:13+00:00" }, + { + "name": "jetbrains/phpstorm-attributes", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/JetBrains/phpstorm-attributes.git", + "reference": "a7a83ae5df4dd3c0875484483de19de8edf60a9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JetBrains/phpstorm-attributes/zipball/a7a83ae5df4dd3c0875484483de19de8edf60a9f", + "reference": "a7a83ae5df4dd3c0875484483de19de8edf60a9f", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "JetBrains\\PhpStorm\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "JetBrains", + "homepage": "https://www.jetbrains.com" + } + ], + "description": "PhpStorm specific attributes", + "keywords": [ + "attributes", + "jetbrains", + "phpstorm" + ], + "support": { + "issues": "https://youtrack.jetbrains.com/newIssue?project=WI", + "source": "https://github.com/JetBrains/phpstorm-attributes/tree/1.0" + }, + "time": "2020-11-17T11:09:47+00:00" + }, { "name": "nette/tester", "version": "v2.5.2", diff --git a/site/config/services.neon b/site/config/services.neon index 1d33d30a7..bfbc6c19b 100644 --- a/site/config/services.neon +++ b/site/config/services.neon @@ -25,6 +25,7 @@ services: - MichalSpacekCz\CompanyInfo\CompanyRegisterAres - MichalSpacekCz\CompanyInfo\CompanyRegisterRegisterUz - MichalSpacekCz\Css\CriticalCssFactory + - MichalSpacekCz\Database\TypedDatabase - MichalSpacekCz\DateTime\DateTime - MichalSpacekCz\DateTime\DateTimeFactory - MichalSpacekCz\DateTime\DateTimeFormatter(@translation.translator::getDefaultLocale()) @@ -83,15 +84,15 @@ services: talkVideoThumbnails: MichalSpacekCz\Media\VideoThumbnails(mediaResources: @talkMediaResources) interviewVideoThumbnails: MichalSpacekCz\Media\VideoThumbnails(mediaResources: @interviewMediaResources) - MichalSpacekCz\Net\DnsResolver - - MichalSpacekCz\Pulse\Companies(@database.pulse.context) - - MichalSpacekCz\Pulse\Passwords\Algorithms\PasswordHashingAlgorithms(@database.pulse.context) - - MichalSpacekCz\Pulse\Passwords\Disclosures\PasswordHashingDisclosures(@database.pulse.context) - - MichalSpacekCz\Pulse\Passwords\Passwords(@database.pulse.context) + - MichalSpacekCz\Pulse\Companies(@database.pulse.explorer) + - MichalSpacekCz\Pulse\Passwords\Algorithms\PasswordHashingAlgorithms(@database.pulse.explorer) + - MichalSpacekCz\Pulse\Passwords\Disclosures\PasswordHashingDisclosures(@database.pulse.explorer) + - MichalSpacekCz\Pulse\Passwords\Passwords(@database.pulse.explorer) - MichalSpacekCz\Pulse\Passwords\PasswordsSorting - MichalSpacekCz\Pulse\Passwords\Rating - MichalSpacekCz\Pulse\Passwords\Storage\StorageAlgorithmAttributesFactory - MichalSpacekCz\Pulse\Passwords\Storage\StorageRegistryFactory - - MichalSpacekCz\Pulse\Sites(@database.pulse.context) + - MichalSpacekCz\Pulse\Sites(@database.pulse.explorer) - MichalSpacekCz\Tags\Tags - MichalSpacekCz\Talks\Slides\TalkSlides - MichalSpacekCz\Talks\TalkFactory(videoFactory: @talkVideoFactory) @@ -145,8 +146,8 @@ services: - MichalSpacekCz\Training\Trainings\Trainings - MichalSpacekCz\Training\Venues\TrainingVenues - MichalSpacekCz\Twitter\TwitterCards - - MichalSpacekCz\UpcKeys\Technicolor(@database.upcKeys.context, apiUrl: %awsLambda.upcKeys.url%, apiKey: %awsLambda.upcKeys.apiKey%) - - MichalSpacekCz\UpcKeys\Ubee(@database.upcKeys.context) + - MichalSpacekCz\UpcKeys\Technicolor(@database.upcKeys.explorer, apiUrl: %awsLambda.upcKeys.url%, apiKey: %awsLambda.upcKeys.apiKey%) + - MichalSpacekCz\UpcKeys\Ubee(@database.upcKeys.explorer) - MichalSpacekCz\UpcKeys\UpcKeys(routers: [@MichalSpacekCz\UpcKeys\Technicolor, @MichalSpacekCz\UpcKeys\Ubee]) - MichalSpacekCz\User\Manager(passwordEncryption: @passwordEncryption, permanentLoginInterval: %permanentLogin.interval%) - MichalSpacekCz\Utils\Strings diff --git a/site/psalm.xml b/site/psalm.xml index 3ed0e6b9f..e006c2b53 100644 --- a/site/psalm.xml +++ b/site/psalm.xml @@ -29,6 +29,7 @@ + diff --git a/site/stubs/Nette/CommandLine/Parser.phpstub b/site/stubs/Nette/CommandLine/Parser.phpstub new file mode 100644 index 000000000..879f9069a --- /dev/null +++ b/site/stubs/Nette/CommandLine/Parser.phpstub @@ -0,0 +1,16 @@ + + */ + public function parse(?array $args = null): array + { + } + +} diff --git a/site/tests/Application/BootstrapTest.phpt b/site/tests/Application/BootstrapTest.phpt index 45bae3e19..9788358d8 100644 --- a/site/tests/Application/BootstrapTest.phpt +++ b/site/tests/Application/BootstrapTest.phpt @@ -24,10 +24,11 @@ class BootstrapTest extends TestCase public function __construct() { - if (Debugger::$logDirectory === null) { + $logDirectory = Debugger::$logDirectory; + if ($logDirectory === null) { throw new ShouldNotHappenException('Call Nette\Bootstrap\Configurator::enableTracy() first, possibly in MichalSpacekCz\Application\Bootstrap::createConfigurator()'); } - $this->exceptionLog = Debugger::$logDirectory . '/' . ILogger::EXCEPTION . '.log'; + $this->exceptionLog = $logDirectory . '/' . ILogger::EXCEPTION . '.log'; if (file_exists($this->exceptionLog)) { $this->tempLog = $this->exceptionLog . '.' . uniqid(more_entropy: true); rename($this->exceptionLog, $this->tempLog); diff --git a/site/tests/Articles/ArticlesTest.phpt b/site/tests/Articles/ArticlesTest.phpt index ee1f900e0..a73fb2311 100644 --- a/site/tests/Articles/ArticlesTest.phpt +++ b/site/tests/Articles/ArticlesTest.phpt @@ -4,15 +4,15 @@ declare(strict_types = 1); namespace MichalSpacekCz\Articles; -use DateTime; use MichalSpacekCz\Articles\Blog\BlogPost; use MichalSpacekCz\Articles\Blog\BlogPostFactory; +use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Formatter\TexyFormatter; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Tags\Tags; use MichalSpacekCz\Test\Database\Database; use MichalSpacekCz\Test\NoOpTranslator; use MichalSpacekCz\Test\TestCaseRunner; +use Nette\Utils\DateTime; use Override; use Tester\Assert; use Tester\TestCase; @@ -28,6 +28,7 @@ class ArticlesTest extends TestCase public function __construct( private readonly Database $database, + TypedDatabase $typedDatabase, private readonly NoOpTranslator $translator, TexyFormatter $texyFormatter, BlogPostFactory $blogPostFactory, @@ -35,6 +36,7 @@ class ArticlesTest extends TestCase ) { $this->articles = new Articles( $this->database, + $typedDatabase, $texyFormatter, $blogPostFactory, $tags, @@ -164,17 +166,9 @@ class ArticlesTest extends TestCase $this->database->setFetchFieldDefaultResult(null); Assert::null($this->articles->getNearestPublishDate()); - $this->database->setFetchFieldDefaultResult(false); - Assert::null($this->articles->getNearestPublishDate()); - - $nearest = new DateTime('+3 days'); + $nearest = new \Nette\Utils\DateTime('+3 days'); $this->database->setFetchFieldDefaultResult($nearest); Assert::same($nearest, $this->articles->getNearestPublishDate()); - - Assert::exception(function (): void { - $this->database->setFetchFieldDefaultResult('\o/'); - $this->articles->getNearestPublishDate(); - }, ShouldNotHappenException::class, 'Nearest published date is a string not a DateTime object'); } @@ -183,17 +177,9 @@ class ArticlesTest extends TestCase $this->database->setFetchFieldDefaultResult(null); Assert::null($this->articles->getNearestPublishDateByTags(['foo'])); - $this->database->setFetchFieldDefaultResult(false); - Assert::null($this->articles->getNearestPublishDateByTags(['foo'])); - $nearest = new DateTime('+3 days'); $this->database->setFetchFieldDefaultResult($nearest); Assert::same($nearest, $this->articles->getNearestPublishDateByTags(['foo'])); - - Assert::exception(function (): void { - $this->database->setFetchFieldDefaultResult('\o/'); - $this->articles->getNearestPublishDateByTags(['foo']); - }, ShouldNotHappenException::class, 'Nearest published date is a string not a DateTime object'); } diff --git a/site/tests/Database/TypedDatabaseTest.phpt b/site/tests/Database/TypedDatabaseTest.phpt new file mode 100644 index 000000000..2bcda28ed --- /dev/null +++ b/site/tests/Database/TypedDatabaseTest.phpt @@ -0,0 +1,238 @@ +database->reset(); + } + + + public function testFetchPairsStringString(): void + { + $this->database->setFetchPairsResult([ + 'foo' => 'bar', + 'waldo' => 'quux', + 'xyzzy' => 'fred', + ]); + $list = $this->typedDatabase->fetchPairsStringString('SELECT foo'); + Assert::same('bar', $list['foo']); + Assert::same('quux', $list['waldo']); + Assert::same('fred', $list['xyzzy']); + } + + + public function testFetchPairsStringStringInvalidTypeKey(): void + { + $this->database->setFetchPairsResult([ + 3 => 'foo', + ]); + Assert::exception(function (): void { + $this->typedDatabase->fetchPairsStringString('SELECT foo'); + }, TypedDatabaseTypeException::class, 'string expected, int given'); + } + + + public function testFetchPairsStringStringInvalidTypeValue(): void + { + $this->database->setFetchPairsResult([ + 'foo' => 3, + ]); + Assert::exception(function (): void { + $this->typedDatabase->fetchPairsStringString('SELECT foo'); + }, TypedDatabaseTypeException::class, 'string expected, int given'); + } + + + public function testFetchPairsIntString(): void + { + $this->database->setFetchPairsResult([ + 1 => 'bar', + 3 => 'quux', + 5 => 'fred', + ]); + $list = $this->typedDatabase->fetchPairsIntString('SELECT foo'); + Assert::same('bar', $list[1]); + Assert::same('quux', $list[3]); + Assert::same('fred', $list[5]); + } + + + public function testFetchPairsIntStringInvalidTypeKey(): void + { + $this->database->setFetchPairsResult([ + 'foo' => 'foo', + ]); + Assert::exception(function (): void { + $this->typedDatabase->fetchPairsIntString('SELECT foo'); + }, TypedDatabaseTypeException::class, 'int expected, string given'); + } + + + public function testFetchPairsIntStringInvalidTypeValue(): void + { + $this->database->setFetchPairsResult([ + 303 => 808, + ]); + Assert::exception(function (): void { + $this->typedDatabase->fetchPairsIntString('SELECT foo'); + }, TypedDatabaseTypeException::class, 'string expected, int given'); + } + + + public function testFetchPairsListDateTime(): void + { + $this->database->setFetchPairsResult([ + 1 => new NetteDateTime('2023-01-01 10:20:30'), + 3 => new NetteDateTime('2023-01-03 10:20:30'), + 5 => new NetteDateTime('2023-01-05 10:20:30'), + ]); + $list = $this->typedDatabase->fetchPairsListDateTime('SELECT foo'); + Assert::type('list', $list); + Assert::same('2023-01-01 10:20:30', $list[0]->format(DateTime::DATE_MYSQL)); + Assert::same('2023-01-03 10:20:30', $list[1]->format(DateTime::DATE_MYSQL)); + Assert::same('2023-01-05 10:20:30', $list[2]->format(DateTime::DATE_MYSQL)); + } + + + public function testFetchPairsListDateTimeInvalidType(): void + { + $this->database->setFetchPairsResult([ + 1 => new NetteDateTime('2023-01-01 10:20:30'), + 3 => 'foo', + ]); + Assert::exception(function (): void { + $this->typedDatabase->fetchPairsListDateTime('SELECT foo'); + }, TypedDatabaseTypeException::class, 'Nette\Utils\DateTime expected, string given'); + } + + + public function testFetchFieldString(): void + { + $this->database->setFetchFieldDefaultResult('foo'); + Assert::same('foo', $this->typedDatabase->fetchFieldString('SELECT foo')); + } + + + public function testFetchFieldStringNullable(): void + { + $this->database->setFetchFieldDefaultResult('foo'); + Assert::same('foo', $this->typedDatabase->fetchFieldStringNullable('SELECT foo')); + $this->database->setFetchFieldDefaultResult(null); + Assert::null($this->typedDatabase->fetchFieldStringNullable('SELECT foo')); + } + + + public function testFetchFieldStringInvalidType(): void + { + $this->database->setFetchFieldDefaultResult(303); + Assert::exception(function (): void { + $this->typedDatabase->fetchFieldString('SELECT foo'); + }, TypedDatabaseTypeException::class, 'string expected, int given'); + } + + + public function testFetchFieldStringNullableInvalidType(): void + { + $this->database->setFetchFieldDefaultResult(303); + Assert::exception(function (): void { + $this->typedDatabase->fetchFieldStringNullable('SELECT foo'); + }, TypedDatabaseTypeException::class, 'string|null expected, int given'); + } + + + public function testFetchFieldInt(): void + { + $this->database->setFetchFieldDefaultResult(303); + Assert::same(303, $this->typedDatabase->fetchFieldInt('SELECT 303')); + } + + + public function testFetchFieldIntNullable(): void + { + $this->database->setFetchFieldDefaultResult(303); + Assert::same(303, $this->typedDatabase->fetchFieldIntNullable('SELECT 303')); + $this->database->setFetchFieldDefaultResult(null); + Assert::null($this->typedDatabase->fetchFieldIntNullable('SELECT 303')); + } + + + public function testFetchFieldIntInvalidType(): void + { + $this->database->setFetchFieldDefaultResult('foo'); + Assert::exception(function (): void { + $this->typedDatabase->fetchFieldInt('SELECT 303'); + }, TypedDatabaseTypeException::class, 'int expected, string given'); + } + + + public function testFetchFieldIntNullableInvalidType(): void + { + $this->database->setFetchFieldDefaultResult('foo'); + Assert::exception(function (): void { + $this->typedDatabase->fetchFieldIntNullable('SELECT 303'); + }, TypedDatabaseTypeException::class, 'int|null expected, string given'); + } + + + public function testFetchFieldDateTime(): void + { + $this->database->setFetchFieldDefaultResult(new NetteDateTime()); + Assert::type(NetteDateTime::class, $this->typedDatabase->fetchFieldDateTime('SELECT 808')); + } + + + public function testFetchFieldDateTimeNullable(): void + { + $this->database->setFetchFieldDefaultResult(new NetteDateTime()); + Assert::type(NetteDateTime::class, $this->typedDatabase->fetchFieldDateTimeNullable('SELECT 808')); + $this->database->setFetchFieldDefaultResult(null); + Assert::null($this->typedDatabase->fetchFieldIntNullable('SELECT 808')); + } + + + public function testFetchFieldDateTimeInvalidType(): void + { + $this->database->setFetchFieldDefaultResult('foo'); + Assert::exception(function (): void { + $this->typedDatabase->fetchFieldDateTime('SELECT 808'); + }, TypedDatabaseTypeException::class, NetteDateTime::class . ' expected, string given'); + } + + + public function testFetchFieldDateTimeNullableInvalidType(): void + { + $this->database->setFetchFieldDefaultResult('foo'); + Assert::exception(function (): void { + $this->typedDatabase->fetchFieldDateTimeNullable('SELECT 808'); + }, TypedDatabaseTypeException::class, NetteDateTime::class . '|null expected, string given'); + } + +} + +TestCaseRunner::run(TypedDatabaseTest::class); diff --git a/site/tests/Http/RedirectionsTest.phpt b/site/tests/Http/RedirectionsTest.phpt index 76ab7109b..7f44f9bdd 100644 --- a/site/tests/Http/RedirectionsTest.phpt +++ b/site/tests/Http/RedirectionsTest.phpt @@ -4,7 +4,6 @@ declare(strict_types = 1); namespace MichalSpacekCz\Http; use MichalSpacekCz\Http\Exceptions\HttpRedirectDestinationUrlMalformedException; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Test\Database\Database; use MichalSpacekCz\Test\TestCaseRunner; use Nette\Http\UrlScript; @@ -29,20 +28,12 @@ class RedirectionsTest extends TestCase $this->database->setFetchFieldDefaultResult(null); Assert::null($this->redirections->getDestination(new UrlScript())); - $this->database->setFetchFieldDefaultResult(false); - Assert::null($this->redirections->getDestination(new UrlScript())); - $this->database->setFetchFieldDefaultResult('https://example.com/'); Assert::same('https://example.com/', $this->redirections->getDestination(new UrlScript())); $this->database->setFetchFieldDefaultResult('/foo.bar'); Assert::same('https://com.example/foo.bar', $this->redirections->getDestination(new UrlScript('https://com.example/waldo'))); - Assert::exception(function (): void { - $this->database->setFetchFieldDefaultResult(3.14); - $this->redirections->getDestination(new UrlScript('https://com.example/waldo')); - }, ShouldNotHappenException::class, "Redirect destination for '/waldo' is a float not a string"); - Assert::exception(function (): void { $this->database->setFetchFieldDefaultResult('https://:80'); $this->redirections->getDestination(new UrlScript()); diff --git a/site/tests/Pulse/Passwords/Disclosures/PasswordHashingDisclosuresTest.phpt b/site/tests/Pulse/Passwords/Disclosures/PasswordHashingDisclosuresTest.phpt index 31283ed70..06d0b2f8c 100644 --- a/site/tests/Pulse/Passwords/Disclosures/PasswordHashingDisclosuresTest.phpt +++ b/site/tests/Pulse/Passwords/Disclosures/PasswordHashingDisclosuresTest.phpt @@ -3,7 +3,6 @@ declare(strict_types = 1); namespace MichalSpacekCz\Pulse\Passwords\Disclosures; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Test\Database\Database; use MichalSpacekCz\Test\TestCaseRunner; use Tester\Assert; @@ -71,11 +70,6 @@ class PasswordHashingDisclosuresTest extends TestCase $this->database->setFetchFieldDefaultResult(null); Assert::null($this->disclosures->getDisclosureId('https://example.com/', 'https://archive.example.com/')); - $this->database->setFetchFieldDefaultResult('foo'); - Assert::exception(function (): void { - $this->disclosures->getDisclosureId('https://example.com/', 'https://archive.example.com/'); - }, ShouldNotHappenException::class); - $this->database->setFetchFieldDefaultResult(123); Assert::same(123, $this->disclosures->getDisclosureId('https://example.com/', 'https://archive.example.com/')); } diff --git a/site/tests/Talks/Slides/TalkSlidesTest.phpt b/site/tests/Talks/Slides/TalkSlidesTest.phpt index e786fd33c..bc4c33cc3 100644 --- a/site/tests/Talks/Slides/TalkSlidesTest.phpt +++ b/site/tests/Talks/Slides/TalkSlidesTest.phpt @@ -6,7 +6,6 @@ namespace MichalSpacekCz\Talks\Slides; use DateTime; use MichalSpacekCz\Media\Video; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Talks\Exceptions\TalkSlideDoesNotExistException; use MichalSpacekCz\Talks\Talk; use MichalSpacekCz\Test\Database\Database; @@ -45,11 +44,6 @@ class TalkSlidesTest extends TestCase $this->database->setFetchFieldDefaultResult(808); Assert::same(808, $this->talkSlides->getSlideNo(1, 'yo')); - - $this->database->setFetchFieldDefaultResult('808'); - Assert::exception(function (): void { - $this->talkSlides->getSlideNo(1, 'yo'); - }, ShouldNotHappenException::class, "Slide number for slide 'yo' of '1' is a string not an integer"); } diff --git a/site/tests/Training/ApplicationForm/TrainingApplicationFormSuccessTest.phpt b/site/tests/Training/ApplicationForm/TrainingApplicationFormSuccessTest.phpt index a8cb107a4..9cd3e1dfe 100644 --- a/site/tests/Training/ApplicationForm/TrainingApplicationFormSuccessTest.phpt +++ b/site/tests/Training/ApplicationForm/TrainingApplicationFormSuccessTest.phpt @@ -159,7 +159,7 @@ class TrainingApplicationFormSuccessTest extends TestCase public function testSuccessUpdateApplication(): void { - $this->sessionSectionSet('application', [ + $this->sessionSectionParentSet->invoke($this->sessionSection, 'application', [ self::TRAINING_ACTION => ['dateId' => self::DATE_ID, 'id' => self::APPLICATION_ID], 'foo' => 'bar', ]); @@ -273,15 +273,17 @@ class TrainingApplicationFormSuccessTest extends TestCase } - private function sessionSectionGet(string $name): mixed - { - return $this->sessionSectionParentGet->invoke($this->sessionSection, $name); - } - - - private function sessionSectionSet(string $name, mixed $value): void + /** + * @return string|int|array + */ + private function sessionSectionGet(string $name): string|int|array { - $this->sessionSectionParentSet->invoke($this->sessionSection, $name, $value); + $result = $this->sessionSectionParentGet->invoke($this->sessionSection, $name); + if (!is_string($result) && !is_int($result) && !is_array($result)) { + throw new ShouldNotHappenException(sprintf('Session data type is %s, but should be string|int|array', get_debug_type($result))); + } else { + return $result; + } } } diff --git a/site/tests/Training/ApplicationStatuses/TrainingApplicationStatusesTest.phpt b/site/tests/Training/ApplicationStatuses/TrainingApplicationStatusesTest.phpt index 1bed7a754..ad3fefec8 100644 --- a/site/tests/Training/ApplicationStatuses/TrainingApplicationStatusesTest.phpt +++ b/site/tests/Training/ApplicationStatuses/TrainingApplicationStatusesTest.phpt @@ -7,7 +7,6 @@ namespace MichalSpacekCz\Training\ApplicationStatuses; use MichalSpacekCz\Test\Database\Database; use MichalSpacekCz\Test\PrivateProperty; use MichalSpacekCz\Test\TestCaseRunner; -use MichalSpacekCz\Training\Exceptions\TrainingStatusIdNotIntException; use Override; use Tester\Assert; use Tester\TestCase; @@ -42,15 +41,6 @@ class TrainingApplicationStatusesTest extends TestCase Assert::same(303, $this->applicationStatuses->getStatusId(TrainingApplicationStatus::SignedUp)); } - - public function testGetStatusIdNotInt(): void - { - $this->database->setFetchFieldDefaultResult('donut'); - Assert::exception(function (): void { - $this->applicationStatuses->getStatusId(TrainingApplicationStatus::SignedUp); - }, TrainingStatusIdNotIntException::class, "Training status 'SIGNED_UP' id is a string not an integer"); - } - } TestCaseRunner::run(TrainingApplicationStatusesTest::class); diff --git a/site/tests/Training/Applications/TrainingApplicationsTest.phpt b/site/tests/Training/Applications/TrainingApplicationsTest.phpt index 0af7213e3..c119a1500 100644 --- a/site/tests/Training/Applications/TrainingApplicationsTest.phpt +++ b/site/tests/Training/Applications/TrainingApplicationsTest.phpt @@ -3,7 +3,6 @@ declare(strict_types = 1); namespace MichalSpacekCz\Training\Applications; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Test\Database\Database; use MichalSpacekCz\Test\TestCaseRunner; use Tester\Assert; @@ -26,11 +25,6 @@ class TrainingApplicationsTest extends TestCase { $this->database->setFetchFieldDefaultResult(909); Assert::same(909, $this->trainingApplications->getValidUnpaidCount()); - - $this->database->setFetchFieldDefaultResult('\o/'); - Assert::exception(function (): void { - $this->trainingApplications->getValidUnpaidCount(); - }, ShouldNotHappenException::class, 'Count is a string not an integer'); } } diff --git a/site/tests/Training/Trainings/TrainingsTest.phpt b/site/tests/Training/Trainings/TrainingsTest.phpt index 622cbd69a..d43851fed 100644 --- a/site/tests/Training/Trainings/TrainingsTest.phpt +++ b/site/tests/Training/Trainings/TrainingsTest.phpt @@ -3,7 +3,6 @@ declare(strict_types = 1); namespace MichalSpacekCz\Training\Trainings; -use MichalSpacekCz\ShouldNotHappenException; use MichalSpacekCz\Test\Database\Database; use MichalSpacekCz\Test\TestCaseRunner; use Tester\Assert; @@ -26,11 +25,6 @@ class TrainingsTest extends TestCase { $this->database->setFetchFieldDefaultResult('pulled pork'); Assert::same('pulled pork', $this->trainings->getActionById(303)); - - $this->database->setFetchFieldDefaultResult(808); - Assert::exception(function (): void { - $this->trainings->getActionById(303); - }, ShouldNotHappenException::class, "Action for id '303' is a int not a string"); } } diff --git a/site/vendor/composer/autoload_psr4.php b/site/vendor/composer/autoload_psr4.php index abb404f38..69db46203 100644 --- a/site/vendor/composer/autoload_psr4.php +++ b/site/vendor/composer/autoload_psr4.php @@ -35,6 +35,7 @@ 'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'), 'PHPStan\\' => array($vendorDir . '/phpstan/phpstan-deprecation-rules/src', $vendorDir . '/phpstan/phpstan-nette/src'), 'MichalSpacekCz\\' => array($baseDir . '/app'), + 'JetBrains\\PhpStorm\\' => array($vendorDir . '/jetbrains/phpstorm-attributes/src'), 'Efabrica\\PHPStanLatte\\' => array($vendorDir . '/efabrica/phpstan-latte/src'), 'Contributte\\Translation\\' => array($vendorDir . '/contributte/translation/src'), ); diff --git a/site/vendor/composer/autoload_static.php b/site/vendor/composer/autoload_static.php index 69e19d46d..687db47df 100644 --- a/site/vendor/composer/autoload_static.php +++ b/site/vendor/composer/autoload_static.php @@ -52,6 +52,10 @@ class ComposerStaticInit247de957f14f643f393d210a332dd05b array ( 'MichalSpacekCz\\' => 15, ), + 'J' => + array ( + 'JetBrains\\PhpStorm\\' => 19, + ), 'E' => array ( 'Efabrica\\PHPStanLatte\\' => 22, @@ -180,6 +184,10 @@ class ComposerStaticInit247de957f14f643f393d210a332dd05b array ( 0 => __DIR__ . '/../..' . '/app', ), + 'JetBrains\\PhpStorm\\' => + array ( + 0 => __DIR__ . '/..' . '/jetbrains/phpstorm-attributes/src', + ), 'Efabrica\\PHPStanLatte\\' => array ( 0 => __DIR__ . '/..' . '/efabrica/phpstan-latte/src', diff --git a/site/vendor/composer/installed.json b/site/vendor/composer/installed.json index 497ca7842..128ced589 100644 --- a/site/vendor/composer/installed.json +++ b/site/vendor/composer/installed.json @@ -151,6 +151,51 @@ }, "install-path": "../efabrica/phpstan-latte" }, + { + "name": "jetbrains/phpstorm-attributes", + "version": "1.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/JetBrains/phpstorm-attributes.git", + "reference": "a7a83ae5df4dd3c0875484483de19de8edf60a9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JetBrains/phpstorm-attributes/zipball/a7a83ae5df4dd3c0875484483de19de8edf60a9f", + "reference": "a7a83ae5df4dd3c0875484483de19de8edf60a9f", + "shasum": "" + }, + "time": "2020-11-17T11:09:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "JetBrains\\PhpStorm\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "JetBrains", + "homepage": "https://www.jetbrains.com" + } + ], + "description": "PhpStorm specific attributes", + "keywords": [ + "attributes", + "jetbrains", + "phpstorm" + ], + "support": { + "issues": "https://youtrack.jetbrains.com/newIssue?project=WI", + "source": "https://github.com/JetBrains/phpstorm-attributes/tree/1.0" + }, + "install-path": "../jetbrains/phpstorm-attributes" + }, { "name": "latte/latte", "version": "v3.0.13", @@ -4745,6 +4790,7 @@ "dev": true, "dev-package-names": [ "efabrica/phpstan-latte", + "jetbrains/phpstorm-attributes", "nette/tester", "php-parallel-lint/php-console-highlighter", "php-parallel-lint/php-parallel-lint", diff --git a/site/vendor/composer/installed.php b/site/vendor/composer/installed.php index cddcfcfc9..df907ebb9 100644 --- a/site/vendor/composer/installed.php +++ b/site/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'spaze/michalspacek.cz', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '3b74388fb0b085f18077ed24694bf651905bfc7c', + 'reference' => 'd2374f02ac03fc0fcca2988698933edbdf5285f7', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -64,6 +64,15 @@ 0 => '*', ), ), + 'jetbrains/phpstorm-attributes' => array( + 'pretty_version' => '1.0', + 'version' => '1.0.0.0', + 'reference' => 'a7a83ae5df4dd3c0875484483de19de8edf60a9f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../jetbrains/phpstorm-attributes', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'latte/latte' => array( 'pretty_version' => 'v3.0.13', 'version' => '3.0.13.0', @@ -456,7 +465,7 @@ 'spaze/michalspacek.cz' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '3b74388fb0b085f18077ed24694bf651905bfc7c', + 'reference' => 'd2374f02ac03fc0fcca2988698933edbdf5285f7', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/site/vendor/jetbrains/phpstorm-attributes/LICENSE b/site/vendor/jetbrains/phpstorm-attributes/LICENSE new file mode 100644 index 000000000..7891c33e1 --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2010-2020 JetBrains s.r.o. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/site/vendor/jetbrains/phpstorm-attributes/composer.json b/site/vendor/jetbrains/phpstorm-attributes/composer.json new file mode 100644 index 000000000..8f9dfb2fe --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/composer.json @@ -0,0 +1,24 @@ +{ + "name": "jetbrains/phpstorm-attributes", + "description": "PhpStorm specific attributes", + "license": "Apache-2.0", + "authors": [ + { + "name": "JetBrains", + "homepage": "https://www.jetbrains.com" + } + ], + "autoload": { + "psr-4": { + "JetBrains\\PhpStorm\\": "src/" + } + }, + "support": { + "issues": "https://youtrack.jetbrains.com/newIssue?project=WI" + }, + "keywords": [ + "phpstorm", + "jetbrains", + "attributes" + ] +} \ No newline at end of file diff --git a/site/vendor/jetbrains/phpstorm-attributes/src/ArrayShape.php b/site/vendor/jetbrains/phpstorm-attributes/src/ArrayShape.php new file mode 100644 index 000000000..8070d8884 --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/src/ArrayShape.php @@ -0,0 +1,23 @@ + + * + * Example:
+ * #[ArrayShape(["f" => "int", "string", "x" => "float"])] + * This usage applied on an element effectively means that the array has 3 dimensions, the keys are "f", 1, and "x", and the corresponding types are "int", "string", and "float". + */ +#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)] +class ArrayShape { + public function __construct(array $shape) + { + } +} diff --git a/site/vendor/jetbrains/phpstorm-attributes/src/Deprecated.php b/site/vendor/jetbrains/phpstorm-attributes/src/Deprecated.php new file mode 100644 index 000000000..a117d453f --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/src/Deprecated.php @@ -0,0 +1,44 @@ + + *
  • %parametersList%: parameters of the function call. For example, for the "f(1,2)" call, %parametersList% will be "1,2"
  • + *
  • %parameter0%,%parameter1%,%parameter2%,...: parameters of the function call. For example, for the "f(1,2)" call, %parameter1% will be "2"
  • + *
  • %name%: For "\x\f(1,2)", %name% will be "\x\f", for "$this->ff()", %name% will be "ff"
  • + *
  • %class%: If the attribute is provided for method "m", then for "$this->f()->m()", %class% will be "$this->f()"
  • + * + * The following example shows how to wrap a function call in another call and swap arguments:
    + * "#[Deprecated(replaceWith: "wrappedCall(%name%(%parameter1%, %parameter0%))")] f($a, $b){}
    + * f(1,2) will be replaced with wrappedCall(f(2,1)) + * @param string $since Element is deprecated starting with the provided PHP language level, applicable only for PhpStorm stubs entries + */ + public function __construct($reason = "", $replacement = "", + #[ExpectedValues(self::PHP_VERSIONS)] $since = "5.6") + { + } +} diff --git a/site/vendor/jetbrains/phpstorm-attributes/src/ExpectedValues.php b/site/vendor/jetbrains/phpstorm-attributes/src/ExpectedValues.php new file mode 100644 index 000000000..f1698e760 --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/src/ExpectedValues.php @@ -0,0 +1,43 @@ + + *
  • Code completion - expected arguments are displayed on the top of the suggestions list when used in comparison expressions
  • + *
  • Inspections [when used in a comparison with a value/assignment to/return from method] - the element absent from the expected values list produces the inspection warning
  • + *
  • Code generation - for example, when generating the 'switch' statement, all possible expected values are inserted automatically
  • + * + * + * Expected values can be any of the following: + *
      + *
    • numbers
    • + *
    • string literals
    • + *
    • constant references
    • + *
    • class constant references
    • + *
    + * + * Expected arguments can be specified in any of the following ways: + *
      + *
    • #[ExpectedValues(values: [1,2,3])] means that one of the following is expected: `1`, `2`, or `3`
    • + *
    • #[ExpectedValues(values: MY_CONST] - default value of MY_CONST is expected to be array creation expression, in this case value of MY_CONST will be inlined
    • + *
    • #[ExpectedValues(flags: [1,2,3])] means that a bitmask of the following is expected: `1`, `2`, or `3`
    • + *
    • #[ExpectedValues(valuesFromClass: MyClass::class)] means that one of the constants from the class `MyClass` is expected
    • + *
    • #[ExpectedValues(flagsFromClass: ExpectedValues::class)] means that a bitmask of the constants from the class `MyClass` is expected
    • + *
    + * + * The attribute with the number of provided constructor arguments different from 1 will result in undefined behavior. + * @since 8.0 + */ +#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)] +class ExpectedValues { + public function __construct(array $values = [], array $flags = [], string $valuesFromClass = null, string $flagsFromClass = null) + { + } +} diff --git a/site/vendor/jetbrains/phpstorm-attributes/src/Immutable.php b/site/vendor/jetbrains/phpstorm-attributes/src/Immutable.php new file mode 100644 index 000000000..d34e7dee8 --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/src/Immutable.php @@ -0,0 +1,30 @@ + + *
  • {@link Immutable::CONSTRUCTOR_WRITE_SCOPE}: write is allowed only in containing class constructor (default choice)
  • + *
  • {@link Immutable::PRIVATE_WRITE_SCOPE}: write is allowed only in places where the property would be accessible if it had 'private' visibility modifier
  • + *
  • {@link Immutable::PROTECTED_WRITE_SCOPE}: write is allowed only in places where the property would be accessible if it had 'protected' visibility modifier
  • + * + * @since 8.0 + */ +#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] +class Immutable +{ + const CONSTRUCTOR_WRITE_SCOPE = "constructor"; + const PRIVATE_WRITE_SCOPE = "private"; + const PROTECTED_WRITE_SCOPE = "protected"; + + public function __construct(#[ExpectedValues(valuesFromClass: Immutable::class)] + $allowedWriteScope = self::CONSTRUCTOR_WRITE_SCOPE) + { + } +} diff --git a/site/vendor/jetbrains/phpstorm-attributes/src/Language.php b/site/vendor/jetbrains/phpstorm-attributes/src/Language.php new file mode 100644 index 000000000..33d70131a --- /dev/null +++ b/site/vendor/jetbrains/phpstorm-attributes/src/Language.php @@ -0,0 +1,21 @@ +