Skip to content

Commit

Permalink
Add ArrayDefinitionHelper::merge() (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Feb 4, 2023
1 parent 6cfdb19 commit d9c2c28
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 35 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
@@ -1,8 +1,8 @@
# Yii Definitions Change Log

## 3.0.3 under development
## 3.1.0 under development

- no changes in this release.
- New #67: Add `ArrayDefinitionHelper::merge()` method that merge array definitions (@vjik)

## 3.0.2 December 02, 2022

Expand Down
16 changes: 3 additions & 13 deletions src/ArrayDefinition.php
Expand Up @@ -10,6 +10,7 @@
use Yiisoft\Definitions\Contract\DefinitionInterface;
use Yiisoft\Definitions\Contract\ReferenceInterface;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Helpers\ArrayDefinitionHelper;
use Yiisoft\Definitions\Helpers\DefinitionExtractor;
use Yiisoft\Definitions\Helpers\DefinitionResolver;

Expand Down Expand Up @@ -281,7 +282,7 @@ public function merge(self $other): self
{
$new = clone $this;
$new->class = $other->class;
$new->constructorArguments = $this->mergeArguments($this->constructorArguments, $other->constructorArguments);
$new->constructorArguments = ArrayDefinitionHelper::mergeArguments($this->constructorArguments, $other->constructorArguments);

$methodsAndProperties = $this->methodsAndProperties;
foreach ($other->methodsAndProperties as $key => $item) {
Expand All @@ -290,7 +291,7 @@ public function merge(self $other): self
} elseif ($item[0] === self::TYPE_METHOD) {
/** @psalm-suppress MixedArgument, MixedAssignment */
$arguments = isset($methodsAndProperties[$key])
? $this->mergeArguments($methodsAndProperties[$key][2], $item[2])
? ArrayDefinitionHelper::mergeArguments($methodsAndProperties[$key][2], $item[2])
: $item[2];
$methodsAndProperties[$key] = [$item[0], $item[1], $arguments];
}
Expand All @@ -299,15 +300,4 @@ public function merge(self $other): self

return $new;
}

private function mergeArguments(array $selfArguments, array $otherArguments): array
{
/** @var mixed $argument */
foreach ($otherArguments as $name => $argument) {
/** @var mixed */
$selfArguments[$name] = $argument;
}

return $selfArguments;
}
}
75 changes: 75 additions & 0 deletions src/Helpers/ArrayDefinitionHelper.php
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Definitions\Helpers;

use Yiisoft\Definitions\ArrayDefinition;

use Yiisoft\Definitions\Exception\InvalidConfigException;

use function is_array;

final class ArrayDefinitionHelper
{
/**
* @throws InvalidConfigException
*/
public static function merge(array ...$configs): array
{
$result = array_shift($configs) ?: [];
while (!empty($configs)) {
/** @var mixed $value */
foreach (array_shift($configs) as $key => $value) {
if (!is_string($key)) {
throw ExceptionHelper::invalidArrayDefinitionKey($key);
}

if (!isset($result[$key])) {
/** @var mixed */
$result[$key] = $value;
continue;
}

if ($key === ArrayDefinition::CONSTRUCTOR) {
if (!is_array($value)) {
throw ExceptionHelper::incorrectArrayDefinitionConstructorArguments($value);
}
if (!is_array($result[$key])) {
throw ExceptionHelper::incorrectArrayDefinitionConstructorArguments($result[$key]);
}
$result[$key] = self::mergeArguments($result[$key], $value);
continue;
}

if (str_ends_with($key, '()')) {
if (!is_array($value)) {
throw ExceptionHelper::incorrectArrayDefinitionMethodArguments($key, $value);
}
if (!is_array($result[$key])) {
throw ExceptionHelper::incorrectArrayDefinitionMethodArguments($key, $result[$key]);
}
/** @var mixed */
$result[$key] = self::mergeArguments($result[$key], $value);
continue;
}

/** @var mixed */
$result[$key] = $value;
}
}

return $result;
}

public static function mergeArguments(array $argumentsA, array $argumentsB): array
{
/** @var mixed $argument */
foreach ($argumentsB as $name => $argument) {
/** @var mixed */
$argumentsA[$name] = $argument;
}

return $argumentsA;
}
}
24 changes: 4 additions & 20 deletions src/Helpers/DefinitionValidator.php
Expand Up @@ -91,12 +91,7 @@ public static function validateArrayDefinition(array $definition, ?string $id =
/** @var mixed $value */
foreach ($definition as $key => $value) {
if (!is_string($key)) {
throw new InvalidConfigException(
sprintf(
'Invalid definition: invalid key in array definition. Only string keys are allowed, got %d.',
$key,
),
);
throw ExceptionHelper::invalidArrayDefinitionKey($key);
}

// Class
Expand Down Expand Up @@ -226,14 +221,7 @@ private static function validateMethod(
);
}
if (!is_array($value)) {
throw new InvalidConfigException(
sprintf(
'Invalid definition: incorrect method "%s" arguments. Expected array, got "%s". ' .
'Probably you should wrap them into square brackets.',
$key,
get_debug_type($value),
)
);
throw ExceptionHelper::incorrectArrayDefinitionMethodArguments($key, $value);
}
}

