Skip to content

Commit

Permalink
Add generics
Browse files Browse the repository at this point in the history
  • Loading branch information
steevanb committed May 15, 2024
1 parent 3ced71c commit 7958531
Show file tree
Hide file tree
Showing 29 changed files with 397 additions and 314 deletions.
53 changes: 20 additions & 33 deletions bin/ci/shellcheck
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,37 @@
set -eu

readonly ROOT_DIR="$(realpath "$(dirname "$(realpath "$0")")/../..")"
. "${ROOT_DIR}"/bin/common.inc.sh
. "${ROOT_DIR}"/bin/dockerise.inc.bash
source "${ROOT_DIR}"/bin/common.inc.sh
source "${ROOT_DIR}"/bin/dockerise.inc.bash

readonly binaries=(
bin/ci/composer-require-checker
bin/ci/composer-validate
bin/ci/docker
bin/ci/env
bin/ci/phpcs
bin/ci/phpdd
bin/ci/phpstan
bin/ci/phpunit
bin/ci/phpunit-coverage
bin/ci/shellcheck
bin/ci/unused-scanner
bin/ci/validate
bin/release/changelog
bin/release/code-lines
bin/release/docker
bin/release/env
bin/release/phpunit-coverage
bin/release/prepare
bin/common.inc.sh
bin/composer
bin/dockerise.inc.bash
)
filesToCheck=()
files=$(find "${ROOT_DIR}"/bin)
for file in ${files}; do
if [ -f "${file}" ] && [ "${file:(-4)}" != ".php" ]; then
filesToCheck+=("${file}")
fi
done

exitCode=0
for binary in "${binaries[@]}"; do
for fileToCheck in "${filesToCheck[@]}"; do
set +e
# SC1090: Can't follow non-constant source. Use a directive to specify location.
# SC1091: Not following: was not specified as input
# SC2034: CI_DOCKER_IMAGE_NAME appears unused. Verify use (or export if used externally).
# SC2086: Double quote to prevent globbing and word splitting. (needed for ${DOCKER_INTERACTIVE_PARAMETER})
# SC2155: Declare and assign separately to avoid masking return values.
# SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
shellcheck --color=always --exclude SC1090,SC1091,SC2034,SC2086,SC2155,SC2181 "${binary}"
# SC1091 (info): Not following: ./../global-vars.inc.bash was not specified as input (see shellcheck -x).
# SC2034 (warning): OUTPUT_REDIRECT_PATHNAME appears unused. Verify use (or export if used externally).
# SC2086 (info): Double quote to prevent globbing and word splitting.
# SC2015 (info): Note that A && B || C is not if-then-else. C may run when A is true.
# SC2068 (error): Double quote array expansions to avoid re-splitting elements.
# SC2181 (style): Check exit code directly with e.g. 'if ! mycmd;', not indirectly with $?.
# SC2155 (warning): Declare and assign separately to avoid masking return values.
# SC2317 (info): Command appears to be unreachable. Check usage (or ignore if invoked indirectly).
shellcheck --color=always --exclude SC1090,SC1091,SC2034,SC2086,SC2015,SC2068,SC2181,SC2155,SC2317 "${fileToCheck}"
if [ ${?} != 0 ]; then
exitCode=1
fi
set -e
done

if [ "${exitCode}" == 0 ]; then
echo -e "\e[42m All files contains valid syntax. \e[0m"
printf "\e[42m %s files contains valid syntax. \e[0m\n" "${#filesToCheck[@]}"
fi
exit ${exitCode}
48 changes: 25 additions & 23 deletions bridge/Symfony/Normalizer/ObjectCollectionDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@

namespace Steevanb\PhpCollection\Bridge\Symfony\Normalizer;

use Steevanb\PhpCollection\{
Exception\PhpCollectionException,
ObjectCollection\AbstractObjectCollection,
ObjectCollection\AbstractObjectNullableCollection
};
use Symfony\Component\Serializer\{
Normalizer\DenormalizerAwareInterface,
Normalizer\DenormalizerAwareTrait,
Normalizer\DenormalizerInterface
};
use Steevanb\PhpCollection\{
Exception\PhpCollectionException,
ObjectCollection\AbstractObjectCollection,
ObjectCollection\AbstractObjectNullableCollection
};

class ObjectCollectionDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
{
Expand All @@ -26,39 +26,41 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
|| is_subclass_of($type, AbstractObjectNullableCollection::class);
}

