Upgrade project to PHP 8.3, add PHPStan, upgrade almost every dependency (#36)

* start updating to PHP 8.3

* taking off!

Co-authored-by: ErickSkrauch <erickskrauch@yandex.ru>
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* dropped this

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* migrate to symfonymailer

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* this is so stupid 😭

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* ah, free, at last.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* oh, Gabriel.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* now dawns thy reckoning.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* and thy gore shall GLISTEN before the temples of man.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* creature of steel.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* my gratitude upon thee for my freedom.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* but the crimes thy kind has committed against humanity

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* Upgrade PHP-CS-Fixer and do fix the codebase

* First review round (maybe I have broken something)

* are NOT forgotten.

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>

* Enable parallel PHP-CS-Fixer runner

* PHPStan level 1

* PHPStan level 2

* PHPStan level 3

* PHPStan level 4

* PHPStan level 5

* Levels 6 and 7 takes too much effort. Generate a baseline and fix them eventually

* Resolve TODO's related to the php-mock

* Drastically reduce baseline size with the Rector

* More code modernization with help of the Rector

* Update GitLab CI

---------

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
Co-authored-by: ErickSkrauch <erickskrauch@yandex.ru>
This commit is contained in:
Octol1ttle
2024-12-02 15:10:55 +05:00
committed by GitHub
parent 625250b367
commit 57d492da8a
356 changed files with 10531 additions and 4761 deletions

View File

@@ -54,7 +54,7 @@ class PrimaryKeyValueBehavior extends Behavior {
$owner = $this->owner;
$primaryKeys = $owner::primaryKey();
if (!isset($primaryKeys[0])) {
throw new InvalidConfigException('"' . get_class($owner) . '" must have a primary key.');
throw new InvalidConfigException('"' . $owner::class . '" must have a primary key.');
}
if (count($primaryKeys) > 1) {

View File

@@ -3,7 +3,7 @@ actor_suffix: Tester
bootstrap: _bootstrap.php
paths:
tests: tests
log: tests/_output
output: tests/_output
data: tests/_data
helpers: tests/_support
settings:

View File

@@ -8,15 +8,11 @@ use GuzzleHttp\ClientInterface;
class Api {
private $baseUrl;
private ?\GuzzleHttp\ClientInterface $client = null;
/**
* @var ClientInterface
*/
private $client;
public function __construct(string $baseUrl) {
$this->baseUrl = $baseUrl;
public function __construct(
private readonly string $baseUrl,
) {
}
public function setClient(ClientInterface $client): void {

View File

@@ -26,10 +26,7 @@ class Component extends \yii\base\Component implements RendererInterface {
*/
public $basePath = '';
/**
* @var Api
*/
private $api;
private ?Api $api = null;
public function init(): void {
parent::init();
@@ -47,11 +44,8 @@ class Component extends \yii\base\Component implements RendererInterface {
}
/**
* @param string $templateName
* @param string $locale
* @param array $params
* @param array<string, mixed> $params
*
* @return string
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function render(string $templateName, string $locale, array $params = []): string {

View File

@@ -5,25 +5,11 @@ namespace common\components\EmailsRenderer\Request;
class TemplateRequest {
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $locale;
/**
* @var array
*/
private $params;
public function __construct(string $name, string $locale, array $params) {
$this->name = $name;
$this->locale = $locale;
$this->params = $params;
public function __construct(
private readonly string $name,
private readonly string $locale,
private readonly array $params,
) {
}
public function getName(): string {

View File

@@ -12,15 +12,20 @@ use ReflectionClass;
class ElyDecorator implements DecoratorInterface {
private const LOGO = __DIR__ . '/resources/logo.svg';
private const string LOGO = __DIR__ . '/resources/logo.svg';
private const CORRECTION_MAP = [
private const array CORRECTION_MAP = [
ErrorCorrectionLevel::L => 7,
ErrorCorrectionLevel::M => 15,
ErrorCorrectionLevel::Q => 25,
ErrorCorrectionLevel::H => 30,
];
/**
* @throws \ImagickException
* @throws \ImagickPixelException
* @throws \ImagickPixelIteratorException
*/
public function preProcess(
QrCode $qrCode,
RendererInterface $renderer,
@@ -28,8 +33,8 @@ class ElyDecorator implements DecoratorInterface {
$outputHeight,
$leftPadding,
$topPadding,
$multiple
) {
$multiple,
): void {
if (!$renderer instanceof Svg) {
throw new InvalidArgumentException('$renderer must by instance of ' . Svg::class);
}
@@ -38,11 +43,11 @@ class ElyDecorator implements DecoratorInterface {
$sizeMultiplier = $correctionLevel + floor($correctionLevel / 3);
$count = $qrCode->getMatrix()->getWidth();
$countToRemoveX = floor($count * $sizeMultiplier / 100);
$countToRemoveY = floor($count * $sizeMultiplier / 100);
$countToRemoveX = (int)floor($count * $sizeMultiplier / 100);
$countToRemoveY = (int)floor($count * $sizeMultiplier / 100);
$startX = $leftPadding + round(($count - $countToRemoveX) / 2 * $multiple);
$startY = $topPadding + round(($count - $countToRemoveY) / 2 * $multiple);
$startX = (int)($leftPadding + round(($count - $countToRemoveX) / 2 * $multiple));
$startY = (int)($topPadding + round(($count - $countToRemoveY) / 2 * $multiple));
$width = $countToRemoveX * $multiple;
$height = $countToRemoveY * $multiple;
@@ -52,11 +57,12 @@ class ElyDecorator implements DecoratorInterface {
/** @var \SimpleXMLElement $svg */
$svg = $property->getValue($renderer);
/** @var \SimpleXMLElement $image */
$image = $svg->addChild('image');
$image->addAttribute('x', $startX);
$image->addAttribute('y', $startY);
$image->addAttribute('width', $width);
$image->addAttribute('height', $height);
$image->addAttribute('x', (string)$startX);
$image->addAttribute('y', (string)$startY);
$image->addAttribute('width', (string)$width);
$image->addAttribute('height', (string)$height);
$image->addAttribute('xlink:href', $this->encodeSvgToBase64(self::LOGO));
$logo = new Imagick();
@@ -89,8 +95,8 @@ class ElyDecorator implements DecoratorInterface {
for ($i = $x - $padding; $i <= $x + $padding; $i++) {
for ($j = $y - $padding; $j <= $y + $padding; $j++) {
$matrixX = floor($i / $multiple);
$matrixY = floor($j / $multiple);
$matrixX = (int)floor($i / $multiple);
$matrixY = (int)floor($j / $multiple);
$qrCode->getMatrix()->set($matrixX, $matrixY, 0);
}
}
@@ -104,7 +110,7 @@ class ElyDecorator implements DecoratorInterface {
$outputHeight,
$leftPadding,
$topPadding,
$multiple
$multiple,
) {
}

View File

@@ -3,18 +3,20 @@ declare(strict_types=1);
namespace common\components;
use mito\sentry\Component;
use nohnaimer\sentry\Component;
use Yii;
class Sentry extends Component {
public function init() {
public bool $enabled = true;
public function init(): void {
if (!$this->enabled) {
return;
}
if (is_array($this->client) && !isset($this->client['release'])) {
$this->client['release'] = Yii::$app->version;
if (is_array($this->clientOptions) && !isset($this->clientOptions['release'])) {
$this->clientOptions['release'] = Yii::$app->version;
}
parent::init();

View File

@@ -10,12 +10,9 @@ use Yii;
// TODO: convert to complete Chrly client library
class SkinsSystemApi {
private string $baseDomain;
private ?ClientInterface $client = null;
public function __construct(string $baseDomain) {
$this->baseDomain = $baseDomain;
public function __construct(private readonly string $baseDomain) {
}
/**

View File

@@ -9,22 +9,13 @@ use yii\base\Component;
class StatsD extends Component {
/**
* @var string
*/
public $host;
public string $host;
/**
* @var int
*/
public $port = 8125;
public int $port = 8125;
/**
* @var string
*/
public $namespace = '';
public string $namespace = '';
private $client;
private ?Client $client = null;
public function inc(string $key): void {
$this->getClient()->increment($key);

View File

@@ -5,7 +5,7 @@ namespace common\components;
class UserFriendlyRandomKey {
public static function make(int $length = 18) {
public static function make(int $length = 18): string {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
$numChars = strlen($chars);
$key = '';

View File

@@ -8,8 +8,8 @@ namespace common\components;
*/
class UserPass {
public static function make($email, $pass) {
return md5($pass . md5(strtolower($email)));
public static function make($email, string $pass): string {
return md5($pass . md5(strtolower((string)$email)));
}
}

View File

@@ -5,14 +5,11 @@ namespace common\config;
use yii\helpers\ArrayHelper;
class ConfigLoader {
final readonly class ConfigLoader {
private const ROOT_PATH = __DIR__ . '/../..';
private const string ROOT_PATH = __DIR__ . '/../..';
private $application;
public function __construct(string $application) {
$this->application = $application;
public function __construct(private string $application) {
}
public function getEnvironment(): string {
@@ -54,11 +51,15 @@ class ConfigLoader {
$toMerge[] = require $path;
}
// @phpstan-ignore arguments.count (Should be covered by Yii2 extension)
return ArrayHelper::merge(...$toMerge);
}
/**
* @return array<string, mixed>
*/
public static function load(string $application): array {
return (new static($application))->getConfig();
return (new self($application))->getConfig();
}
}

View File

@@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
defined('YII_ENV') ?: define('YII_ENV', 'test');
return common\config\ConfigLoader::load('api');

View File

@@ -1,4 +1,5 @@
<?php
return [
'version' => '{{PLACE_VERSION_HERE}}', // This will be replaced by build tool
'vendorPath' => dirname(__DIR__, 2) . '/vendor',
@@ -55,10 +56,10 @@ return [
],
],
'mailer' => [
'class' => yii\swiftmailer\Mailer::class,
'class' => yii\symfonymailer\Mailer::class,
'viewPath' => '@common/mail',
'transport' => [
'class' => Swift_SmtpTransport::class,
'class' => Symfony\Component\Mailer\Transport\Smtp\SmtpTransport::class,
'host' => getenv('SMTP_HOST'),
'username' => getenv('SMTP_USER'),
'password' => getenv('SMTP_PASS'),
@@ -104,7 +105,7 @@ return [
'statsd' => [
'class' => common\components\StatsD::class,
'host' => getenv('STATSD_HOST'),
'port' => getenv('STATSD_PORT') ?: 8125,
'port' => (int)getenv('STATSD_PORT') ?: 8125,
'namespace' => getenv('STATSD_NAMESPACE') ?: 'ely.accounts.' . gethostname() . '.app',
],
'queue' => [

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace common\db\mysql;
use SamIT\Yii2\MariaDb\QueryBuilder as MariaDbQueryBuilder;
use yii\db\ExpressionInterface;
use yii\db\Expression;
class QueryBuilder extends MariaDbQueryBuilder {
@@ -15,7 +15,7 @@ class QueryBuilder extends MariaDbQueryBuilder {
$orders = [];
foreach ($columns as $name => $direction) {
if ($direction instanceof ExpressionInterface) {
if ($direction instanceof Expression) {
$orders[] = $direction->expression;
} elseif (is_array($direction)) {
// This condition branch is our custom solution

View File

@@ -10,13 +10,9 @@ use yii\mail\MessageInterface;
abstract class Template {
/**
* @var MailerInterface
*/
private $mailer;
public function __construct(MailerInterface $mailer) {
$this->mailer = $mailer;
public function __construct(
private readonly MailerInterface $mailer,
) {
}
abstract public function getSubject(): string;
@@ -25,7 +21,7 @@ abstract class Template {
* @return array|string
* @throws InvalidConfigException
*/
public function getFrom() {
public function getFrom(): array|string {
$fromEmail = Yii::$app->params['fromEmail'] ?? '';
if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params');
@@ -34,25 +30,31 @@ abstract class Template {
return [$fromEmail => 'Ely.by Accounts'];
}
/**
* @return array<string, mixed>
*/
public function getParams(): array {
return [];
}
/**
* @param string|array $to see \yii\mail\MessageInterface::setTo to know the format.
* @param array|string $to see \yii\mail\MessageInterface::setTo to know the format.
*
* @throws \common\emails\exceptions\CannotSendEmailException
*/
public function send($to): void {
public function send(array|string $to): void {
if (!$this->createMessage($to)->send()) {
throw new exceptions\CannotSendEmailException();
}
}
/**
* @return string|array
* @return string|array{
* html?: string,
* text?: string,
* }
*/
abstract protected function getView();
abstract protected function getView(): string|array;
final protected function getMailer(): MailerInterface {
return $this->mailer;

View File

@@ -9,19 +9,16 @@ use yii\mail\MessageInterface;
abstract class TemplateWithRenderer extends Template {
/**
* @var RendererInterface
*/
private $renderer;
/**
* @var string
*/
private $locale = 'en';
private string $locale = 'en';
public function __construct(MailerInterface $mailer, RendererInterface $renderer) {
public function __construct(
MailerInterface $mailer,
private readonly RendererInterface $renderer,
) {
parent::__construct($mailer);
$this->renderer = $renderer;
}
public function setLocale(string $locale): void {
@@ -44,7 +41,7 @@ abstract class TemplateWithRenderer extends Template {
return $this->renderer;
}
final protected function getView() {
final protected function getView(): string {
return $this->getTemplateName();
}

View File

@@ -8,10 +8,7 @@ use yii\base\InvalidCallException;
class ChangeEmail extends Template {
/**
* @var string|null
*/
private $key;
private ?string $key = null;
public function setKey(string $key): void {
$this->key = $key;
@@ -31,7 +28,7 @@ class ChangeEmail extends Template {
];
}
protected function getView() {
protected function getView(): array {
return [
'html' => '@common/emails/views/current-email-confirmation-html',
'text' => '@common/emails/views/current-email-confirmation-text',

View File

@@ -8,15 +8,9 @@ use yii\base\InvalidCallException;
class ConfirmNewEmail extends Template {
/**
* @var string|null
*/
private $username;
private ?string $username = null;
/**
* @var string|null
*/
private $key;
private ?string $key = null;
public function setUsername(string $username): void {
$this->username = $username;
@@ -41,7 +35,7 @@ class ConfirmNewEmail extends Template {
];
}
protected function getView() {
protected function getView(): array {
return [
'html' => '@common/emails/views/new-email-confirmation-html',
'text' => '@common/emails/views/new-email-confirmation-text',

View File

@@ -8,10 +8,7 @@ use yii\base\InvalidCallException;
class ForgotPasswordEmail extends TemplateWithRenderer {
/**
* @var ForgotPasswordParams|null
*/
private $params;
private ?ForgotPasswordParams $params = null;
public function getSubject(): string {
return 'Ely.by Account forgot password';
@@ -31,9 +28,9 @@ class ForgotPasswordEmail extends TemplateWithRenderer {
}
return [
'username' => $this->params->getUsername(),
'code' => $this->params->getCode(),
'link' => $this->params->getLink(),
'username' => $this->params->username,
'code' => $this->params->code,
'link' => $this->params->link,
];
}

View File

@@ -3,30 +3,13 @@ declare(strict_types=1);
namespace common\emails\templates;
class ForgotPasswordParams {
final readonly class ForgotPasswordParams {
private $username;
private $code;
private $link;
public function __construct(string $username, string $code, string $link) {
$this->username = $username;
$this->code = $code;
$this->link = $link;
}
public function getUsername(): string {
return $this->username;
}
public function getCode(): string {
return $this->code;
}
public function getLink(): string {
return $this->link;
public function __construct(
public string $username,
public string $code,
public string $link,
) {
}
}

View File

@@ -8,10 +8,7 @@ use yii\base\InvalidCallException;
class RegistrationEmail extends TemplateWithRenderer {
/**
* @var RegistrationEmailParams|null
*/
private $params;
private ?RegistrationEmailParams $params = null;
public function getSubject(): string {
return 'Ely.by Account registration';

View File

@@ -5,16 +5,11 @@ namespace common\emails\templates;
class RegistrationEmailParams {
private $username;
private $code;
private $link;
public function __construct(string $username, string $code, string $link) {
$this->username = $username;
$this->code = $code;
$this->link = $link;
public function __construct(
private readonly string $username,
private readonly string $code,
private readonly string $link,
) {
}
public function getUsername(): string {

View File

@@ -1,8 +0,0 @@
<?php
namespace common\helpers;
use common\components\RabbitMQ\Helper;
class Amqp extends Helper {
}

View File

@@ -3,71 +3,71 @@ namespace common\helpers;
final class Error {
public const USERNAME_REQUIRED = 'error.username_required';
public const USERNAME_TOO_SHORT = 'error.username_too_short';
public const USERNAME_TOO_LONG = 'error.username_too_long';
public const USERNAME_INVALID = 'error.username_invalid';
public const USERNAME_NOT_AVAILABLE = 'error.username_not_available';
public const string USERNAME_REQUIRED = 'error.username_required';
public const string USERNAME_TOO_SHORT = 'error.username_too_short';
public const string USERNAME_TOO_LONG = 'error.username_too_long';
public const string USERNAME_INVALID = 'error.username_invalid';
public const string USERNAME_NOT_AVAILABLE = 'error.username_not_available';
public const EMAIL_REQUIRED = 'error.email_required';
public const EMAIL_TOO_LONG = 'error.email_too_long';
public const EMAIL_INVALID = 'error.email_invalid';
public const EMAIL_IS_TEMPMAIL = 'error.email_is_tempmail';
public const EMAIL_HOST_IS_NOT_ALLOWED = 'error.email_host_is_not_allowed';
public const EMAIL_NOT_AVAILABLE = 'error.email_not_available';
public const EMAIL_NOT_FOUND = 'error.email_not_found';
public const string EMAIL_REQUIRED = 'error.email_required';
public const string EMAIL_TOO_LONG = 'error.email_too_long';
public const string EMAIL_INVALID = 'error.email_invalid';
public const string EMAIL_IS_TEMPMAIL = 'error.email_is_tempmail';
public const string EMAIL_HOST_IS_NOT_ALLOWED = 'error.email_host_is_not_allowed';
public const string EMAIL_NOT_AVAILABLE = 'error.email_not_available';
public const string EMAIL_NOT_FOUND = 'error.email_not_found';
public const LOGIN_REQUIRED = 'error.login_required';
public const LOGIN_NOT_EXIST = 'error.login_not_exist';
public const string LOGIN_REQUIRED = 'error.login_required';
public const string LOGIN_NOT_EXIST = 'error.login_not_exist';
public const PASSWORD_REQUIRED = 'error.password_required';
public const PASSWORD_INCORRECT = 'error.password_incorrect';
public const PASSWORD_TOO_SHORT = 'error.password_too_short';
public const string PASSWORD_REQUIRED = 'error.password_required';
public const string PASSWORD_INCORRECT = 'error.password_incorrect';
public const string PASSWORD_TOO_SHORT = 'error.password_too_short';
public const KEY_REQUIRED = 'error.key_required';
public const KEY_NOT_EXISTS = 'error.key_not_exists';
public const KEY_EXPIRE = 'error.key_expire';
public const string KEY_REQUIRED = 'error.key_required';
public const string KEY_NOT_EXISTS = 'error.key_not_exists';
public const string KEY_EXPIRE = 'error.key_expire';
public const ACCOUNT_BANNED = 'error.account_banned';
public const ACCOUNT_NOT_ACTIVATED = 'error.account_not_activated';
public const ACCOUNT_ALREADY_ACTIVATED = 'error.account_already_activated';
public const ACCOUNT_CANNOT_RESEND_MESSAGE = 'error.account_cannot_resend_message';
public const string ACCOUNT_BANNED = 'error.account_banned';
public const string ACCOUNT_NOT_ACTIVATED = 'error.account_not_activated';
public const string ACCOUNT_ALREADY_ACTIVATED = 'error.account_already_activated';
public const string ACCOUNT_CANNOT_RESEND_MESSAGE = 'error.account_cannot_resend_message';
public const RECENTLY_SENT_MESSAGE = 'error.recently_sent_message';
public const string RECENTLY_SENT_MESSAGE = 'error.recently_sent_message';
public const NEW_PASSWORD_REQUIRED = 'error.newPassword_required';
public const NEW_RE_PASSWORD_REQUIRED = 'error.newRePassword_required';
public const NEW_RE_PASSWORD_DOES_NOT_MATCH = self::RE_PASSWORD_DOES_NOT_MATCH;
public const string NEW_PASSWORD_REQUIRED = 'error.newPassword_required';
public const string NEW_RE_PASSWORD_REQUIRED = 'error.newRePassword_required';
public const string NEW_RE_PASSWORD_DOES_NOT_MATCH = self::RE_PASSWORD_DOES_NOT_MATCH;
public const REFRESH_TOKEN_REQUIRED = 'error.refresh_token_required';
public const REFRESH_TOKEN_NOT_EXISTS = 'error.refresh_token_not_exist';
public const string REFRESH_TOKEN_REQUIRED = 'error.refresh_token_required';
public const string REFRESH_TOKEN_NOT_EXISTS = 'error.refresh_token_not_exist';
public const CAPTCHA_REQUIRED = 'error.captcha_required';
public const CAPTCHA_INVALID = 'error.captcha_invalid';
public const string CAPTCHA_REQUIRED = 'error.captcha_required';
public const string CAPTCHA_INVALID = 'error.captcha_invalid';
public const RULES_AGREEMENT_REQUIRED = 'error.rulesAgreement_required';
public const string RULES_AGREEMENT_REQUIRED = 'error.rulesAgreement_required';
public const RE_PASSWORD_REQUIRED = 'error.rePassword_required';
public const RE_PASSWORD_DOES_NOT_MATCH = 'error.rePassword_does_not_match';
public const string RE_PASSWORD_REQUIRED = 'error.rePassword_required';
public const string RE_PASSWORD_DOES_NOT_MATCH = 'error.rePassword_does_not_match';
public const UNSUPPORTED_LANGUAGE = 'error.unsupported_language';
public const string UNSUPPORTED_LANGUAGE = 'error.unsupported_language';
public const SUBJECT_REQUIRED = 'error.subject_required';
public const MESSAGE_REQUIRED = 'error.message_required';
public const string SUBJECT_REQUIRED = 'error.subject_required';
public const string MESSAGE_REQUIRED = 'error.message_required';
public const TOTP_REQUIRED = 'error.totp_required';
public const TOTP_INCORRECT = 'error.totp_incorrect';
public const string TOTP_REQUIRED = 'error.totp_required';
public const string TOTP_INCORRECT = 'error.totp_incorrect';
public const OTP_ALREADY_ENABLED = 'error.otp_already_enabled';
public const OTP_NOT_ENABLED = 'error.otp_not_enabled';
public const string OTP_ALREADY_ENABLED = 'error.otp_already_enabled';
public const string OTP_NOT_ENABLED = 'error.otp_not_enabled';
public const NAME_REQUIRED = 'error.name_required';
public const string NAME_REQUIRED = 'error.name_required';
public const REDIRECT_URI_REQUIRED = 'error.redirectUri_required';
public const REDIRECT_URI_INVALID = 'error.redirectUri_invalid';
public const string REDIRECT_URI_REQUIRED = 'error.redirectUri_required';
public const string REDIRECT_URI_INVALID = 'error.redirectUri_invalid';
public const WEBSITE_URL_INVALID = 'error.websiteUrl_invalid';
public const string WEBSITE_URL_INVALID = 'error.websiteUrl_invalid';
public const MINECRAFT_SERVER_IP_INVALID = 'error.minecraftServerIp_invalid';
public const string MINECRAFT_SERVER_IP_INVALID = 'error.minecraftServerIp_invalid';
}

View File

@@ -26,8 +26,9 @@ class StringHelper {
public static function isUuid(string $uuid): bool {
try {
Uuid::fromString($uuid);
} catch (\InvalidArgumentException $e) {
/** @throws \InvalidArgumentException */
Uuid::fromString($uuid); // @phpstan-ignore staticMethod.resultUnused (we don't care about the result, we need only an exception)
} catch (\InvalidArgumentException) {
return false;
}

View File

@@ -1,9 +1,11 @@
<?php
use yii\helpers\Html;
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */
/**
* @var \yii\web\View $this view component instance
* @var \yii\mail\MessageInterface $message the message being composed
* @var string $content main view render result
*/
?>
<?php $this->beginPage() ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

View File

@@ -1,8 +1,8 @@
<?php
/**
* @var $this \yii\web\View view component instance
* @var $message \yii\mail\MessageInterface the message being composed
* @var $content string main view render result
* @var \yii\web\View $this view component instance
* @var \yii\mail\MessageInterface $message the message being composed
* @var string $content main view render result
*/
?>

View File

@@ -30,7 +30,7 @@ use const common\LATEST_RULES_VERSION;
* @property int|null $rules_agreement_version
* @property string|null $registration_ip
* @property string|null $otp_secret
* @property int $is_otp_enabled
* @property bool $is_otp_enabled
* @property int $created_at
* @property int $updated_at
* @property int $password_changed_at
@@ -81,16 +81,11 @@ class Account extends ActiveRecord {
$passwordHashStrategy = $this->password_hash_strategy;
}
switch ($passwordHashStrategy) {
case self::PASS_HASH_STRATEGY_OLD_ELY:
return UserPass::make($this->email, $password) === $this->password_hash;
case self::PASS_HASH_STRATEGY_YII2:
return Yii::$app->security->validatePassword($password, $this->password_hash);
default:
throw new InvalidConfigException('You must set valid password_hash_strategy before you can validate password');
}
return match ($passwordHashStrategy) {
self::PASS_HASH_STRATEGY_OLD_ELY => UserPass::make($this->email, $password) === $this->password_hash,
self::PASS_HASH_STRATEGY_YII2 => Yii::$app->security->validatePassword($password, $this->password_hash),
default => throw new InvalidConfigException('You must set valid password_hash_strategy before you can validate password'),
};
}
public function setPassword(string $password): void {
@@ -103,6 +98,9 @@ class Account extends ActiveRecord {
return $this->hasMany(EmailActivation::class, ['account_id' => 'id']);
}
/**
* @return \yii\db\ActiveQuery<\common\models\OauthSession>
*/
public function getOauthSessions(): ActiveQuery {
return $this->hasMany(OauthSession::class, ['account_id' => 'id']);
}
@@ -116,6 +114,9 @@ class Account extends ActiveRecord {
return $this->hasMany(UsernameHistory::class, ['account_id' => 'id']);
}
/**
* @return \yii\db\ActiveQuery<\common\models\AccountSession>
*/
public function getSessions(): ActiveQuery {
return $this->hasMany(AccountSession::class, ['account_id' => 'id']);
}

View File

@@ -8,12 +8,12 @@ use yii\db\ActiveRecord;
/**
* Fields:
* @property integer $id
* @property integer $account_id
* @property int $id
* @property int $account_id
* @property string $refresh_token
* @property integer $last_used_ip
* @property integer $created_at
* @property integer $last_refreshed_at
* @property int $last_used_ip
* @property int $created_at
* @property int $last_refreshed_at
*
* Relations:
* @property Account $account

View File

@@ -37,6 +37,9 @@ class EmailActivation extends ActiveRecord {
return 'email_activations';
}
/**
* @return array<self::TYPE_*, class-string<\common\models\EmailActivation>>
*/
public static function getClassMap(): array {
return [
self::TYPE_REGISTRATION_EMAIL_CONFIRMATION => confirmations\RegistrationConfirmation::class,
@@ -46,18 +49,16 @@ class EmailActivation extends ActiveRecord {
];
}
public static function instantiate($row) {
public static function instantiate($row): static {
$type = ArrayHelper::getValue($row, 'type');
if ($type === null) {
return parent::instantiate($row);
}
$classMap = self::getClassMap();
if (!isset($classMap[$type])) {
throw new InvalidConfigException('Unexpected type');
}
$className = self::getClassMap()[$type] ?? throw new InvalidConfigException('Unexpected type');
return new $classMap[$type]();
// @phpstan-ignore return.type (the type is correct, but it seems like it must be fixed within Yii2-extension)
return new $className();
}
public static function find(): EmailActivationQuery {
@@ -72,9 +73,7 @@ class EmailActivation extends ActiveRecord {
],
[
'class' => PrimaryKeyValueBehavior::class,
'value' => function(): string {
return UserFriendlyRandomKey::make();
},
'value' => fn(): string => UserFriendlyRandomKey::make(),
],
];
}

View File

@@ -6,9 +6,10 @@ namespace common\models;
use yii\db\ActiveQuery;
/**
* @extends \yii\db\ActiveQuery<\common\models\EmailActivation>
* @see EmailActivation
*/
class EmailActivationQuery extends ActiveQuery {
final class EmailActivationQuery extends ActiveQuery {
public function withType(int ...$typeId): self {
return $this->andWhere(['type' => $typeId]);

View File

@@ -11,7 +11,7 @@ use yii\db\Command;
*/
class OauthClientQuery extends ActiveQuery {
private $showDeleted = false;
private bool $showDeleted = false;
public function includeDeleted(): self {
$this->showDeleted = true;

View File

@@ -10,12 +10,9 @@ use Yii;
class Textures {
private const MAX_RETRIES = 3;
private const int MAX_RETRIES = 3;
protected Account $account;
public function __construct(Account $account) {
$this->account = $account;
public function __construct(protected Account $account) {
}
public function getMinecraftResponse(bool $signed = false): array {

View File

@@ -6,7 +6,7 @@ namespace common\notifications;
use common\models\Account;
use Webmozart\Assert\Assert;
final class AccountDeletedNotification implements NotificationInterface {
final readonly class AccountDeletedNotification implements NotificationInterface {
private Account $account;

View File

@@ -5,15 +5,12 @@ namespace common\notifications;
use common\models\Account;
final class AccountEditNotification implements NotificationInterface {
final readonly class AccountEditNotification implements NotificationInterface {
private Account $account;
private array $changedAttributes;
public function __construct(Account $account, array $changedAttributes) {
$this->account = $account;
$this->changedAttributes = $changedAttributes;
public function __construct(
private Account $account,
private array $changedAttributes,
) {
}
public static function getType(): string {

View File

@@ -6,7 +6,7 @@ namespace common\notifications;
use common\models\OauthSession;
use Webmozart\Assert\Assert;
final class OAuthSessionRevokedNotification implements NotificationInterface {
final readonly class OAuthSessionRevokedNotification implements NotificationInterface {
private OauthSession $oauthSession;

View File

@@ -7,12 +7,9 @@ use common\models\Account;
use Yii;
use yii\queue\RetryableJobInterface;
final class ClearAccountSessions implements RetryableJobInterface {
final readonly class ClearAccountSessions implements RetryableJobInterface {
private int $accountId;
public function __construct(int $accountId) {
$this->accountId = $accountId;
public function __construct(private int $accountId) {
}
/**

View File

@@ -9,16 +9,13 @@ use yii\queue\RetryableJobInterface;
final class ClearOauthSessions implements RetryableJobInterface {
public string $clientId;
/**
* @var int|null unix timestamp, that allows to limit this task to clear only some old sessions
*/
public ?int $notSince;
public function __construct(string $clientId, int $notSince = null) {
$this->clientId = $clientId;
$this->notSince = $notSince;
public function __construct(
public string $clientId,
/**
* @var int|null unix timestamp, that allows to limit this task to clear only some old sessions
*/
public ?int $notSince = null,
) {
}
public static function createFromOauthClient(OauthClient $client, int $notSince = null): self {
@@ -26,7 +23,7 @@ final class ClearOauthSessions implements RetryableJobInterface {
}
public function getTtr(): int {
return 60/*sec*/ * 5/*min*/;
return 60/* sec */ * 5/* min */;
}
public function canRetry($attempt, $error): bool {

View File

@@ -8,12 +8,9 @@ use common\notifications\NotificationInterface;
use yii\db\Expression;
use yii\queue\RetryableJobInterface;
final class CreateWebHooksDeliveries implements RetryableJobInterface {
final readonly class CreateWebHooksDeliveries implements RetryableJobInterface {
private NotificationInterface $notification;
public function __construct(NotificationInterface $notification) {
$this->notification = $notification;
public function __construct(private NotificationInterface $notification) {
}
public function getTtr(): int {

View File

@@ -7,12 +7,9 @@ use common\models\Account;
use Yii;
use yii\queue\RetryableJobInterface;
final class DeleteAccount implements RetryableJobInterface {
final readonly class DeleteAccount implements RetryableJobInterface {
private int $accountId;
public function __construct(int $accountId) {
$this->accountId = $accountId;
public function __construct(private int $accountId) {
}
public function getTtr(): int {

View File

@@ -20,7 +20,7 @@ class DeliveryWebHook implements RetryableJobInterface {
public string $url;
public ?string $secret;
public ?string $secret = null;
public array $payloads;

View File

@@ -13,12 +13,12 @@ use Webmozart\Assert\Assert;
use Yii;
use yii\queue\JobInterface;
class PullMojangUsername implements JobInterface {
final class PullMojangUsername implements JobInterface {
public $username;
public static function createFromAccount(Account $account): self {
$result = new static();
$result = new self();
$result->username = $account->username;
return $result;
@@ -29,17 +29,17 @@ class PullMojangUsername implements JobInterface {
*
* @throws \Exception
*/
public function execute($queue) {
public function execute($queue): void {
Yii::$app->statsd->inc('queue.pullMojangUsername.attempt');
/** @var MojangApi $mojangApi */
$mojangApi = Yii::$container->get(MojangApi::class);
try {
$response = $mojangApi->usernameToUUID($this->username);
Yii::$app->statsd->inc('queue.pullMojangUsername.found');
} catch (NoContentException $e) {
} catch (NoContentException) {
$response = false;
Yii::$app->statsd->inc('queue.pullMojangUsername.not_found');
} catch (GuzzleException | MojangApiException $e) {
} catch (GuzzleException|MojangApiException) {
Yii::$app->statsd->inc('queue.pullMojangUsername.error');
return;
}

View File

@@ -7,18 +7,22 @@ use common\emails\EmailHelper;
use common\emails\templates\ChangeEmail;
use common\models\confirmations\CurrentEmailConfirmation;
use Yii;
use yii\mail\MailerInterface;
use yii\queue\RetryableJobInterface;
class SendCurrentEmailConfirmation implements RetryableJobInterface {
public $email;
public mixed $email = null;
public $username;
public mixed $username = null;
public $code;
public mixed $code = null;
public function __construct(public MailerInterface $mailer) {
}
public static function createFromConfirmation(CurrentEmailConfirmation $confirmation): self {
$result = new self();
$result = new self(Yii::$app->mailer);
$result->email = $confirmation->account->email;
$result->username = $confirmation->account->username;
$result->code = $confirmation->key;
@@ -38,9 +42,9 @@ class SendCurrentEmailConfirmation implements RetryableJobInterface {
* @param \yii\queue\Queue $queue
* @throws \common\emails\exceptions\CannotSendEmailException
*/
public function execute($queue) {
public function execute($queue): void {
Yii::$app->statsd->inc('queue.sendCurrentEmailConfirmation.attempt');
$template = new ChangeEmail(Yii::$app->mailer);
$template = new ChangeEmail($this->mailer);
$template->setKey($this->code);
$template->send(EmailHelper::buildTo($this->username, $this->email));
}

View File

@@ -38,7 +38,7 @@ class SendNewEmailConfirmation implements RetryableJobInterface {
* @param \yii\queue\Queue $queue
* @throws \common\emails\exceptions\CannotSendEmailException
*/
public function execute($queue) {
public function execute($queue): void {
Yii::$app->statsd->inc('queue.sendNewEmailConfirmation.attempt');
$template = new ConfirmNewEmail(Yii::$app->mailer);
$template->setKey($this->code);

View File

@@ -47,7 +47,7 @@ class SendPasswordRecoveryEmail implements RetryableJobInterface {
* @param \yii\queue\Queue $queue
* @throws \common\emails\exceptions\CannotSendEmailException
*/
public function execute($queue) {
public function execute($queue): void {
Yii::$app->statsd->inc('queue.sendPasswordRecovery.attempt');
$template = new ForgotPasswordEmail(Yii::$app->mailer, Yii::$app->emailsRenderer);
$template->setLocale($this->locale);

View File

@@ -47,7 +47,7 @@ class SendRegistrationEmail implements RetryableJobInterface {
* @param \yii\queue\Queue $queue
* @throws \common\emails\exceptions\CannotSendEmailException
*/
public function execute($queue) {
public function execute($queue): void {
Yii::$app->statsd->inc('queue.sendRegistrationEmail.attempt');
$template = new RegistrationEmail(Yii::$app->mailer, Yii::$app->emailsRenderer);
$template->setLocale($this->locale);

View File

@@ -9,7 +9,7 @@ use Yii;
class ApplicationRedisBridge extends Module {
protected $config = [
protected array $config = [
'module' => 'Redis',
];

View File

@@ -16,7 +16,6 @@ use yii\test\InitDbFixture;
* TODO: try to remove
*/
class FixtureHelper extends Module {
/**
* Redeclare visibility because codeception includes all public methods that do not start with "_"
* and are not excluded by module settings, in actor class.
@@ -30,21 +29,21 @@ class FixtureHelper extends Module {
getFixture as protected;
}
public function _before(TestInterface $test) {
public function _before(TestInterface $test): void {
$this->loadFixtures();
}
public function _after(TestInterface $test) {
public function _after(TestInterface $test): void {
$this->unloadFixtures();
}
public function globalFixtures() {
public function globalFixtures(): array {
return [
InitDbFixture::class,
];
}
public function fixtures() {
public function fixtures(): array {
return [
'accounts' => fixtures\AccountFixture::class,
'accountSessions' => fixtures\AccountSessionFixture::class,

View File

@@ -16,10 +16,7 @@ class Fixture extends BaseFixture {
use ArrayAccessTrait;
use FileFixtureTrait;
/**
* @var Connection
*/
public $redis = 'redis';
public string|Connection $redis = 'redis';
public $keysPrefix = '';
@@ -27,12 +24,12 @@ class Fixture extends BaseFixture {
public $data = [];
public function init() {
public function init(): void {
parent::init();
$this->redis = Instance::ensure($this->redis, Connection::class);
}
public function load() {
public function load(): void {
$this->data = [];
foreach ($this->getData() as $key => $data) {
$key = $this->buildKey($key);
@@ -47,7 +44,7 @@ class Fixture extends BaseFixture {
}
}
public function unload() {
public function unload(): void {
$this->redis->flushdb();
}
@@ -75,7 +72,7 @@ class Fixture extends BaseFixture {
throw new InvalidArgumentException('Unsupported input type');
}
protected function buildKey($key): string {
protected function buildKey(string|int $key): string {
return $this->keysPrefix . $key . $this->keysPostfix;
}

View File

@@ -4,15 +4,14 @@ namespace common\tests\_support\queue;
use Codeception\Exception\ModuleException;
use Codeception\Module;
use Codeception\Module\Yii2;
use yii\queue\JobInterface;
class CodeceptionQueueHelper extends Module {
/**
* Returns last sent message
*
* @return \yii\queue\JobInterface|null
*/
public function grabLastQueuedJob() {
public function grabLastQueuedJob(): ?JobInterface {
$messages = $this->grabQueueJobs();
$last = end($messages);
if ($last === false) {
@@ -27,11 +26,10 @@ class CodeceptionQueueHelper extends Module {
* Each message is `\PhpAmqpLib\Message\AMQPMessage` instance.
* Useful to perform additional checks using `Asserts` module.
*
* @param string|null $exchange
* @return \yii\queue\JobInterface[]
* @throws ModuleException
*/
public function grabQueueJobs() {
public function grabQueueJobs(): array {
$amqp = $this->grabComponent('queue');
if (!$amqp instanceof Queue) {
throw new ModuleException($this, 'AMQP module is not mocked, can\'t test messages');

View File

@@ -6,15 +6,15 @@ use yii\queue\Queue as BaseQueue;
class Queue extends BaseQueue {
private $messages = [];
private array $messages = [];
public function __set($name, $value) {
// Yii2 components may contains some configuration
// But we just ignore it for this mock component
}
public function push($job) {
$this->messages[] = $job;
public function push($job): ?string {
return (string)array_push($this->messages, $job);
}
public function status($id) {
@@ -25,8 +25,8 @@ class Queue extends BaseQueue {
return $this->messages;
}
protected function pushMessage($message, $ttr, $delay, $priority) {
// This function is abstract, but will be not called
protected function pushMessage($message, $ttr, $delay, $priority): string {
return '';
}
}

View File

@@ -4,5 +4,4 @@ use common\config\ConfigLoader;
use yii\helpers\ArrayHelper;
return ArrayHelper::merge(ConfigLoader::load('common'), [
]);

View File

@@ -3,8 +3,9 @@ declare(strict_types=1);
namespace common\tests\helpers;
use phpmock\Deactivatable;
use phpmock\phpunit\MockObjectProxy;
use phpmock\phpunit\PHPMock;
use PHPUnit\Framework\MockObject\MockObject;
use ReflectionClass;
trait ExtendedPHPMock {
@@ -13,12 +14,35 @@ trait ExtendedPHPMock {
defineFunctionMock as private defineOriginalFunctionMock;
}
public function getFunctionMock($namespace, $name): MockObject {
return $this->getOriginalFunctionMock(static::getClassNamespace($namespace), $name);
/**
* @var Deactivatable[]
*/
private array $deactivatables = [];
public function getFunctionMock($namespace, $name): MockObjectProxy {
// @phpstan-ignore return.type
return $this->getOriginalFunctionMock(self::getClassNamespace($namespace), $name);
}
public static function defineFunctionMock($namespace, $name) {
static::defineOriginalFunctionMock(static::getClassNamespace($namespace), $name);
public static function defineFunctionMock($namespace, $name): void {
self::defineOriginalFunctionMock(self::getClassNamespace($namespace), $name);
}
/**
* Override this method since original implementation relies on the PHPUnit's state,
* but we're dealing with the Codeception, which uses different event system
*/
public function registerForTearDown(Deactivatable $deactivatable): void {
$this->deactivatables[] = $deactivatable;
}
protected function _after(): void {
parent::_after();
foreach ($this->deactivatables as $deactivatable) {
$deactivatable->disable();
}
$this->deactivatables = [];
}
private static function getClassNamespace(string $className): string {

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace common\tests\helpers;
use phpmock\mockery\PHPMockery;
use ReflectionClass;
class Mock {
/**
* @param string $className
* @param string $function
*
* @return \Mockery\Expectation
*/
public static function func(string $className, string $function) {
return PHPMockery::mock(self::getClassNamespace($className), $function);
}
public static function define(string $className, string $function): void {
PHPMockery::define(self::getClassNamespace($className), $function);
}
private static function getClassNamespace(string $className): string {
return (new ReflectionClass($className))->getNamespaceName();
}
}

View File

@@ -9,20 +9,18 @@ use yii\db\ActiveRecord;
class PrimaryKeyValueBehaviorTest extends TestCase {
public function testGenerateValueForThePrimaryKey() {
public function testGenerateValueForThePrimaryKey(): void {
$model = $this->createDummyModel();
$behavior = $this->createPartialMock(PrimaryKeyValueBehavior::class, ['isValueExists']);
$behavior->method('isValueExists')->willReturn(false);
$behavior->value = function() {
return 'mock';
};
$behavior->value = fn(): string => 'mock';
$model->attachBehavior('primary-key-value-behavior', $behavior);
$behavior->setPrimaryKeyValue();
$this->assertSame('mock', $model->id);
}
public function testShouldRegenerateValueWhenGeneratedAlreadyExists() {
public function testShouldRegenerateValueWhenGeneratedAlreadyExists(): void {
$model = $this->createDummyModel();
$behavior = $this->createPartialMock(PrimaryKeyValueBehavior::class, ['isValueExists', 'generateValue']);
$behavior->expects($this->exactly(3))->method('generateValue')->willReturnOnConsecutiveCalls('1', '2', '3');
@@ -33,11 +31,14 @@ class PrimaryKeyValueBehaviorTest extends TestCase {
$this->assertSame('3', $model->id);
}
private function createDummyModel() {
/**
* @return \yii\db\ActiveRecord&object{ id: string }
*/
private function createDummyModel(): ActiveRecord {
return new class extends ActiveRecord {
public $id;
public string $id;
public static function primaryKey() {
public static function primaryKey(): array {
return ['id'];
}
};

View File

@@ -14,20 +14,19 @@ use GuzzleHttp\Psr7\Response;
class ApiTest extends TestCase {
/**
* @var Api
*/
private $api;
private Api $api;
private MockHandler $mockHandler;
/**
* @var MockHandler
* @var array<array{
* request: \Psr\Http\Message\RequestInterface,
* response: \Psr\Http\Message\ResponseInterface,
* error: string|null,
* options: array<mixed>,
* }>
*/
private $mockHandler;
/**
* @var \Psr\Http\Message\RequestInterface[]
*/
private $history;
private array $history = [];
protected function setUp(): void {
parent::setUp();
@@ -44,7 +43,7 @@ class ApiTest extends TestCase {
$this->api->setClient($client);
}
public function testGetTemplate() {
public function testGetTemplate(): void {
$this->mockHandler->append(new Response(200, [], 'mock-response'));
$request = new TemplateRequest('mock-name', 'mock-locale', ['find-me' => 'please']);

View File

@@ -7,18 +7,13 @@ use common\components\EmailsRenderer\Api;
use common\components\EmailsRenderer\Component;
use common\components\EmailsRenderer\Request\TemplateRequest;
use common\tests\unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
class ComponentTest extends TestCase {
/**
* @var Api|\PHPUnit\Framework\MockObject\MockObject
*/
private $api;
private Api&MockObject $api;
/**
* @var Component
*/
private $component;
private Component $component;
protected function setUp(): void {
parent::setUp();
@@ -30,7 +25,7 @@ class ComponentTest extends TestCase {
'basePath' => '/images/emails-templates',
];
$this->component = new class($componentParams) extends Component {
public $api;
public Api $api;
protected function getApi(): Api {
return $this->api;
@@ -38,7 +33,7 @@ class ComponentTest extends TestCase {
};
}
public function testRender() {
public function testRender(): void {
$expectedRequest = new TemplateRequest('mock-name', 'mock-locale', [
'find-me' => 'please',
'assetsHost' => 'http://localhost/images/emails-templates',

View File

@@ -7,7 +7,7 @@ use Yii;
class QueryBuilderTest extends TestCase {
public function testBuildOrderByField() {
public function testBuildOrderByField(): void {
$queryBuilder = new QueryBuilder(Yii::$app->db);
$result = $queryBuilder->buildOrderBy(['dummy' => ['first', 'second']]);
$this->assertSame("ORDER BY FIELD(`dummy`,'first','second')", $result);

View File

@@ -6,7 +6,7 @@ use common\tests\unit\TestCase;
class EmailHelperTest extends TestCase {
public function testBuildTo() {
public function testBuildTo(): void {
$this->assertSame(['mock@ely.by' => 'username'], EmailHelper::buildTo('username', 'mock@ely.by'));
}

View File

@@ -6,37 +6,29 @@ namespace common\tests\unit\emails;
use common\emails\exceptions\CannotSendEmailException;
use common\emails\Template;
use common\tests\unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Yii;
use yii\mail\MailerInterface;
use yii\mail\MessageInterface;
class TemplateTest extends TestCase {
/**
* @var Template|\PHPUnit\Framework\MockObject\MockObject $template
*/
private $template;
private Template&MockObject $template;
/**
* @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $mailer;
private MailerInterface&MockObject $mailer;
/**
* @var string
*/
private $initialFromEmail;
private string $initialFromEmail;
public function testGetters() {
public function testGetters(): void {
$this->assertSame(['find-me' => 'Ely.by Accounts'], $this->template->getFrom());
$this->assertSame([], $this->template->getParams());
}
public function testSend() {
public function testSend(): void {
$this->runTestForSend(true);
}
public function testNotSend() {
public function testNotSend(): void {
$this->expectException(CannotSendEmailException::class);
$this->runTestForSend(false);
}
@@ -49,16 +41,15 @@ class TemplateTest extends TestCase {
Yii::$app->params['fromEmail'] = 'find-me';
}
protected function _after() {
protected function _after(): void {
parent::_after();
Yii::$app->params['fromEmail'] = $this->initialFromEmail;
}
private function runTestForSend(bool $sendResult) {
private function runTestForSend(bool $sendResult): void {
$this->template->expects($this->once())->method('getSubject')->willReturn('mock-subject');
$this->template->expects($this->once())->method('getView')->willReturn('mock-view');
/** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $message */
$message = $this->createMock(MessageInterface::class);
$message->expects($this->once())->method('setTo')->with(['to@ely.by' => 'To'])->willReturnSelf();
$message->expects($this->once())->method('setFrom')->with(['find-me' => 'Ely.by Accounts'])->willReturnSelf();

View File

@@ -8,43 +8,32 @@ use common\emails\RendererInterface;
use common\emails\TemplateWithRenderer;
use common\tests\unit\TestCase;
use Exception;
use PHPUnit\Framework\MockObject\MockObject;
use Yii;
use yii\mail\MailerInterface;
use yii\mail\MessageInterface;
class TemplateWithRendererTest extends TestCase {
/**
* @var TemplateWithRenderer|\PHPUnit\Framework\MockObject\MockObject $template
*/
private $template;
private TemplateWithRenderer&MockObject $template;
/**
* @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $mailer;
private MailerInterface&MockObject $mailer;
/**
* @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $renderer;
private RendererInterface&MockObject $renderer;
/**
* @var string
*/
private $initialFromEmail;
private string $initialFromEmail;
public function testGetLocale() {
public function testGetLocale(): void {
$this->assertSame('en', $this->template->getLocale());
$this->template->setLocale('find me');
$this->assertSame('find me', $this->template->getLocale());
}
public function testSend() {
public function testSend(): void {
$this->runTestForSend();
}
public function testSendWithRenderError() {
public function testSendWithRenderError(): void {
$renderException = new Exception('find me');
try {
$this->runTestForSend($renderException);
@@ -56,10 +45,10 @@ class TemplateWithRendererTest extends TestCase {
return;
}
$this->assertFalse(true, 'no exception was thrown');
$this->fail('no exception was thrown');
}
protected function _before() {
protected function _before(): void {
parent::_before();
$this->mailer = $this->createMock(MailerInterface::class);
$this->renderer = $this->createMock(RendererInterface::class);
@@ -68,12 +57,15 @@ class TemplateWithRendererTest extends TestCase {
Yii::$app->params['fromEmail'] = 'find-me';
}
protected function _after() {
protected function _after(): void {
parent::_after();
Yii::$app->params['fromEmail'] = $this->initialFromEmail;
}
private function runTestForSend($renderException = null) {
/**
* @throws \common\emails\exceptions\CannotRenderEmailException
*/
private function runTestForSend($renderException = null): void {
$renderMethodExpectation = $this->renderer->expects($this->once())->method('render')->with('mock-template', 'mock-locale', []);
if ($renderException === null) {
$renderMethodExpectation->willReturn('mock-template-contents');
@@ -86,7 +78,6 @@ class TemplateWithRendererTest extends TestCase {
$this->template->expects($times())->method('getSubject')->willReturn('mock-subject');
$this->template->expects($times())->method('getTemplateName')->willReturn('mock-template');
/** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $message */
$message = $this->createMock(MessageInterface::class);
$message->expects($times())->method('setTo')->with(['to@ely.by' => 'To'])->willReturnSelf();
$message->expects($times())->method('setHtmlBody')->with('mock-template-contents')->willReturnSelf();

View File

@@ -8,25 +8,22 @@ use common\tests\unit\TestCase;
use yii\base\InvalidCallException;
use yii\mail\MailerInterface;
class ChangeEmailTest extends TestCase {
final class ChangeEmailTest extends TestCase {
/**
* @var ChangeEmail()|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
private ChangeEmail $template;
public function testParams() {
public function testParams(): void {
$this->template->setKey('mock-key');
$params = $this->template->getParams();
$this->assertSame('mock-key', $params['key']);
}
public function testInvalidCallOfParams() {
public function testInvalidCallOfParams(): void {
$this->expectException(InvalidCallException::class);
$this->template->getParams();
}
protected function _before() {
protected function _before(): void {
parent::_before();
/** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */
$mailer = $this->createMock(MailerInterface::class);

View File

@@ -10,12 +10,9 @@ use yii\mail\MailerInterface;
class ConfirmNewEmailTest extends TestCase {
/**
* @var ConfirmNewEmail|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
private ConfirmNewEmail $template;
public function testParams() {
public function testParams(): void {
$this->template->setUsername('mock-username');
$this->template->setKey('mock-key');
$params = $this->template->getParams();
@@ -26,22 +23,21 @@ class ConfirmNewEmailTest extends TestCase {
/**
* @dataProvider getInvalidCallsCases
*/
public function testInvalidCallOfParams(?string $username, ?string $key) {
public function testInvalidCallOfParams(?string $username, ?string $key): void {
$this->expectException(InvalidCallException::class);
$username !== null && $this->template->setUsername($username);
$key !== null && $this->template->setKey($key);
$this->template->getParams();
}
public function getInvalidCallsCases() {
public function getInvalidCallsCases(): iterable {
yield [null, null];
yield ['value', null];
yield [null, 'value'];
}
protected function _before() {
protected function _before(): void {
parent::_before();
/** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */
$mailer = $this->createMock(MailerInterface::class);
$this->template = new ConfirmNewEmail($mailer);
}

View File

@@ -12,12 +12,9 @@ use yii\mail\MailerInterface;
class ForgotPasswordEmailTest extends TestCase {
/**
* @var ForgotPasswordEmail|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
private ForgotPasswordEmail $template;
public function testParams() {
public function testParams(): void {
$this->template->setParams(new ForgotPasswordParams('mock-username', 'mock-code', 'mock-link'));
$params = $this->template->getParams();
$this->assertSame('mock-username', $params['username']);
@@ -25,7 +22,7 @@ class ForgotPasswordEmailTest extends TestCase {
$this->assertSame('mock-link', $params['link']);
}
public function testInvalidCallOfParams() {
public function testInvalidCallOfParams(): void {
$this->expectException(InvalidCallException::class);
$this->template->getParams();
}

View File

@@ -12,12 +12,9 @@ use yii\mail\MailerInterface;
class RegistrationEmailTest extends TestCase {
/**
* @var RegistrationEmail()|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
private RegistrationEmail $template;
public function testParams() {
public function testParams(): void {
$this->template->setParams(new RegistrationEmailParams('mock-username', 'mock-code', 'mock-link'));
$params = $this->template->getParams();
$this->assertSame('mock-username', $params['username']);
@@ -25,16 +22,14 @@ class RegistrationEmailTest extends TestCase {
$this->assertSame('mock-link', $params['link']);
}
public function testInvalidCallOfParams() {
public function testInvalidCallOfParams(): void {
$this->expectException(InvalidCallException::class);
$this->template->getParams();
}
protected function _before() {
protected function _before(): void {
parent::_before();
/** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */
$mailer = $this->createMock(MailerInterface::class);
/** @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject $renderer */
$renderer = $this->createMock(RendererInterface::class);
$this->template = new RegistrationEmail($mailer, $renderer);
}

View File

@@ -6,7 +6,7 @@ use common\tests\unit\TestCase;
class StringHelperTest extends TestCase {
public function testGetEmailMask() {
public function testGetEmailMask(): void {
$this->assertSame('**@ely.by', StringHelper::getEmailMask('e@ely.by'));
$this->assertSame('e**@ely.by', StringHelper::getEmailMask('es@ely.by'));
$this->assertSame('e**i@ely.by', StringHelper::getEmailMask('eri@ely.by'));
@@ -14,16 +14,16 @@ class StringHelperTest extends TestCase {
$this->assertSame('эр**уч@елу.бел', StringHelper::getEmailMask('эрикскрауч@елу.бел'));
}
public function testIsUuid() {
public function testIsUuid(): void {
$this->assertTrue(StringHelper::isUuid('a80b4487-a5c6-45a5-9829-373b4a494135'));
$this->assertTrue(StringHelper::isUuid('a80b4487a5c645a59829373b4a494135'));
$this->assertFalse(StringHelper::isUuid('12345678'));
}
/**
* @dataProvider trimProvider()
* @dataProvider trimProvider
*/
public function testTrim($expected, $string) {
public function testTrim(string $expected, string $string): void {
$result = StringHelper::trim($string);
$this->assertSame($expected, $result);
}
@@ -34,7 +34,7 @@ class StringHelperTest extends TestCase {
*
* @return array
*/
public function trimProvider() {
public static function trimProvider(): array {
return [
['foo bar', ' foo bar '], // Simple spaces
['foo bar', ' foo bar'], // Only left side space

View File

@@ -6,19 +6,19 @@ use common\tests\unit\TestCase;
class AccountSessionTest extends TestCase {
public function testGenerateRefreshToken() {
public function testGenerateRefreshToken(): void {
$model = new AccountSession();
$model->generateRefreshToken();
$this->assertNotNull($model->refresh_token, 'method call will set refresh_token value');
}
public function testSetIp() {
public function testSetIp(): void {
$model = new AccountSession();
$model->setIp('127.0.0.1');
$this->assertSame(2130706433, $model->last_used_ip, 'method should convert passed ip string to long');
}
public function testGetReadableIp() {
public function testGetReadableIp(): void {
$model = new AccountSession();
$model->last_used_ip = 2130706433;
$this->assertSame('127.0.0.1', $model->getReadableIp(), 'method should convert stored long into readable ip');

View File

@@ -17,7 +17,7 @@ use const common\LATEST_RULES_VERSION;
*/
class AccountTest extends TestCase {
public function testSetPassword() {
public function testSetPassword(): void {
$model = new Account();
$model->setPassword('12345678');
$this->assertNotEmpty($model->password_hash, 'hash should be set');
@@ -25,19 +25,17 @@ class AccountTest extends TestCase {
$this->assertSame(Account::PASS_HASH_STRATEGY_YII2, $model->password_hash_strategy, 'latest password hash should be used');
}
public function testValidatePassword() {
public function testValidatePassword(): void {
// Use old hashing algorithm
$model = new Account();
$model->email = 'erick@skrauch.net';
$model->password_hash = '2cfdb29eb354af970865a923335d17d9'; // 12345678
$model->password_hash_strategy = null; // To be sure it's not set
$this->assertTrue($model->validatePassword('12345678', Account::PASS_HASH_STRATEGY_OLD_ELY), 'valid password should pass');
$this->assertFalse($model->validatePassword('87654321', Account::PASS_HASH_STRATEGY_OLD_ELY), 'invalid password should fail');
// Modern hash algorithm should also work
$model = new Account();
$model->password_hash = '$2y$04$N0q8DaHzlYILCnLYrpZfEeWKEqkPZzbawiS07GbSr/.xbRNweSLU6'; // 12345678
$model->password_hash_strategy = null; // To be sure it's not set
$this->assertTrue($model->validatePassword('12345678', Account::PASS_HASH_STRATEGY_YII2), 'valid password should pass');
$this->assertFalse($model->validatePassword('87654321', Account::PASS_HASH_STRATEGY_YII2), 'invalid password should fail');
@@ -57,7 +55,7 @@ class AccountTest extends TestCase {
$this->assertFalse($model->validatePassword('87654321'), 'invalid password should fail');
}
public function testHasMojangUsernameCollision() {
public function testHasMojangUsernameCollision(): void {
$model = new Account();
$model->username = 'ErickSkrauch';
$this->assertFalse($model->hasMojangUsernameCollision());
@@ -69,13 +67,13 @@ class AccountTest extends TestCase {
$this->assertTrue($model->hasMojangUsernameCollision());
}
public function testGetProfileLink() {
public function testGetProfileLink(): void {
$model = new Account();
$model->id = '123';
$model->id = 123;
$this->assertSame('http://ely.by/u123', $model->getProfileLink());
}
public function testIsAgreedWithActualRules() {
public function testIsAgreedWithActualRules(): void {
$model = new Account();
$this->assertFalse($model->isAgreedWithActualRules(), 'field is null');
@@ -86,7 +84,7 @@ class AccountTest extends TestCase {
$this->assertTrue($model->isAgreedWithActualRules());
}
public function testSetRegistrationIp() {
public function testSetRegistrationIp(): void {
$account = new Account();
$account->setRegistrationIp('42.72.205.204');
$this->assertSame('42.72.205.204', inet_ntop($account->registration_ip));
@@ -96,7 +94,7 @@ class AccountTest extends TestCase {
$this->assertNull($account->registration_ip);
}
public function testGetRegistrationIp() {
public function testGetRegistrationIp(): void {
$account = new Account();
$account->setRegistrationIp('42.72.205.204');
$this->assertSame('42.72.205.204', $account->getRegistrationIp());
@@ -106,7 +104,7 @@ class AccountTest extends TestCase {
$this->assertNull($account->getRegistrationIp());
}
public function testAfterSaveInsertEvent() {
public function testAfterSaveInsertEvent(): void {
$account = new Account();
$account->afterSave(true, [
'username' => 'old-username',
@@ -114,7 +112,7 @@ class AccountTest extends TestCase {
$this->assertNull($this->tester->grabLastQueuedJob());
}
public function testAfterSaveNotMeaningfulAttributes() {
public function testAfterSaveNotMeaningfulAttributes(): void {
$account = new Account();
$account->afterSave(false, [
'updatedAt' => time(),
@@ -122,7 +120,7 @@ class AccountTest extends TestCase {
$this->assertNull($this->tester->grabLastQueuedJob());
}
public function testAfterSavePushEvent() {
public function testAfterSavePushEvent(): void {
$changedAttributes = [
'username' => 'old-username',
'email' => 'old-email@ely.by',
@@ -144,7 +142,7 @@ class AccountTest extends TestCase {
$this->assertSame($changedAttributes, $notification->getPayloads()['changedAttributes']);
}
public function testAfterDeletePushEvent() {
public function testAfterDeletePushEvent(): void {
$account = new Account();
$account->id = 1;
$account->status = Account::STATUS_REGISTERED;

View File

@@ -21,18 +21,18 @@ class EmailActivationTest extends TestCase {
/**
* @dataProvider getInstantiateTestCases
*/
public function testInstantiate(int $type, string $expectedClassType) {
public function testInstantiate(int $type, string $expectedClassType): void {
$this->assertInstanceOf($expectedClassType, EmailActivation::findOne(['type' => $type]));
}
public function getInstantiateTestCases() {
public function getInstantiateTestCases(): iterable {
yield [EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, confirmations\RegistrationConfirmation::class];
yield [EmailActivation::TYPE_FORGOT_PASSWORD_KEY, confirmations\ForgotPassword::class];
yield [EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION, confirmations\CurrentEmailConfirmation::class];
yield [EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION, confirmations\NewEmailConfirmation::class];
}
public function testCanResend() {
public function testCanResend(): void {
$model = $this->createPartialMock(EmailActivation::class, ['getResendTimeout']);
$model->method('getResendTimeout')->willReturn(new DateInterval('PT10M'));
@@ -45,7 +45,7 @@ class EmailActivationTest extends TestCase {
$this->assertEqualsWithDelta(Carbon::now()->subSecond(), $model->canResendAt(), 3);
}
public function testCanResendWithNullTimeout() {
public function testCanResendWithNullTimeout(): void {
$model = $this->createPartialMock(EmailActivation::class, ['getResendTimeout']);
$model->method('getResendTimeout')->willReturn(null);
@@ -54,7 +54,7 @@ class EmailActivationTest extends TestCase {
$this->assertEqualsWithDelta(Carbon::now(), $model->canResendAt(), 3);
}
public function testIsStale() {
public function testIsStale(): void {
$model = $this->createPartialMock(EmailActivation::class, ['getExpireDuration']);
$model->method('getExpireDuration')->willReturn(new DateInterval('PT10M'));
@@ -65,7 +65,7 @@ class EmailActivationTest extends TestCase {
$this->assertTrue($model->isStale());
}
public function testIsStaleWithNullDuration() {
public function testIsStaleWithNullDuration(): void {
$model = $this->createPartialMock(EmailActivation::class, ['getExpireDuration']);
$model->method('getExpireDuration')->willReturn(null);

View File

@@ -13,30 +13,24 @@ class OauthClientQueryTest extends TestCase {
];
}
public function testDefaultHideDeletedEntries() {
public function testDefaultHideDeletedEntries(): void {
/** @var OauthClient[] $clients */
$clients = OauthClient::find()->all();
$this->assertEmpty(array_filter($clients, function(OauthClient $client) {
return (bool)$client->is_deleted === true;
}));
$this->assertEmpty(array_filter($clients, fn(OauthClient $client): bool => (bool)$client->is_deleted === true));
$this->assertNull(OauthClient::findOne('deleted-oauth-client'));
}
public function testAllowFindDeletedEntries() {
public function testAllowFindDeletedEntries(): void {
/** @var OauthClient[] $clients */
$clients = OauthClient::find()->includeDeleted()->all();
$this->assertNotEmpty(array_filter($clients, function(OauthClient $client) {
return (bool)$client->is_deleted === true;
}));
$this->assertNotEmpty(array_filter($clients, fn(OauthClient $client): bool => (bool)$client->is_deleted === true));
$client = OauthClient::find()
->includeDeleted()
->andWhere(['id' => 'deleted-oauth-client'])
->one();
$this->assertInstanceOf(OauthClient::class, $client);
$deletedClients = OauthClient::find()->onlyDeleted()->all();
$this->assertEmpty(array_filter($deletedClients, function(OauthClient $client) {
return (bool)$client->is_deleted === false;
}));
$this->assertEmpty(array_filter($deletedClients, fn(OauthClient $client): bool => (bool)$client->is_deleted === false));
}
}

View File

@@ -21,7 +21,7 @@ class ClearAccountSessionsTest extends TestCase {
];
}
public function testExecute() {
public function testExecute(): void {
/** @var \common\models\Account $bannedAccount */
$bannedAccount = $this->tester->grabFixture('accounts', 'banned-account');
$task = new ClearAccountSessions($bannedAccount->id);

View File

@@ -19,7 +19,7 @@ class ClearOauthSessionsTest extends TestCase {
];
}
public function testCreateFromClient() {
public function testCreateFromClient(): void {
$client = new OauthClient();
$client->id = 'mocked-id';
@@ -34,7 +34,7 @@ class ClearOauthSessionsTest extends TestCase {
$this->assertEqualsWithDelta(time(), $result->notSince, 1);
}
public function testExecute() {
public function testExecute(): void {
$task = new ClearOauthSessions('deleted-oauth-client-with-sessions', 1519510065);
$task->execute($this->createMock(Queue::class));

View File

@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace common\tests\unit\tasks;
use Carbon\Exceptions\UnreachableException;
use common\notifications\NotificationInterface;
use common\tasks\CreateWebHooksDeliveries;
use common\tasks\DeliveryWebHook;
@@ -21,7 +22,7 @@ class CreateWebHooksDeliveriesTest extends TestCase {
];
}
public function testExecute() {
public function testExecute(): void {
$notification = new class implements NotificationInterface {
public static function getType(): string {
return 'account.edit';
@@ -33,24 +34,28 @@ class CreateWebHooksDeliveriesTest extends TestCase {
};
$queue = $this->createMock(Queue::class);
$queue->expects($this->exactly(2))->method('push')->withConsecutive(
[$this->callback(function(DeliveryWebHook $task): bool {
$invocationCount = $this->exactly(2);
$queue->expects($invocationCount)->method('push')->willReturnCallback(function(DeliveryWebHook $task) use ($invocationCount): bool {
if ($invocationCount->numberOfInvocations() === 1) {
$this->assertSame('account.edit', $task->type);
$this->assertSame(['key' => 'value'], $task->payloads);
$this->assertSame('http://localhost:80/webhooks/ely', $task->url);
$this->assertSame('my-secret', $task->secret);
return true;
})],
[$this->callback(function(DeliveryWebHook $task): bool {
}
if ($invocationCount->numberOfInvocations() === 2) {
$this->assertSame('account.edit', $task->type);
$this->assertSame(['key' => 'value'], $task->payloads);
$this->assertSame('http://localhost:81/webhooks/ely', $task->url);
$this->assertNull($task->secret);
return true;
})],
);
}
throw new UnreachableException();
});
$task = new CreateWebHooksDeliveries($notification);
$task->execute($queue);

View File

@@ -25,7 +25,7 @@ class DeleteAccountTest extends TestCase {
];
}
public function testExecute() {
public function testExecute(): void {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$account->status = Account::STATUS_DELETED;
@@ -46,7 +46,7 @@ class DeleteAccountTest extends TestCase {
* When a user restores his account back, the task doesn't removed
* @throws \Throwable
*/
public function testExecuteOnNotDeletedAccount() {
public function testExecuteOnNotDeletedAccount(): void {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
// By default, this account is active
@@ -66,7 +66,7 @@ class DeleteAccountTest extends TestCase {
* For each deletion the job will be created, so assert, that job for restored deleting will not work
* @throws \Throwable
*/
public function testExecuteOnDeletedAccountWhichWasRestoredAndThenDeletedAgain() {
public function testExecuteOnDeletedAccountWhichWasRestoredAndThenDeletedAgain(): void {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$account->status = Account::STATUS_DELETED;

View File

@@ -20,14 +20,14 @@ use yii\queue\Queue;
*/
class DeliveryWebHookTest extends TestCase {
private $historyContainer = [];
private array $historyContainer = [];
/**
* @var Response|\GuzzleHttp\Exception\GuzzleException
*/
private $response;
public function testCanRetry() {
public function testCanRetry(): void {
$task = new DeliveryWebHook();
$this->assertFalse($task->canRetry(1, new \Exception()));
$request = $this->createMock(RequestInterface::class);
@@ -38,7 +38,7 @@ class DeliveryWebHookTest extends TestCase {
$this->assertFalse($task->canRetry(5, new ServerException('', $request, $response)));
}
public function testExecuteSuccessDelivery() {
public function testExecuteSuccessDelivery(): void {
$this->response = new Response();
$task = $this->createMockedTask();
$task->type = 'account.edit';
@@ -58,7 +58,7 @@ class DeliveryWebHookTest extends TestCase {
$this->assertSame('key=value&another=value', (string)$request->getBody());
}
public function testExecuteSuccessDeliveryWithSignature() {
public function testExecuteSuccessDeliveryWithSignature(): void {
$this->response = new Response();
$task = $this->createMockedTask();
$task->type = 'account.edit';
@@ -79,7 +79,7 @@ class DeliveryWebHookTest extends TestCase {
$this->assertSame('key=value&another=value', (string)$request->getBody());
}
public function testExecuteHandleClientException() {
public function testExecuteHandleClientException(): void {
$this->response = new Response(403);
$task = $this->createMockedTask();
$task->type = 'account.edit';
@@ -92,7 +92,7 @@ class DeliveryWebHookTest extends TestCase {
$task->execute($this->createMock(Queue::class));
}
public function testExecuteUnhandledException() {
public function testExecuteUnhandledException(): void {
$this->expectException(ServerException::class);
$this->response = new Response(502);
@@ -114,11 +114,11 @@ class DeliveryWebHookTest extends TestCase {
return new class($container, $response) extends DeliveryWebHook {
private $historyContainer;
private $response;
public function __construct(array &$historyContainer, $response) {
public function __construct(
array & $historyContainer,
private $response,
) {
$this->historyContainer = &$historyContainer;
$this->response = $response;
}
protected function createStack(): HandlerStack {

View File

@@ -30,7 +30,7 @@ class PullMojangUsernameTest extends TestCase {
];
}
public function _before() {
public function _before(): void {
parent::_before();
/** @var \PHPUnit\Framework\MockObject\MockObject|MojangApi $mockApi */
@@ -40,14 +40,14 @@ class PullMojangUsernameTest extends TestCase {
Yii::$container->set(MojangApi::class, $mockApi);
}
public function testCreateFromAccount() {
public function testCreateFromAccount(): void {
$account = new Account();
$account->username = 'find-me';
$result = PullMojangUsername::createFromAccount($account);
$this->assertSame('find-me', $result->username);
}
public function testExecuteUsernameExists() {
public function testExecuteUsernameExists(): void {
$this->mockedMethod->willReturn(new ProfileInfo('069a79f444e94726a5befca90e38aaf5', 'Notch'));
/** @var MojangUsername $mojangUsernameFixture */
@@ -62,7 +62,7 @@ class PullMojangUsernameTest extends TestCase {
$this->assertLessThanOrEqual(time(), $mojangUsername->last_pulled_at);
}
public function testExecuteChangedUsernameExists() {
public function testExecuteChangedUsernameExists(): void {
$this->mockedMethod->willReturn(new ProfileInfo('069a79f444e94726a5befca90e38aaf5', 'Notch'));
/** @var MojangUsername $mojangUsernameFixture */
@@ -77,7 +77,7 @@ class PullMojangUsernameTest extends TestCase {
$this->assertLessThanOrEqual(time(), $mojangUsername->last_pulled_at);
}
public function testExecuteChangedUsernameNotExists() {
public function testExecuteChangedUsernameNotExists(): void {
$this->mockedMethod->willReturn(new ProfileInfo('607153852b8c4909811f507ed8ee737f', 'Chest'));
$task = new PullMojangUsername();
@@ -88,7 +88,7 @@ class PullMojangUsernameTest extends TestCase {
$this->assertInstanceOf(MojangUsername::class, $mojangUsername);
}
public function testExecuteRemoveIfExistsNoMore() {
public function testExecuteRemoveIfExistsNoMore(): void {
$this->mockedMethod->willThrowException(new NoContentException(new Request('GET', ''), new Response()));
$username = $this->tester->grabFixture('mojangUsernames', 'not-exists')['username'];
@@ -100,7 +100,7 @@ class PullMojangUsernameTest extends TestCase {
$this->assertNull($mojangUsername);
}
public function testExecuteUuidUpdated() {
public function testExecuteUuidUpdated(): void {
$this->mockedMethod->willReturn(new ProfileInfo('f498513ce8c84773be26ecfc7ed5185d', 'jeb'));
/** @var MojangUsername $mojangInfo */

View File

@@ -8,11 +8,13 @@ use common\models\AccountQuery;
use common\models\confirmations\CurrentEmailConfirmation;
use common\tasks\SendCurrentEmailConfirmation;
use common\tests\unit\TestCase;
use Yii;
use yii\queue\Queue;
use yii\symfonymailer\Message;
class SendCurrentEmailConfirmationTest extends TestCase {
public function testCreateFromConfirmation() {
public function testCreateFromConfirmation(): void {
$account = new Account();
$account->username = 'mock-username';
$account->email = 'mock@ely.by';
@@ -31,8 +33,8 @@ class SendCurrentEmailConfirmationTest extends TestCase {
$this->assertSame('ABCDEFG', $result->code);
}
public function testExecute() {
$task = new SendCurrentEmailConfirmation();
public function testExecute(): void {
$task = new SendCurrentEmailConfirmation(Yii::$app->mailer);
$task->username = 'mock-username';
$task->email = 'mock@ely.by';
$task->code = 'GFEDCBA';
@@ -40,12 +42,11 @@ class SendCurrentEmailConfirmationTest extends TestCase {
$task->execute($this->createMock(Queue::class));
$this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */
/** @var Message $email */
$email = $this->tester->grabSentEmails()[0];
$this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo());
$this->assertSame('Ely.by Account change E-mail confirmation', $email->getSubject());
$children = $email->getSwiftMessage()->getChildren()[0];
$this->assertStringContainsString('GFEDCBA', $children->getBody());
$this->assertStringContainsString('GFEDCBA', $email->getSymfonyEmail()->getTextBody());
}
}

View File

@@ -12,7 +12,7 @@ use yii\queue\Queue;
class SendNewEmailConfirmationTest extends TestCase {
public function testCreateFromConfirmation() {
public function testCreateFromConfirmation(): void {
$account = new Account();
$account->username = 'mock-username';
$account->lang = 'id';
@@ -31,7 +31,7 @@ class SendNewEmailConfirmationTest extends TestCase {
$this->assertSame('ABCDEFG', $result->code);
}
public function testExecute() {
public function testExecute(): void {
$task = new SendNewEmailConfirmation();
$task->username = 'mock-username';
$task->email = 'mock@ely.by';
@@ -40,12 +40,11 @@ class SendNewEmailConfirmationTest extends TestCase {
$task->execute($this->createMock(Queue::class));
$this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */
/** @var \yii\symfonymailer\Message $email */
$email = $this->tester->grabSentEmails()[0];
$this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo());
$this->assertSame('Ely.by Account new E-mail confirmation', $email->getSubject());
$children = $email->getSwiftMessage()->getChildren()[0];
$this->assertStringContainsString('GFEDCBA', $children->getBody());
$this->assertStringContainsString('GFEDCBA', $email->getSymfonyEmail()->getTextBody());
}
}

View File

@@ -9,17 +9,15 @@ use common\models\AccountQuery;
use common\models\confirmations\ForgotPassword;
use common\tasks\SendPasswordRecoveryEmail;
use common\tests\unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Yii;
use yii\queue\Queue;
class SendPasswordRecoveryEmailTest extends TestCase {
/**
* @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $renderer;
private RendererInterface&MockObject $renderer;
public function testCreateFromConfirmation() {
public function testCreateFromConfirmation(): void {
$account = new Account();
$account->username = 'mock-username';
$account->email = 'mock@ely.by';
@@ -40,7 +38,7 @@ class SendPasswordRecoveryEmailTest extends TestCase {
$this->assertSame('id', $result->locale);
}
public function testExecute() {
public function testExecute(): void {
$task = new SendPasswordRecoveryEmail();
$task->username = 'mock-username';
$task->email = 'mock@ely.by';
@@ -57,14 +55,14 @@ class SendPasswordRecoveryEmailTest extends TestCase {
$task->execute($this->createMock(Queue::class));
$this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */
/** @var \yii\symfonymailer\Message $email */
$email = $this->tester->grabSentEmails()[0];
$this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo());
$this->assertSame('Ely.by Account forgot password', $email->getSubject());
$this->assertSame('mock-template', $email->getSwiftMessage()->getBody());
$this->assertSame('mock-template', $email->getSymfonyEmail()->getHtmlBody());
}
protected function _before() {
protected function _before(): void {
parent::_before();
$this->renderer = $this->createMock(RendererInterface::class);

View File

@@ -9,17 +9,15 @@ use common\models\AccountQuery;
use common\models\confirmations\RegistrationConfirmation;
use common\tasks\SendRegistrationEmail;
use common\tests\unit\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Yii;
use yii\queue\Queue;
class SendRegistrationEmailTest extends TestCase {
/**
* @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $renderer;
private RendererInterface&MockObject $renderer;
public function testCreateFromConfirmation() {
public function testCreateFromConfirmation(): void {
$account = new Account();
$account->username = 'mock-username';
$account->email = 'mock@ely.by';
@@ -40,7 +38,7 @@ class SendRegistrationEmailTest extends TestCase {
$this->assertSame('ru', $result->locale);
}
public function testExecute() {
public function testExecute(): void {
$task = new SendRegistrationEmail();
$task->username = 'mock-username';
$task->email = 'mock@ely.by';
@@ -57,11 +55,11 @@ class SendRegistrationEmailTest extends TestCase {
$task->execute($this->createMock(Queue::class));
$this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */
/** @var \yii\symfonymailer\Message $email */
$email = $this->tester->grabSentEmails()[0];
$this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo());
$this->assertSame('Ely.by Account registration', $email->getSubject());
$this->assertSame('mock-template', $email->getSwiftMessage()->getBody());
$this->assertSame('mock-template', $email->getSymfonyEmail()->getHtmlBody());
}
protected function _before() {

View File

@@ -3,9 +3,11 @@ declare(strict_types=1);
namespace common\tests\unit\validators;
use common\models\Account;
use common\tests\fixtures\AccountFixture;
use common\tests\unit\TestCase;
use common\validators\EmailValidator;
use Generator;
use yii\base\Model;
use yii\validators\EmailValidator as YiiEmailValidator;
@@ -16,7 +18,7 @@ final class EmailValidatorTest extends TestCase {
private EmailValidator $validator;
public function _before() {
public function _before(): void {
parent::_before();
self::defineFunctionMock(YiiEmailValidator::class, 'checkdnsrr');
@@ -25,7 +27,7 @@ final class EmailValidatorTest extends TestCase {
$this->validator = new EmailValidator();
}
public function testValidateTrimming() {
public function testValidateTrimming(): void {
// Prevent it to access to db
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(false);
@@ -35,7 +37,7 @@ final class EmailValidatorTest extends TestCase {
$this->assertSame('testemail@ely.by', $model->field);
}
public function testValidateAttributeRequired() {
public function testValidateAttributeRequired(): void {
$model = $this->createModel('');
$this->validator->validateAttribute($model, 'field');
$this->assertSame(['error.email_required'], $model->getErrors('field'));
@@ -45,14 +47,14 @@ final class EmailValidatorTest extends TestCase {
$this->assertNotSame(['error.email_required'], $model->getErrors('field'));
}
public function testValidateAttributeLength() {
public function testValidateAttributeLength(): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(false);
$model = $this->createModel(
'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' .
'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' .
'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' .
'@gmail.com' // = 256 symbols
'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail'
. 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail'
. 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail'
. '@gmail.com', // = 256 symbols
);
$this->validator->validateAttribute($model, 'field');
$this->assertSame(['error.email_too_long'], $model->getErrors('field'));
@@ -62,7 +64,7 @@ final class EmailValidatorTest extends TestCase {
$this->assertNotSame(['error.email_too_long'], $model->getErrors('field'));
}
public function testValidateAttributeEmailCaseNotExistsDomain() {
public function testValidateAttributeEmailCaseNotExistsDomain(): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(false);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->never());
@@ -71,7 +73,7 @@ final class EmailValidatorTest extends TestCase {
$this->assertSame(['error.email_invalid'], $model->getErrors('field'));
}
public function testValidateAttributeEmailCaseExistsDomainButWithoutMXRecord() {
public function testValidateAttributeEmailCaseExistsDomainButWithoutMXRecord(): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->exactly(2))->willReturnOnConsecutiveCalls(false, true);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->any())->willReturn(['127.0.0.1']);
@@ -80,7 +82,7 @@ final class EmailValidatorTest extends TestCase {
$this->assertNotSame(['error.email_invalid'], $model->getErrors('field'));
}
public function testValidateAttributeEmailCaseExistsDomainWithMXRecord() {
public function testValidateAttributeEmailCaseExistsDomainWithMXRecord(): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(true);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->any())->willReturn(['mx.google.com']);
@@ -98,7 +100,7 @@ final class EmailValidatorTest extends TestCase {
$this->assertSame(['error.email_invalid'], $model->getErrors('field'));
}
public function testValidateAttributeTempmail() {
public function testValidateAttributeTempmail(): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(true);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->any())->willReturn(['127.0.0.1']);
@@ -114,7 +116,7 @@ final class EmailValidatorTest extends TestCase {
/**
* @dataProvider getValidateAttributeBlacklistedHostTestCases
*/
public function testValidateAttributeBlacklistedHost(string $email, bool $expectValid) {
public function testValidateAttributeBlacklistedHost(string $email, bool $expectValid): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(true);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->any())->willReturn(['127.0.0.1']);
@@ -128,7 +130,7 @@ final class EmailValidatorTest extends TestCase {
}
}
public function getValidateAttributeBlacklistedHostTestCases() {
public static function getValidateAttributeBlacklistedHostTestCases(): Generator {
yield 'seznam.cz' => ['user@seznam.cz', false];
yield 'valid' => ['valid@google.com', true];
}
@@ -136,7 +138,7 @@ final class EmailValidatorTest extends TestCase {
/**
* @dataProvider getValidateAttributeIdnaTestCases
*/
public function testValidateAttributeIdna(string $input, string $expectedOutput) {
public function testValidateAttributeIdna(string $input, string $expectedOutput): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(true);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->any())->willReturn(['127.0.0.1']);
@@ -145,13 +147,13 @@ final class EmailValidatorTest extends TestCase {
$this->assertSame($expectedOutput, $model->field);
}
public function getValidateAttributeIdnaTestCases() {
public static function getValidateAttributeIdnaTestCases(): Generator {
yield ['qdushyantasunassm@❕.gq', 'qdushyantasunassm@xn--bei.gq'];
yield ['Rafaelaabraão@gmail.com', 'xn--rafaelaabrao-dcb@gmail.com'];
yield ['valid-email@gmail.com', 'valid-email@gmail.com'];
}
public function testValidateAttributeUnique() {
public function testValidateAttributeUnique(): void {
$this->getFunctionMock(YiiEmailValidator::class, 'checkdnsrr')->expects($this->any())->willReturn(true);
$this->getFunctionMock(YiiEmailValidator::class, 'dns_get_record')->expects($this->any())->willReturn(['127.0.0.1']);
@@ -159,7 +161,7 @@ final class EmailValidatorTest extends TestCase {
'accounts' => AccountFixture::class,
]);
/** @var \common\models\Account $accountFixture */
/** @var Account $accountFixture */
$accountFixture = $this->tester->grabFixture('accounts', 'admin');
$model = $this->createModel($accountFixture->email);
@@ -167,9 +169,7 @@ final class EmailValidatorTest extends TestCase {
$this->assertSame(['error.email_not_available'], $model->getErrors('field'));
$model = $this->createModel($accountFixture->email);
$this->validator->accountCallback = function() use ($accountFixture) {
return $accountFixture->id;
};
$this->validator->accountCallback = fn() => $accountFixture->id;
$this->validator->validateAttribute($model, 'field');
$this->assertNotSame(['error.email_not_available'], $model->getErrors('field'));
$this->validator->accountCallback = null;
@@ -180,12 +180,11 @@ final class EmailValidatorTest extends TestCase {
}
/**
* @param string $fieldValue
* @return Model
* @return Model&object{ field: mixed }
*/
private function createModel(string $fieldValue): Model {
$class = new class extends Model {
public $field;
public string $field;
};
$class->field = $fieldValue;

View File

@@ -9,7 +9,7 @@ class MinecraftServerAddressValidatorTest extends TestCase {
/**
* @dataProvider domainNames
*/
public function testValidate($address, $shouldBeValid) {
public function testValidate(string $address, bool $shouldBeValid): void {
$validator = new MinecraftServerAddressValidator();
$validator->message = 'mock message';
$validator->validate($address, $errors);
@@ -20,7 +20,7 @@ class MinecraftServerAddressValidatorTest extends TestCase {
}
}
public function domainNames() {
public function domainNames(): array {
return [
['localhost', true],
['localhost:25565', true],

View File

@@ -8,24 +8,21 @@ use yii\base\Model;
class UsernameValidatorTest extends TestCase {
/**
* @var UsernameValidator
*/
private $validator;
private UsernameValidator $validator;
public function _before() {
public function _before(): void {
parent::_before();
$this->validator = new UsernameValidator();
}
public function testValidateTrimming() {
public function testValidateTrimming(): void {
$model = $this->createModel("HereIsJohnny#\u{feff}"); // Zero width no-break space (U+FEFF)
$this->validator->validateAttribute($model, 'field');
$this->assertSame(['error.username_invalid'], $model->getErrors('field'));
$this->assertSame('HereIsJohnny#', $model->field);
}
public function testValidateAttributeRequired() {
public function testValidateAttributeRequired(): void {
$model = $this->createModel('');
$this->validator->validateAttribute($model, 'field');
$this->assertSame(['error.username_required'], $model->getErrors('field'));
@@ -35,7 +32,7 @@ class UsernameValidatorTest extends TestCase {
$this->assertNotSame(['error.username_required'], $model->getErrors('field'));
}
public function testValidateAttributeLength() {
public function testValidateAttributeLength(): void {
$model = $this->createModel('at');
$this->validator->validateAttribute($model, 'field');
$this->assertSame(['error.username_too_short'], $model->getErrors('field'));
@@ -51,7 +48,7 @@ class UsernameValidatorTest extends TestCase {
}
// TODO: rewrite this test with @provider usage
public function testValidateAttributePattern() {
public function testValidateAttributePattern(): void {
$shouldBeValid = [
'русский_ник', 'русский_ник_на_грани!', 'numbers1132', '*__*-Stars-*__*', '1-_.!$%^&*()[]',
'[ESP]Эрик', 'Свят_помидор;', роблена_ў_беларусі:)',
@@ -72,7 +69,7 @@ class UsernameValidatorTest extends TestCase {
}
}
public function testValidateAttributeUnique() {
public function testValidateAttributeUnique(): void {
$this->tester->haveFixtures([
'accounts' => AccountFixture::class,
]);
@@ -85,9 +82,7 @@ class UsernameValidatorTest extends TestCase {
$this->assertSame(['error.username_not_available'], $model->getErrors('field'));
$model = $this->createModel($accountFixture->username);
$this->validator->accountCallback = function() use ($accountFixture) {
return $accountFixture->id;
};
$this->validator->accountCallback = fn() => $accountFixture->id;
$this->validator->validateAttribute($model, 'field');
$this->assertNotSame(['error.username_not_available'], $model->getErrors('field'));
$this->validator->accountCallback = null;
@@ -99,11 +94,11 @@ class UsernameValidatorTest extends TestCase {
/**
* @param string $fieldValue
* @return Model
* @return Model&object{ field: string }
*/
private function createModel(string $fieldValue): Model {
$class = new class extends Model {
public $field;
public string $field;
};
$class->field = $fieldValue;

View File

@@ -15,15 +15,15 @@ use yii\validators\Validator;
final class EmailValidator extends Validator {
/**
* @var callable(): int the function must return the account id for which the current validation is being performed.
* @phpstan-var \Closure(): int the function must return the account id for which the current validation is being performed.
* Allows you to skip the email uniqueness check for the current account.
*/
public $accountCallback;
public ?\Closure $accountCallback = null;
public $skipOnEmpty = false;
public function validateAttribute($model, $attribute): void {
$trim = new validators\FilterValidator(['filter' => [StringHelper::class, 'trim']]);
$trim = new validators\FilterValidator(['filter' => StringHelper::trim(...)]);
$required = new validators\RequiredValidator();
$required->message = E::EMAIL_REQUIRED;
@@ -40,7 +40,7 @@ final class EmailValidator extends Validator {
$additionalEmail = new class extends Validator {
protected function validateValue($value): ?array {
// Disallow emails starting with slash since Postfix (or someone before?) can't correctly handle it
if (str_starts_with($value, '/')) {
if (str_starts_with((string)$value, '/')) {
return [E::EMAIL_INVALID, []];
}
@@ -57,7 +57,7 @@ final class EmailValidator extends Validator {
];
protected function validateValue($value): ?array {
$host = explode('@', $value)[1];
$host = explode('@', (string)$value)[1];
if (in_array($host, $this->hosts, true)) {
return [E::EMAIL_HOST_IS_NOT_ALLOWED, []];
}
@@ -81,15 +81,15 @@ final class EmailValidator extends Validator {
};
}
$this->executeValidation($trim, $model, $attribute) &&
$this->executeValidation($required, $model, $attribute) &&
$this->executeValidation($length, $model, $attribute) &&
$this->executeValidation($email, $model, $attribute) &&
$this->executeValidation($additionalEmail, $model, $attribute) &&
$this->executeValidation($tempmail, $model, $attribute) &&
$this->executeValidation($blacklist, $model, $attribute) &&
$this->executeValidation($idnaDomain, $model, $attribute) &&
$this->executeValidation($unique, $model, $attribute);
$this->executeValidation($trim, $model, $attribute)
&& $this->executeValidation($required, $model, $attribute)
&& $this->executeValidation($length, $model, $attribute)
&& $this->executeValidation($email, $model, $attribute)
&& $this->executeValidation($additionalEmail, $model, $attribute)
&& $this->executeValidation($tempmail, $model, $attribute)
&& $this->executeValidation($blacklist, $model, $attribute)
&& $this->executeValidation($idnaDomain, $model, $attribute)
&& $this->executeValidation($unique, $model, $attribute);
}
private function executeValidation(Validator $validator, Model $model, string $attribute): bool {

View File

@@ -27,7 +27,7 @@ class LanguageValidator extends Validator {
$primary = Locale::getPrimaryLanguage($value);
$region = Locale::getRegion($value);
$locales = ResourceBundle::getLocales(''); // http://php.net/manual/ru/resourcebundle.locales.php#115965
if (($region !== '' && strtolower($primary) !== strtolower($region)) && !in_array($value, $locales)) {
if (($region !== '' && strtolower((string)$primary) !== strtolower((string)$region)) && !in_array($value, $locales)) {
return [$this->message, []];
}

View File

@@ -7,7 +7,7 @@ use yii\validators\Validator;
class MinecraftServerAddressValidator extends Validator {
protected function validateValue($value) {
protected function validateValue($value): ?array {
// we will add minecraft protocol to help parse_url understand all another parts
$urlParts = parse_url('minecraft://' . $value);
$cnt = count($urlParts);

View File

@@ -1,6 +1,7 @@
<?php
namespace common\validators;
use Closure;
use common\helpers\Error as E;
use common\helpers\StringHelper;
use common\models\Account;
@@ -12,15 +13,15 @@ use yii\validators\Validator;
class UsernameValidator extends Validator {
/**
* @var \Closure the function must return the account id for which the current validation is being performed.
* @phpstan-var \Closure(): int the function must return the account id for which the current validation is being performed.
* Allows you to skip the username check for the current account.
*/
public $accountCallback;
public ?Closure $accountCallback = null;
public $skipOnEmpty = false;
public function validateAttribute($model, $attribute) {
$filter = new validators\FilterValidator(['filter' => [StringHelper::class, 'trim']]);
public function validateAttribute($model, $attribute): ?array {
$filter = new validators\FilterValidator(['filter' => StringHelper::trim(...)]);
$required = new validators\RequiredValidator();
$required->message = E::USERNAME_REQUIRED;
@@ -39,16 +40,18 @@ class UsernameValidator extends Validator {
$unique->targetClass = Account::class;
$unique->targetAttribute = 'username';
if ($this->accountCallback !== null) {
$unique->filter = function(QueryInterface $query) {
$unique->filter = function(QueryInterface $query): void {
$query->andWhere(['NOT', ['id' => ($this->accountCallback)()]]);
};
}
$this->executeValidation($filter, $model, $attribute) &&
$this->executeValidation($required, $model, $attribute) &&
$this->executeValidation($length, $model, $attribute) &&
$this->executeValidation($pattern, $model, $attribute) &&
$this->executeValidation($unique, $model, $attribute);
$this->executeValidation($filter, $model, $attribute)
&& $this->executeValidation($required, $model, $attribute)
&& $this->executeValidation($length, $model, $attribute)
&& $this->executeValidation($pattern, $model, $attribute)
&& $this->executeValidation($unique, $model, $attribute);
return null;
}
protected function executeValidation(Validator $validator, Model $model, string $attribute) {