Skip to content

Commit be546a9

Browse files
IBX-9727: Fixed strict types of translatable Exceptions and Values (#590)
For more details see https://issues.ibexa.co/browse/IBX-9727 and #590 Key changes: * Fixed strict types of Translatable classes * Fixed strict types for translatable contracts * Fixed strict types for translatable Exception classes * Fixed incorrect instantiation of ValidationError across the codebase * Fixed InvalidArgumentException usage in NotificationService::createNotification * Marked InvalidResponseException as final * [Tests] Added unit test for ValidationError class * [Tests] Added unit test for UnauthorizedException class * [Tests] Added coverage for ValidationError with null value --------- Co-Authored-By: Konrad Oboza <[email protected]>
1 parent 0963e11 commit be546a9

File tree

53 files changed

+512
-1461
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+512
-1461
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 1170 deletions
Large diffs are not rendered by default.

src/contracts/FieldType/Generic/ValidationError/ConstraintViolationAdapter.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,15 @@
2121
*/
2222
final class ConstraintViolationAdapter implements ValidationErrorInterface
2323
{
24-
/** @var \Symfony\Component\Validator\ConstraintViolationInterface */
25-
private $violation;
24+
private ConstraintViolationInterface $violation;
2625

2726
/**
2827
* Element on which the error occurred
2928
* e.g. property name or property path compatible with Symfony PropertyAccess component.
3029
*
3130
* Example: StringLengthValidator[minStringLength]
32-
*
33-
* @var string
3431
*/
35-
private $target;
32+
private string $target;
3633

3734
public function __construct(ConstraintViolationInterface $violation)
3835
{
@@ -48,7 +45,7 @@ public function getTranslatableMessage(): Translation
4845
);
4946
}
5047

51-
public function setTarget($target): void
48+
public function setTarget(string $target): void
5249
{
5350
$this->target = $target;
5451
}

src/contracts/FieldType/ValidationError.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,13 @@ interface ValidationError extends Translatable
2525
* Can be a property path compatible with Symfony PropertyAccess component.
2626
*
2727
* Examples:
28-
* - "[StringLengthValidator][minStringLength]" => Target is "minStringLength" key under "StringLengthValidator" key (fieldtype validator configuration)
28+
* - "[StringLengthValidator][minStringLength]" => Target is the "minStringLength" key under "StringLengthValidator" key (field type validator configuration)
2929
* - "my_field_definition_identifier"
30-
*
31-
* @param string $target
3230
*/
33-
public function setTarget($target);
31+
public function setTarget(string $target): void;
3432

3533
/**
3634
* Returns the target element on which the error occurred.
37-
*
38-
* @return string
3935
*/
40-
public function getTarget();
36+
public function getTarget(): ?string;
4137
}