/** @param array<mixed> $context */
/**
* @param array<mixed> $context
* @return AbstractObjectCollection<object>|AbstractObjectNullableCollection<object|null>
*/
public function denormalize(
mixed $data,
string $type,
string $format = null,
array $context = []
): AbstractObjectCollection|AbstractObjectNullableCollection {
/** @var class-string<AbstractObjectCollection|AbstractObjectNullableCollection> $type */
/** @var class-string<AbstractObjectCollection<object>|AbstractObjectNullableCollection<object|null>> $type */

if (is_array($data) === false) {
throw new PhpCollectionException('$data should be an array, ' . get_debug_type($data) . ' given.');
throw new PhpCollectionException('$data should be an array.');
}

$values = [];
foreach ($data as $value) {
$values[] = $this->denormalizeValue($value, $type, $format, $context);
/** @var AbstractObjectCollection<object>|AbstractObjectNullableCollection<object|null> $collection */
$collection = $this->createCollection($type);
foreach ($data as $datum) {
$collection->add(
$datum === null
? null
: $this->denormalizer->denormalize($datum, $collection::getValueFqcn(), $format, $context)
);
}

return new $type($values);
return $collection;
}

/**
* @param class-string<AbstractObjectCollection|AbstractObjectNullableCollection> $collectionFqcn
* @param array<mixed> $context
* @param class-string<AbstractObjectCollection<object>|AbstractObjectNullableCollection<object|null>> $fqcn
* @return AbstractObjectCollection<object>|AbstractObjectNullableCollection<object|null>
*/
protected function denormalizeValue(
mixed $value,
string $collectionFqcn,
?string $format,
array $context
): object|null {
return $value === null
? null
: $this->denormalizer->denormalize($value, $collectionFqcn::getValueFqcn(), $format, $context);
protected function createCollection(string $fqcn): AbstractObjectCollection|AbstractObjectNullableCollection
{
return new $fqcn();
}
}
7 changes: 5 additions & 2 deletions bridge/Symfony/Normalizer/ScalarCollectionDenormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,17 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
);
}

