Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Image validation rule #670

Merged
merged 24 commits into from Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Expand Up @@ -26,7 +26,7 @@ jobs:
phpunit:
uses: yiisoft/actions/.github/workflows/phpunit.yml@master
with:
extensions: intl
extensions: intl,fileinfo
coverage: xdebug
os: >-
['ubuntu-latest', 'windows-latest']
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

- New #665: Add methods `addErrorWithFormatOnly()` and `addErrorWithoutPostProcessing()` to `Result` object (@vjik)
- Enh #668: Clarify psalm types in `Result` (@vjik)
- New #670: Add `Image` validation rule (@vjik, @arogachev)

## 1.2.0 February 21, 2024

Expand Down
1 change: 1 addition & 0 deletions composer-require-checker.json
Expand Up @@ -15,6 +15,7 @@
"Reflection",
"SPL",
"standard",
"fileinfo",
"intl"
],
"scan-files": []
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Expand Up @@ -30,11 +30,12 @@
"php": "^8.0",
"ext-mbstring": "*",
"psr/container": "^1.0|^2.0",
"psr/http-message": "^1.0|^2.0",
"yiisoft/arrays": "^2.1|^3.0",
"yiisoft/translator": "^2.1|^3.0",
"yiisoft/friendly-exception": "^1.0",
"yiisoft/network-utilities": "^1.0",
"yiisoft/strings": "^2.1"
"yiisoft/strings": "^2.1",
"yiisoft/translator": "^2.1|^3.0"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "^1.0",
Expand All @@ -52,6 +53,7 @@
},
"suggest": {
"ext-intl": "Allows using IDN validation for emails",
"ext-fileinfo": "To use image rule",
"yiisoft/di": "To create rule handlers via Yii DI"
},
"autoload": {
Expand Down
4 changes: 4 additions & 0 deletions docs/guide/en/built-in-rules.md
Expand Up @@ -45,6 +45,10 @@ Here is a list of all available built-in rules, divided by category.
- [Count](../../../src/Rule/Count.php)
- [OneOf](../../../src/Rule/OneOf.php)

### File rules

- [Image](../../../src/Rule/Image/Image.php)

### General purpose rules

- [Callback](../../../src/Rule/Callback.php)
Expand Down
30 changes: 30 additions & 0 deletions src/Rule/Image/CompositeImageInfoProvider.php
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Validator\Rule\Image;

final class CompositeImageInfoProvider implements ImageInfoProviderInterface
{
/**
* @var ImageInfoProviderInterface[]
*/
private array $providers;

public function __construct(
ImageInfoProviderInterface ...$providers
) {
$this->providers = $providers;
}

public function get(string $path): ?ImageInfo
{
foreach ($this->providers as $provider) {
$info = $provider->get($path);
if ($info !== null) {
return $info;
}
}
return null;
}
}
245 changes: 245 additions & 0 deletions src/Rule/Image/Image.php
@@ -0,0 +1,245 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Validator\Rule\Image;

use Attribute;
use Closure;
use Yiisoft\Validator\Rule\Trait\SkipOnEmptyTrait;
use Yiisoft\Validator\Rule\Trait\SkipOnErrorTrait;
use Yiisoft\Validator\Rule\Trait\WhenTrait;
use Yiisoft\Validator\RuleWithOptionsInterface;
use Yiisoft\Validator\SkipOnEmptyInterface;
use Yiisoft\Validator\SkipOnErrorInterface;
use Yiisoft\Validator\WhenInterface;

/**
* Defines validation options to check that a value is an image with a certain dimensions (optionally).
vjik marked this conversation as resolved.
Show resolved Hide resolved
*
* > Currently not adapted for using with HEIF / HEIC formats.
*
* @see ImageHandler
*
* @psalm-import-type SkipOnEmptyValue from SkipOnEmptyInterface
* @psalm-import-type WhenType from WhenInterface
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Image implements RuleWithOptionsInterface, SkipOnErrorInterface, WhenInterface, SkipOnEmptyInterface
{
use SkipOnEmptyTrait;
use SkipOnErrorTrait;
use WhenTrait;

/**
* @param int|null $width Expected exact width of validated image file.
* @param int|null $height Expected exact height of validated image file.
* @param int|null $minWidth Expected minimum width of validated image file.
* @param int|null $minHeight Expected minimum height of validated image file.
* @param int|null $maxWidth Expected maximum width of validated image file.
* @param int|null $maxHeight Expected maximum height of validated image file.
* @param string $notImageMessage A message used when the validated value is not valid image file.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
*
* @param string $notExactWidthMessage A message used when the width of validated image file doesn't exactly equal
* to {@see $width}.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
* - `{exactly}`: expected exact width of validated image file.
*
* @param string $notExactHeightMessage A message used when the height of validated image file doesn't exactly equal
* to {@see $height}.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
* - `{exactly}`: expected exact height of validated image file.
*
* @param string $tooSmallWidthMessage A message used when the width of validated image file is less than
* {@see $minWidth}.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
* - `{limit}`: expected minimum width of validated image file.
*
* @param string $tooSmallHeightMessage A message used when the height of validated image file is less than
* {@see $minHeight}.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
* - `{limit}`: expected minimum height of validated image file.
*
* @param string $tooLargeWidthMessage A message used when the width of validated image file is more than
* {@see $maxWidth}.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
* - `{limit}`: expected maximum width of validated image file.
*
* @param string $tooLargeHeightMessage A message used when the height of validated image file is more than
* {@see $maxHeight}.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the translated label of the attribute being validated.
* - `{limit}`: expected maximum height of validated image file.
* @param bool|callable|null $skipOnEmpty Whether to skip this rule if the value validated is empty.
* See {@see SkipOnEmptyInterface}.
* @param bool $skipOnError Whether to skip this rule if any of the previous rules gave an error.
* See {@see SkipOnErrorInterface}.
* @param Closure|null $when A callable to define a condition for applying the rule. See {@see WhenInterface}.
*
* @psalm-param SkipOnEmptyValue $skipOnEmpty
* @psalm-param WhenType $when
*/
public function __construct(
private ?int $width = null,
private ?int $height = null,
private ?int $minWidth = null,
private ?int $minHeight = null,
private ?int $maxWidth = null,
private ?int $maxHeight = null,
private string $notImageMessage = 'The value must be an image.',
private string $notExactWidthMessage = 'The width of image "{attribute}" must be exactly {exactly, number} {exactly, plural, one{pixel} other{pixels}}.',
private string $notExactHeightMessage = 'The height of image "{attribute}" must be exactly {exactly, number} {exactly, plural, one{pixel} other{pixels}}.',
private string $tooSmallWidthMessage = 'The width of image "{attribute}" cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.',
private string $tooSmallHeightMessage = 'The height of image "{attribute}" cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.',
private string $tooLargeWidthMessage = 'The width of image "{attribute}" cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.',
private string $tooLargeHeightMessage = 'The height of image "{attribute}" cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.',
private mixed $skipOnEmpty = null,
private bool $skipOnError = false,
private Closure|null $when = null,
) {
}

public function getWidth(): ?int
{
return $this->width;
}

public function getHeight(): ?int
{
return $this->height;
}

public function getMinWidth(): ?int
{
return $this->minWidth;
}

public function getMinHeight(): ?int
{
return $this->minHeight;
}

public function getMaxWidth(): ?int
{
return $this->maxWidth;
}

public function getMaxHeight(): ?int
{
return $this->maxHeight;
}

public function getNotImageMessage(): string
{
return $this->notImageMessage;
}

public function getNotExactWidthMessage(): string
{
return $this->notExactWidthMessage;
}

public function getNotExactHeightMessage(): string
{
return $this->notExactHeightMessage;
}

public function getTooSmallWidthMessage(): string
{
return $this->tooSmallWidthMessage;
}

public function getTooSmallHeightMessage(): string
{
return $this->tooSmallHeightMessage;
}

public function getTooLargeWidthMessage(): string
{
return $this->tooLargeWidthMessage;
}

public function getTooLargeHeightMessage(): string
{
return $this->tooLargeHeightMessage;
}

public function getName(): string
{
return 'image';
}

public function getHandler(): string
{
return ImageHandler::class;
}

public function getOptions(): array
{
return [
'notExactWidthMessage' => [
'template' => $this->notExactWidthMessage,
'parameters' => [
'exactly' => $this->width,
],
],
'notExactHeightMessage' => [
'template' => $this->notExactHeightMessage,
'parameters' => [
'exactly' => $this->height,
],
],
'tooSmallWidthMessage' => [
'template' => $this->tooSmallWidthMessage,
'parameters' => [
'limit' => $this->minWidth,
],
],
'tooSmallHeightMessage' => [
'template' => $this->tooSmallHeightMessage,
'parameters' => [
'limit' => $this->minHeight,
],
],
'tooLargeWidthMessage' => [
'template' => $this->tooLargeWidthMessage,
'parameters' => [
'limit' => $this->maxWidth,
],
],
'tooLargeHeightMessage' => [
'template' => $this->tooLargeHeightMessage,
'parameters' => [
'limit' => $this->maxHeight,
],
],
'notImageMessage' => [
'template' => $this->notImageMessage,
'parameters' => [],
],
'skipOnEmpty' => $this->getSkipOnEmptyOption(),
'skipOnError' => $this->skipOnError,
];
}
}