src/contracts/FieldType/ValidationError/AbstractValidationError.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,24 @@
1717
*/
1818
abstract class AbstractValidationError implements ValidationError
1919
{
20-
/** @var string */
21-
protected $message;
20+
protected string $message;
2221

23-
/** @var array */
24-
protected $parameters;
22+
/** @var array<string, scalar> */
23+
protected array $parameters;
2524

2625
/**
2726
* Element on which the error occurred
28-
* e.g. property name or property path compatible with Symfony PropertyAccess component.
27+
* e.g., property name or property path compatible with Symfony PropertyAccess component.
2928
*
3029
* Example: StringLengthValidator[minStringLength]
3130
*
3231
* @var string
3332
*/
34-
protected $target;
33+
protected string $target;
3534

35+
/**
36+
* @param array<string, scalar> $parameters
37+
*/
3638
public function __construct(string $message, array $parameters, string $target)
3739
{
3840
$this->message = $message;
@@ -45,7 +47,7 @@ public function getTranslatableMessage(): Translation
4547
return new Message($this->message, $this->parameters);
4648
}
4749

48-
public function setTarget($target): void
50+
public function setTarget(string $target): void
4951
{
5052
$this->target = $target;
5153
}

src/contracts/Repository/Translatable.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
* @copyright Copyright (C) Ibexa AS. All rights reserved.
55
* @license For full copyright and license information view LICENSE file distributed with this source code.
66
*/
7+
declare(strict_types=1);
78

89
namespace Ibexa\Contracts\Core\Repository;
910

11+
use Ibexa\Contracts\Core\Repository\Values\Translation;
12+
1013
/**
1114
* Interface implemented by everything which should be translatable. This
12-
* should for example be implemented by any exception, which might bubble up to
15+
* should, for example, be implemented by any exception, which might bubble up to
1316
* a user, or validation errors.
1417
*/
1518
interface Translatable
1619
{
1720
/**
1821
* Returns a translatable Message.
19-
*
20-
* @return \Ibexa\Contracts\Core\Repository\Values\Translation
2122
*/
22-
public function getTranslatableMessage();
23+
public function getTranslatableMessage(): Translation;
2324
}

src/contracts/Repository/Values/Filter/Filter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function __construct(?FilteringCriterion $criterion = null, array $sortCl
4747
sprintf(
4848
'Expected an instance of "%s", got "%s" at position %d',
4949
FilteringSortClause::class,
50-
is_object($sortClause) ? get_class($sortClause) : gettype($sortClause),
50+
get_debug_type($sortClause),
5151
$idx
5252
)
5353
);

src/contracts/Repository/Values/Translation/Message.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,24 @@ class Message extends Translation
2020
{
2121
/**
2222
* Message string. Might use replacements like %foo%, which are replaced by
23-
* the values specified in the values array.
23+
* the values specified in the `$values` array.
2424
*/
2525
protected string $message;
2626

2727
/**
2828
* Translation value objects. May not contain any numbers, which might
29-
* result in requiring plural forms. Use Plural for that.
29+
* result in requiring plural forms. Use `Plural` class for that.
3030
*
31-
* @var array<string, scalar>
31+
* @see \Ibexa\Contracts\Core\Repository\Values\Translation\Plural
32+
*
33+
* @var array<string, scalar|null>
3234
*/
3335
protected array $values;
3436

3537
/**
36-
* Construct singular only message from string and optional value array.
38+
* Construct a singular only message from string and optional value array.
3739
*
38-
* @param array<string, scalar> $values
40+
* @param array<string, scalar|null> $values
3941
*/
4042
public function __construct(string $message, array $values = [])
4143
{

src/contracts/Repository/Values/Translation/Plural.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,44 @@
2020
* strings provided should be English and will be translated depending on the
2121
* environment language.
2222
*
23-
* This interface follows the interfaces of XLIFF, gettext, Symfony Translations and Zend_Translate.
24-
* For singular forms you just provide a plain string (with optional placeholders without effects on the plural forms).
25-
* For potential plural forms you always provide a singular variant and an English simple plural variant.
26-
* An instance of this class can be cast to a string. In such case whether to use singular or plural form is determined
27-
* based on the value of first element of $values array (it needs to be 1 for singular, anything else for plural).
28-
* If plurality cannot be inferred from $values, a plural form is assumed as default. To force singular form,
23+
* This interface follows the interfaces of XLIFF, gettext, Symfony Translations, and Zend_Translate.
24+
* For singular forms, you provide a plain string (with optional placeholders without effects on the plural forms).
25+
* For potential plural forms, you always provide a singular variant and an English simple plural variant.
26+
* An instance of this class can be cast to a string. In such a case, whether to use singular or plural form is determined
27+
* based on the value of the first element of $values array (it needs to be 1 for singular, anything else for plural).
28+
* If a plurality cannot be inferred from $values, a plural form is assumed as default. To force singular form,
2929
* use {@see \Ibexa\Contracts\Core\Repository\Values\Translation\Message} instead.
3030
*
3131
* No implementation supports multiple different plural forms in one single message.
3232
*
33-
* The singular / plural string could, for Symfony, for example be converted
34-
* to <code>"$singular|$plural"</code>, and you would call gettext like: <code>ngettext($singular, $plural, $count ).</code>
33+
* The singular / plural string could, for Symfony, for example, be converted
34+
* to ```"$singular|$plural"```, and you would call gettext like: ```ngettext($singular, $plural, $count ).```
3535
*/
3636
class Plural extends Translation
3737
{
3838
/**
3939
* Singular string. Might use replacements like %foo%, which are replaced by
40-
* the values specified in the values array.
40+
* the values specified in the `$values` array.
4141
*/
4242
protected string $singular;
4343

4444
/**
4545
* Message string. Might use replacements like %foo%, which are replaced by
46-
* the values specified in the values array.
46+
* the values specified in the `$values` array.
4747
*/
4848
protected string $plural;
4949

5050
/**
5151
* Translation value objects.
5252
*
53-
* @var array<string, scalar>
53+
* @var array<string, scalar|null>
5454
*/
5555
protected array $values;
5656

5757
/**
5858
* Construct plural message from singular, plural and value array.
5959
*
60-
* @param array<string, scalar> $values
60+
* @param array<string, scalar|null> $values
6161
*/
6262
public function __construct(string $singular, string $plural, array $values)
6363
{

src/lib/Base/Exceptions/BadStateException.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* @copyright Copyright (C) Ibexa AS. All rights reserved.
55
* @license For full copyright and license information view LICENSE file distributed with this source code.
66
*/
7+
declare(strict_types=1);
78

89
namespace Ibexa\Core\Base\Exceptions;
910

@@ -15,20 +16,19 @@
1516
/**
1617
* BadState Exception implementation.
1718
*
18-
* Usage: throw new BadState( 'nodes', 'array' );
19+
* Usage:
20+
* ```
21+
* throw new BadStateException('nodes', 'array');
22+
* ```
1923
*/
2024
class BadStateException extends APIBadStateException implements Translatable
2125
{
2226
use TranslatableBase;
2327

2428
/**
2529
* Generates: "Argument '{$argumentName}' has a bad state: {$whatIsWrong}".
26-
*
27-
* @param string $argumentName
28-
* @param string $whatIsWrong
29-
* @param \Exception|null $previous
3030
*/
31-
public function __construct($argumentName, $whatIsWrong, Exception $previous = null)
31+
public function __construct(string $argumentName, string $whatIsWrong, ?Exception $previous = null)
3232
{
3333
$this->setMessageTemplate("Argument '%argumentName%' has a bad state: %whatIsWrong%");
3434
$this->setParameters(['%argumentName%' => $argumentName, '%whatIsWrong%' => $whatIsWrong]);

src/lib/Base/Exceptions/ContentFieldValidationException.php

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
* @copyright Copyright (C) Ibexa AS. All rights reserved.
55
* @license For full copyright and license information view LICENSE file distributed with this source code.
66
*/
7+
declare(strict_types=1);
78

89
namespace Ibexa\Core\Base\Exceptions;
910

1011
use Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException as APIContentFieldValidationException;
12+
use Ibexa\Contracts\Core\Repository\Values\Translation;
1113
use Ibexa\Core\Base\Translatable;
1214
use Ibexa\Core\Base\TranslatableBase;
15+
use Ibexa\Core\FieldType\ValidationError;
1316

1417
/**
1518
* This Exception is thrown on create or update content one or more given fields are not valid.
@@ -18,23 +21,22 @@ class ContentFieldValidationException extends APIContentFieldValidationException
1821
{
1922
use TranslatableBase;
2023

21-
private const MAX_MESSAGES_NUMBER = 32;
24+
private const int MAX_MESSAGES_NUMBER = 32;
2225

2326
/**
2427
* Contains an array of field ValidationError objects indexed with FieldDefinition id and language code.
2528
*
2629
* Example:
27-
* <code>
30+
* ```
2831
* $fieldErrors = $exception->getFieldErrors();
29-
* $fieldErrors[43]["eng-GB"]->getTranslatableMessage();
30-
* </code>
32+
* $fieldErrors[43]["eng-GB"][0]->getTranslatableMessage();
33+
* ```
3134
*
3235
* @var array<int, array<string, \Ibexa\Contracts\Core\FieldType\ValidationError[]>>
3336
*/
34-
protected $errors;
37+
protected array $errors;
3538

36-
/** @var string|null */
37-
protected $contentName;
39+
protected ?string $contentName;
3840

3941
/**
4042
* Generates: Content fields did not validate.
@@ -63,7 +65,7 @@ public static function createNewWithMultiline(array $errors, ?string $contentNam
6365
$exception->setMessageTemplate('Content "%contentName%" fields did not validate: %errors%');
6466
$exception->setParameters([
6567
'%errors%' => $exception->generateValidationErrorsMessages(),
66-
'%contentName%' => $exception->contentName !== null ? $exception->contentName : '',
68+
'%contentName%' => $exception->contentName ?? '',
6769
]);
6870
$exception->message = $exception->getBaseTranslation();
6971

@@ -83,18 +85,20 @@ public function getFieldErrors(): array
8385
private function generateValidationErrorsMessages(): string
8486
{
8587
$validationErrors = $this->collectValidationErrors();
86-
$maxMessagesNumber = self::MAX_MESSAGES_NUMBER;
87-
88-
if (count($validationErrors) > $maxMessagesNumber) {
89-
array_splice($validationErrors, $maxMessagesNumber);
90-
$validationErrors[] = sprintf('Limit: %d of validation errors has been exceeded.', $maxMessagesNumber);
88+
if (count($validationErrors) > self::MAX_MESSAGES_NUMBER) {
89+
array_splice($validationErrors, self::MAX_MESSAGES_NUMBER);
90+
$validationErrors[] = new Translation\Message(
91+
'Limit of %max_message_number% validation errors has been exceeded.',
92+
[
93+
'%max_message_number%' => self::MAX_MESSAGES_NUMBER,
94+
]
95+
);
9196
}
9297

93-
/** @var callable(string|\Ibexa\Contracts\Core\Repository\Values\Translation): string $convertToString */
94-
$convertToString = static function ($error): string {
95-
return (string)$error;
96-
};
97-
$validationErrors = array_map($convertToString, $validationErrors);
98+
$validationErrors = array_map(
99+
static fn (Translation $error): string => (string)$error,
100+
$validationErrors
101+
);
98102

99103
return "\n- " . implode("\n- ", $validationErrors);
100104
}

0 commit comments

Comments
 (0)