Expand Down Expand Up @@ -286,13 +274,9 @@ private static function validateProperty(
private static function validateConstructor(mixed $value): void
{
if (!is_array($value)) {
throw new InvalidConfigException(
sprintf(
'Invalid definition: incorrect constructor arguments. Expected array, got %s.',
get_debug_type($value)
)
);
throw ExceptionHelper::incorrectArrayDefinitionConstructorArguments($value);
}

/** @var mixed $argument */
foreach ($value as $argument) {
if (is_object($argument) && !self::isValidObject($argument)) {
Expand Down
45 changes: 45 additions & 0 deletions src/Helpers/ExceptionHelper.php
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Definitions\Helpers;

use Yiisoft\Definitions\Exception\InvalidConfigException;

/**
* @internal
*/
final class ExceptionHelper
{
public static function invalidArrayDefinitionKey(int|string $key): InvalidConfigException
{
return new InvalidConfigException(
sprintf(
'Invalid definition: invalid key in array definition. Only string keys are allowed, got %d.',
$key,
),
);
}

public static function incorrectArrayDefinitionConstructorArguments(mixed $value): InvalidConfigException
{
return new InvalidConfigException(
sprintf(
'Invalid definition: incorrect constructor arguments. Expected array, got %s.',
get_debug_type($value)
)
);
}

public static function incorrectArrayDefinitionMethodArguments(string $key, mixed $value): InvalidConfigException
{
return new InvalidConfigException(
sprintf(
'Invalid definition: incorrect method "%s" arguments. Expected array, got "%s". ' .
'Probably you should wrap them into square brackets.',
$key,
get_debug_type($value),
)
);
}
}
151 changes: 151 additions & 0 deletions tests/Unit/Helpers/ArrayDefinitionHelperTest.php
@@ -0,0 +1,151 @@
<?php

declare(strict_types=1);

namespace Helpers;

use PHPUnit\Framework\TestCase;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Helpers\ArrayDefinitionHelper;

final class ArrayDefinitionHelperTest extends TestCase
{
public function dataBase(): array
{
return [
'without configs' => [
[],
[],
],
'one config' => [
['$value' => 42],
[
['$value' => 42],
],
],
'indexed constructor arguments' => [
['__construct()' => ['y', 42]],
[
['__construct()' => ['x']],
['__construct()' => ['y', 42]],
],
],
'named constructor arguments' => [
['__construct()' => ['number' => 42, 'color' => 'red']],
[
['__construct()' => ['number' => 42]],
['__construct()' => ['color' => 'green']],
['__construct()' => ['color' => 'red']],
],
],
'indexed method arguments' => [
['run()' => ['y', 42]],
[
['run()' => ['x']],
['run()' => ['y', 42]],
],
],
'named method arguments' => [
['run()' => ['number' => 42, 'color' => 'red']],
[
['run()' => ['number' => 42]],
['run()' => ['color' => 'green']],
['run()' => ['color' => 'red']],
],
],
'extra keys' => [
['$value' => 7, 'meta' => [1, 2]],
[
['$value' => 7, 'meta' => 42],
['meta' => [1, 2]],
],
],
'complex test' => [
[
'__construct()' => ['number' => 42, 'color' => 'green'],
'run()' => [15, 23],
'$value' => 7,
'$count' => 7,
'support' => ['name' => 'background'],
'do()' => [1],
],
[
[
'__construct()' => ['number' => 42, 'color' => 'red'],
'run()' => [3],
],
[
'$value' => 7,
'run()' => [15, 23],
'$count' => 7,
'support' => ['name' => 'background'],
],
[
'__construct()' => ['color' => 'green'],
'do()' => [1],
],
],
],
];
}

/**
* @dataProvider dataBase
*/
public function testBase(array $expected, array $configs): void
{
$result = ArrayDefinitionHelper::merge(...$configs);
$this->assertSame($expected, $result);
}

public function dataInvalidConfigException(): array
{
return [
'non-string key' => [
'Invalid definition: invalid key in array definition. Only string keys are allowed, got 0.',
[
[],
['run'],
],
],
'non-array constructor arguments 1' => [
'Invalid definition: incorrect constructor arguments. Expected array, got string.',
[
['__construct()' => ['finish']],
['__construct()' => 'start'],
],
],
'non-array constructor arguments 2' => [
'Invalid definition: incorrect constructor arguments. Expected array, got string.',
[
['__construct()' => 'start'],
['__construct()' => ['finish']],
],
],
'non-array method arguments 1' => [
'Invalid definition: incorrect method "run()" arguments. Expected array, got "string". Probably you should wrap them into square brackets.',
[
['run()' => ['finish']],
['run()' => 'start'],
],
],
'non-array method arguments 2' => [
'Invalid definition: incorrect method "run()" arguments. Expected array, got "string". Probably you should wrap them into square brackets.',
[
['run()' => 'start'],
['run()' => ['finish']],
],
],
];
}

/**
* @dataProvider dataInvalidConfigException
*/
public function testInvalidConfigException(string $expectedMessage, array $configs): void
{
$this->expectException(InvalidConfigException::class);
$this->expectExceptionMessage($expectedMessage);
ArrayDefinitionHelper::merge(...$configs);
}
}

0 comments on commit d9c2c28

Please sign in to comment.