/** @param array<mixed> $context */
/**
* @param array<mixed> $context
* @return CollectionInterface<float|integer|string|null>
*/
public function denormalize(
mixed $data,
string $type,
string $format = null,
array $context = []
): CollectionInterface {
/** @var class-string<CollectionInterface> $type */
/** @var class-string<CollectionInterface<float|integer|string|null>> $type */
return (new $type())->replace($data);
}
}
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Add support for Symfony `6.3` and `6.4`
- Add support for PHP `8.3`
- Update CI dependencies
- Add support for generics
- [BC break] Remove `ObjectCollectionDenormalizer::createObjectCollection()` and `add()`
- [BC break] Rename ObjectCollectionDenormalizer::denormalizeObject()` to `denormalizeValue()`
- [BC break] Remove `ScalarCollectionInterface`
Expand Down Expand Up @@ -38,6 +39,8 @@
- Because of generics, remove methods in `StringCollection`: `__construct()`, `replace()`, `has()`, `get()`, `merge()` and `toArray()`
- Because of generics, remove methods in `StringNullableCollection`: `__construct()`, `replace()`, `has()`, `get()`, `merge()` and `toArray()`
- [Edhrendal](https://github.com/Edhrendal) Add `AbstractCollection::isEmpty()`
- [BC break] Add `CollectionInterface::isEmpty()`
- Remove `CollectionInterface::getIntegerKeys()` and `CollectionInterface::getStringKeys()`. It still exists in `AbstractCollection`.

### [5.0.1](../../compare/5.0.0...5.0.1) - 2023-03-14

Expand Down
1 change: 0 additions & 1 deletion config/ci/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ parameters:
- ../../tests
excludePaths:
- ../../tests/Unit/ArrayTest.php
checkGenericClassInNonGenericObjectType: false
includes:
- /composer/common/vendor/phpstan/phpstan-deprecation-rules/rules.neon
- /composer/common/vendor/phpstan/phpstan-phpunit/extension.neon
Expand Down
5 changes: 2 additions & 3 deletions docker/ci/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:22.04
FROM ubuntu:24.04

COPY --from=composer:2.7.6 /usr/bin/composer /usr/local/bin/composer

Expand Down Expand Up @@ -34,8 +34,7 @@ RUN \
&& update-alternatives --set php /usr/bin/php8.1 \

# Install CI tools
&& cd ${COMPOSER_HOME} \
&& composer up \
&& composer global up \
&& ln -s ${COMPOSER_HOME}/vendor/bin/composer-require-checker /usr/local/bin/composer-require-checker \
&& ln -s ${COMPOSER_HOME}/vendor/bin/phpdd /usr/local/bin/phpdd \
&& ln -s ${COMPOSER_HOME}/vendor/bin/phpcs /usr/local/bin/phpcs \
Expand Down
16 changes: 8 additions & 8 deletions docker/ci/composer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"require": {
"steevanb/php-parallel-processes": "0.13.*",
"phpstan/phpstan": "1.10.*",
"phpstan/phpstan-deprecation-rules": "1.1.*",
"phpstan/phpstan-strict-rules": "1.5.*",
"phpstan/phpstan-phpunit": "1.3.*",
"maglnet/composer-require-checker": "4.6.*",
"steevanb/php-parallel-processes": "0.13.0",
"phpstan/phpstan": "1.11.0",
"phpstan/phpstan-deprecation-rules": "1.2.0",
"phpstan/phpstan-strict-rules": "1.6.0",
"phpstan/phpstan-phpunit": "1.4.0",
"maglnet/composer-require-checker": "4.7.1",
"wapmorgan/php-deprecation-detector": "2.0.*",
"steevanb/php-code-sniffs": "4.4.*",
"insolita/unused-scanner": "2.4.*"
"steevanb/php-code-sniffs": "4.5.1",
"insolita/unused-scanner": "2.4.0"
}
}
55 changes: 28 additions & 27 deletions src/AbstractCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
};

/**
* @template T
* @implements CollectionInterface<T>
* @template TValueType
* @implements CollectionInterface<TValueType>
*/
abstract class AbstractCollection implements CollectionInterface
{
/** @var array<T> */
/** @var array<TValueType> */
protected array $values = [];

protected bool $readOnly = false;

/** @param iterable<T> $values */
/** @param iterable<TValueType> $values */
public function __construct(
iterable $values = [],
private readonly ValueAlreadyExistsModeEnum $valueAlreadyExistsMode = ValueAlreadyExistsModeEnum::ADD
Expand Down Expand Up @@ -130,27 +130,39 @@ public function changeKeyCase(KeyCaseEnum $case = KeyCaseEnum::LOWER): static
->replace(array_change_key_case($this->toArray(), $case->value));
}

/** @return array<string|int, T> */
/** @return array<string|int, TValueType> */
public function toArray(): array
{
return $this->values;
}

/** @param iterable<T> $values */
/** @param TValueType $value */
public function set(string|int $key, mixed $value): static
{
$this->assertIsNotReadOnly();

if ($this->canAddValue($value)) {
$this->values[$key] = $value;
}

return $this;
}

/** @param iterable<TValueType> $values */
public function replace(iterable $values): static
{
$this->assertIsNotReadOnly();

$this->clear();
foreach ($values as $key => $value) {
$this->doSet($key, $value);
$this->set($key, $value);
}
reset($this->values);

return $this;
}

/** @return T */
/** @return TValueType */
public function get(string|int $key): mixed
{
if ($this->hasKey($key) === false) {
Expand All @@ -160,26 +172,14 @@ public function get(string|int $key): mixed
return $this->values[$key];
}

/** @param T $value */
/** @param TValueType $value */
public function contains(mixed $value): bool
{
return in_array($value, $this->values, true);
}

/** @param T $value */
protected function doSet(string|int $key, mixed $value): static
{
$this->assertIsNotReadOnly();

if ($this->canAddValue($value)) {
$this->values[$key] = $value;
}

return $this;
}

/** @param T $value */
protected function doAdd(mixed $value): static
/** @param TValueType $value */
public function add(mixed $value): static
{
$this->assertIsNotReadOnly();

Expand All @@ -190,7 +190,8 @@ protected function doAdd(mixed $value): static
return $this;
}

protected function doMerge(CollectionInterface $collection): static
/** @param CollectionInterface<TValueType> $collection */
public function merge(CollectionInterface $collection): static
{
return $this->replace(array_merge($this->values, $collection->toArray()));
}
Expand All @@ -205,15 +206,15 @@ protected function assertIsNotReadOnly(): static
}

/**
* @param T $firstValue
* @param T $secondValue
* @param TValueType $firstValue
* @param TValueType $secondValue
*/
protected function isSameValues(mixed $firstValue, mixed $secondValue): bool
{
return $firstValue === $secondValue;
}

/** @param T $value */
/** @param TValueType $value */
protected function castValueToString(mixed $value): string
{
return is_null($value) ? 'NULL' : (string) $value;
Expand Down
Loading

0 comments on commit 7958531

Please sign in to comment.