Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Migration to phpstan/phpstan 1.10 #126

Merged
merged 5 commits into from
Jan 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .scenarios.lock/symfony4/composer.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
"require-dev": {
"symfony/cache": "^4.0 || ^5.0 || ^6.0",
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^0.12.38",
"phpstan/phpstan": "^1.10",
"mapado/php-cs-fixer-config": "^3.2",
"g1a/composer-test-scenarios": "^3.0",
"giggsey/libphonenumber-for-php": "^8.0",
226 changes: 113 additions & 113 deletions .scenarios.lock/symfony4/composer.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .scenarios.lock/symfony5/composer.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
"require-dev": {
"symfony/cache": "^4.0 || ^5.0 || ^6.0",
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^0.12.38",
"phpstan/phpstan": "^1.10",
"mapado/php-cs-fixer-config": "^3.2",
"g1a/composer-test-scenarios": "^3.0",
"giggsey/libphonenumber-for-php": "^8.0",
238 changes: 119 additions & 119 deletions .scenarios.lock/symfony5/composer.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .scenarios.lock/symfony6/composer.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
"require-dev": {
"symfony/cache": "^4.0 || ^5.0 || ^6.0",
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^0.12.38",
"phpstan/phpstan": "^1.10",
"mapado/php-cs-fixer-config": "^3.2",
"g1a/composer-test-scenarios": "^3.0",
"giggsey/libphonenumber-for-php": "^8.0",
226 changes: 113 additions & 113 deletions .scenarios.lock/symfony6/composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Tests/Model/JsonLd/Product.php
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ class Product
* @var int
*
* @Rest\Id
*
* @Rest\Attribute(name="id", type="integer")
*/
private $id;
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
"require-dev": {
"symfony/cache": "^4.0 || ^5.0 || ^6.0",
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^0.12.38",
"phpstan/phpstan": "^1.10",
"mapado/php-cs-fixer-config": "^3.2",
"g1a/composer-test-scenarios": "^3.0",
"giggsey/libphonenumber-for-php": "^8.0",
30 changes: 16 additions & 14 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -5,6 +5,10 @@
"lint-staged": "^9.4.0",
"prettier": "^1.18.2"
},
"scripts": {
"stan": "vendor/bin/phpstan analyse src/ -c phpstan.neon",
"phpunit": "vendor/bin/phpunit"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
20 changes: 19 additions & 1 deletion src/Collection/Collection.php
Original file line number Diff line number Diff line change
@@ -59,7 +59,11 @@ public function __serialize(): array
*/
public function __unserialize($values): void
{
$this->elements = unserialize($values);
$unserializedValues = unserialize($values);

if (is_array($unserializedValues)) {
$this->elements = $unserializedValues;
}
}

/**
@@ -167,6 +171,20 @@ public function getExtraProperties(): array
return $this->extraProperties;
}

public function getStringExtraProperty(string $key): ?string
{
$value = $this->getExtraProperty($key);

return is_string($value) ? $value : null;
}

public function getIntExtraProperty(string $key): ?int
{
$value = $this->getExtraProperty($key);

return is_int($value) ? $value : null;
}

/**
* return the value of an extra property
*
8 changes: 4 additions & 4 deletions src/Collection/HydraPaginatedCollection.php
Original file line number Diff line number Diff line change
@@ -16,30 +16,30 @@ class HydraPaginatedCollection extends Collection
*/
public function getFirstPage(): ?string
{
return $this->getExtraProperty('hydra:firstPage');
return $this->getStringExtraProperty('hydra:firstPage');
}

/**
* Returns last page URI.
*/
public function getLastPage(): ?string
{
return $this->getExtraProperty('hydra:lastPage');
return $this->getStringExtraProperty('hydra:lastPage');
}

/**
* Returns next page URI.
*/
public function getNextPage(): ?string
{
return $this->getExtraProperty('hydra:nextPage');
return $this->getStringExtraProperty('hydra:nextPage');
}

/**
* Returns total item count.
*/
public function getTotalItems(): int
{
return $this->getExtraProperty('hydra:totalItems') ?? 0;
return $this->getIntExtraProperty('hydra:totalItems') ?? 0;
}
}
32 changes: 20 additions & 12 deletions src/EntityRepository.php
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ class EntityRepository
protected $sdk;

/**
* @var string
* @var class-string
*/
protected $entityName;

@@ -53,7 +53,7 @@ class EntityRepository
*
* @param SdkClient $sdkClient The client to connect to the datasource with
* @param RestClient $restClient The client to process the http requests
* @param string $entityName The entity to work with
* @param class-string $entityName The entity to work with
*/
public function __construct(
SdkClient $sdkClient,
@@ -122,7 +122,7 @@ public function __call(string $method, array $arguments)

$data = $this->assertArray($data, $methodName);
$entityList = ArrayHelper::arrayGet($data, $collectionKey);
if (!empty($entityList)) {
if (!empty($entityList) && is_array($entityList)) {
$data = current($entityList);
$hydratedData = $hydrator->hydrate($data, $this->entityName);

@@ -144,8 +144,11 @@ public function __call(string $method, array $arguments)
// then cache each entity from list
foreach ($hydratedData as $entity) {
$identifier = $entity->{$this->getClassMetadata()->getIdGetter()}();
$this->saveToCache($identifier, $entity);
$this->unitOfWork->registerClean($identifier, $entity);

if (is_object($entity)) {
$this->saveToCache($identifier, $entity);
$this->unitOfWork->registerClean($identifier, $entity);
}
}
}

@@ -157,10 +160,9 @@ public function __call(string $method, array $arguments)
/**
* find - finds one item of the entity based on the @REST\Id field in the entity
*
* @param string|int|mixed $id id of the element to fetch
* @param array $queryParams query parameters to add to the query
*/
public function find($id, array $queryParams = []): ?object
public function find(string|int $id, array $queryParams = []): ?object
{
$hydrator = $this->sdk->getModelHydrator();
$id = $hydrator->convertId($id, $this->entityName);
@@ -169,7 +171,7 @@ public function find($id, array $queryParams = []): ?object

// if entity is found in cache, return it
$entityFromCache = $this->fetchFromCache($id);
if (false != $entityFromCache) {
if ($entityFromCache) {
return $entityFromCache;
}

@@ -216,7 +218,12 @@ public function findAll(): Collection

// then cache each entity from list
foreach ($entityList as $entity) {
if (!is_object($entity)) {
throw new \RuntimeException("Entity should be an object. This should not happen.");
}

$identifier = $entity->{$this->getClassMetadata()->getIdGetter()}();

$this->unitOfWork->registerClean($identifier, $entity);
$this->saveToCache($identifier, $entity);
}
@@ -325,10 +332,7 @@ public function persist(
return $out;
}

/**
* @return object|false
*/
protected function fetchFromCache(string $key)
protected function fetchFromCache(string $key): object|false
{
$key = $this->normalizeCacheKey($key);
$cacheItemPool = $this->sdk->getCacheItemPool();
@@ -338,6 +342,10 @@ protected function fetchFromCache(string $key)
$cacheItem = $cacheItemPool->getItem($cacheKey);
$cacheData = $cacheItem->get();

if (!is_object($cacheData)) {
throw new \RuntimeException('Cache data should be an object. This should not happen.');
}

return $cacheData;
}
}
7 changes: 5 additions & 2 deletions src/Helper/ArrayHelper.php
Original file line number Diff line number Diff line change
@@ -19,8 +19,11 @@ class ArrayHelper
*
* @return mixed
*/
public static function arrayGet(array $array, ?string $key, $default = null)
{
public static function arrayGet(
array $array,
?string $key,
$default = null
): mixed {
if (null === $key) {
return $array;
}
4 changes: 3 additions & 1 deletion src/Mapping.php
Original file line number Diff line number Diff line change
@@ -72,6 +72,8 @@ public function setMapping(array $classMetadataList): self

/**
* return a model class name for a given key
*
* @return class-string
*/
public function getModelName(string $key): string
{
@@ -211,7 +213,7 @@ private function checkMappingExistence(
}

if ($checkModelName) {
if ('' === $metadata->getModelName()) {
if (empty($metadata->getModelName())) {
throw new MappingException(
$key . ' key is mapped but the model name is empty'
);
12 changes: 12 additions & 0 deletions src/Mapping/Annotations/Entity.php
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
* @author Julien Deniau <julien.deniau@mapado.com>
*
* @Annotation
*
* @Target("CLASS")
*/
final class Entity
@@ -29,4 +30,15 @@ final class Entity
* @var string
*/
public $repository;

/**
* @return ?class-string
*/
public function getRepository(): ?string
{
/** @var ?class-string $repository */
$repository = $this->repository;

return $repository;
Comment on lines +39 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do not return repository & update type definition on property declaration ?

Suggested change
/** @var ?class-string $repository */
$repository = $this->repository;
return $repository;
return $this->repository;

Copy link
Contributor Author

@JulienRAVIA JulienRAVIA Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's for the phpDoc ;) else it's not handled
it's very temporary, this pr : #124 will overwrite it.

}
}
21 changes: 19 additions & 2 deletions src/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ class ClassMetadata
/**
* Model name (entity class with full namespace, ie: "Foo\Entity\Article").
*
* @var string
* @var class-string
*/
private $modelName;

@@ -32,7 +32,7 @@ class ClassMetadata
/**
* Repository name (repository class with full namespace, ie: "Foo\Repository\ArticleRepository").
*
* @var string
* @var class-string
*/
private $repositoryName;

@@ -57,6 +57,10 @@ class ClassMetadata
*/
private $identifierAttribute;

/**
* @param class-string $modelName
* @param class-string $repositoryName
*/
public function __construct(
string $key,
string $modelName,
@@ -69,11 +73,18 @@ public function __construct(
$this->relationList = [];
}

/**
* @return class-string
*/
public function getModelName(): string
{
return $this->modelName;
}

/**
* @param class-string $modelName
* @return $this
*/
public function setModelName(string $modelName): self
{
$this->modelName = $modelName;
@@ -194,11 +205,17 @@ public function getRelation(string $key): ?Relation
return null;
}

/**
* @return class-string
*/
public function getRepositoryName(): string
{
return $this->repositoryName;
}

/**
* @param class-string $repositoryName
*/
public function setRepositoryName(string $repositoryName): self
{
$this->repositoryName = $repositoryName;
3 changes: 2 additions & 1 deletion src/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ public function loadDirectory(string $path): array
throw new MappingException($path . ' is not a valid directory');
}

/** @var array<int, array|string> $iterator */
$iterator = new \RegexIterator(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
@@ -199,7 +200,7 @@ private function getClassMetadataForClassname(
$classMetadata = new ClassMetadata(
$classAnnotation->key,
$classname,
$classAnnotation->repository
$classAnnotation->getRepository()
);
$classMetadata->setAttributeList($attributeList);
$classMetadata->setRelationList($relationList);
43 changes: 27 additions & 16 deletions src/Model/ModelHydrator.php
Original file line number Diff line number Diff line change
@@ -27,10 +27,7 @@ public function __construct(SdkClient $sdk)
$this->sdk = $sdk;
}

/**
* @param string|int|mixed $id
*/
public function convertId($id, string $modelName): string
public function convertId(string|int $id, string $modelName): string
{
$id = (string) $id;

@@ -48,9 +45,6 @@ public function convertId($id, string $modelName): string
return $id;
}

/**
* convert data as array to entity
*/
public function hydrate(?array $data, string $modelName): ?object
{
$mapping = $this->sdk->getMapping();
@@ -62,6 +56,8 @@ public function hydrate(?array $data, string $modelName): ?object

/**
* convert API response to Collection containing entities
*
* @param class-string $modelName
*/
public function hydrateList(?array $data, string $modelName): Collection
{
@@ -76,30 +72,45 @@ public function hydrateList(?array $data, string $modelName): Collection

/**
* convert list of data as array to Collection containing entities
*
* @param class-string $modelName
*/
private function deserializeAll(array $data, string $modelName): Collection
{
$collectionKey = $this->sdk->getMapping()->getConfig()['collectionKey'];

$itemList = array_map(function ($member) use ($modelName) {
return $this->deserialize($member, $modelName);
}, ArrayHelper::arrayGet($data, $collectionKey));
$itemList = ArrayHelper::arrayGet($data, $collectionKey);

if (!is_array($itemList)) {
throw new \RuntimeException(sprintf(
'Unable to deserialize collection, %s key not found in response',
$collectionKey
));
}

$itemList = array_map(fn (?array $member) => $this->deserialize($member, $modelName), $itemList);

$extraProperties = array_filter(
$data,
function ($key) use ($collectionKey) {
return $key !== $collectionKey;
},
ARRAY_FILTER_USE_KEY
fn ($key) => $key !== $collectionKey,
\ARRAY_FILTER_USE_KEY
);

/** @var class-string $collectionClassName */
$collectionClassName = $this->guessCollectionClassname($data);

if (!class_exists($collectionClassName)) {
throw new \RuntimeException("Seem's like $collectionClassName does not exist");
}

/** @var Collection */
return new $collectionClassName($itemList, $extraProperties);
}

/**
* convert array to entity
*
* @param class-string $modelName
*/
private function deserialize(?array $data, string $modelName): ?object
{
@@ -116,8 +127,8 @@ private function deserialize(?array $data, string $modelName): ?object
private function guessCollectionClassname(array $data): string
{
switch (true) {
case !empty($data['@type']) &&
'hydra:PagedCollection' === $data['@type']:
case !empty($data['@type'])
&& 'hydra:PagedCollection' === $data['@type']:
return HydraPaginatedCollection::class;

case array_key_exists('_embedded', $data):
35 changes: 16 additions & 19 deletions src/Model/Serializer.php
Original file line number Diff line number Diff line change
@@ -86,6 +86,10 @@ public function serialize(
return $out;
}

/**
* @param array $data
* @param class-string $className
*/
public function deserialize(array $data, string $className): object
{
$className = $this->resolveRealClassName($data, $className);
@@ -96,6 +100,12 @@ public function deserialize(array $data, string $className): object

$instance = new $className();

if (!is_object($instance)) {
throw new \RuntimeException(
"The class $className is not instantiable"
);
}

if ($attributeList) {
foreach ($attributeList as $attribute) {
$key = $attribute->getSerializedKey();
@@ -145,6 +155,12 @@ public function deserialize(array $data, string $className): object

if (isset($value)) {
if ('datetime' === $attribute->getType()) {
if (!is_string($value)) {
throw new \RuntimeException(
"The value for $attributeName to cast to datetime value should be a string"
);
}

$this->setDateTimeValue(
$instance,
$attributeName,
@@ -396,25 +412,6 @@ private function setDateTimeValue(
throw $e;
}

// The excepted value is a DateTimeImmutable, so let's do that
$this->propertyAccessor->setValue(
$instance,
$attributeName,
new DateTimeImmutable($value)
);
} catch (\TypeError $e) {
// this `catch` block can be dropped when minimum support of symfony/property-access is 3.4
if (
false ===
mb_strpos(
$e->getMessage(),
'must be an instance of DateTimeImmutable, instance of DateTime given'
)
) {
// not an issue with DateTimeImmutable, then rethrow exception
throw $e;
}

// The excepted value is a DateTimeImmutable, so let's do that
$this->propertyAccessor->setValue(
$instance,
68 changes: 59 additions & 9 deletions src/RestClient.php
Original file line number Diff line number Diff line change
@@ -99,9 +99,21 @@ public function get(string $path, array $parameters = [])
if (null !== $response && 404 === $response->getStatusCode()) {
return null;
}
throw new RestClientException('Error while getting resource', $path, [], 7, $e);
throw new RestClientException(
'Error while getting resource',
$path,
[],
7,
$e
);
} catch (TransferException $e) {
throw new RestException('Error while getting resource', $path, [], 1, $e);
throw new RestException(
'Error while getting resource',
$path,
[],
1,
$e
);
}
}

@@ -115,7 +127,13 @@ public function delete(string $path): void
} catch (ClientException $e) {
return;
} catch (TransferException $e) {
throw new RestException('Error while deleting resource', $path, [], 2, $e);
throw new RestException(
'Error while deleting resource',
$path,
[],
2,
$e
);
}
}

@@ -135,9 +153,21 @@ public function post(string $path, array $data, array $parameters = [])
$parameters
);
} catch (ClientException $e) {
throw new RestClientException('Cannot create resource', $path, [], 3, $e);
throw new RestClientException(
'Cannot create resource',
$path,
[],
3,
$e
);
} catch (TransferException $e) {
throw new RestException('Error while posting resource', $path, [], 4, $e);
throw new RestException(
'Error while posting resource',
$path,
[],
4,
$e
);
}
}

@@ -158,9 +188,21 @@ public function put(string $path, array $data, array $parameters = [])
$parameters
);
} catch (ClientException $e) {
throw new RestClientException('Cannot update resource', $path, [], 5, $e);
throw new RestClientException(
'Cannot update resource',
$path,
[],
5,
$e
);
} catch (TransferException $e) {
throw new RestException('Error while puting resource', $path, [], 6, $e);
throw new RestException(
'Error while puting resource',
$path,
[],
6,
$e
);
}
}

@@ -180,7 +222,12 @@ protected function mergeDefaultParameters(array $parameters): array
$out = array_replace_recursive($defaultParameters, $parameters);

if (null === $out) {
throw new \RuntimeException(sprintf('Error while calling array_replace_recursive in %s. This should not happen.', __METHOD__));
throw new \RuntimeException(
sprintf(
'Error while calling array_replace_recursive in %s. This should not happen.',
__METHOD__
)
);
}

return $out;
@@ -261,7 +308,10 @@ private function executeRequest(
}

if ($requestIsJson) {
return json_decode((string) $response->getBody(), true);
/** @var array $decodedJson */
$decodedJson = json_decode((string) $response->getBody(), true);

return $decodedJson;
} else {
return $response;
}
5 changes: 4 additions & 1 deletion src/SdkClient.php
Original file line number Diff line number Diff line change
@@ -184,11 +184,14 @@ public function createProxy(string $id): GhostObjectInterface

$proxyModelName = preg_replace('/^\\\\*/', '', $modelName);

/**
* @var \Closure(RealObjectType&GhostObjectInterface<RealObjectType>, string, array<string, mixed>, ?Closure, array<string, mixed>) $initializer
*/
$initializer = function (
GhostObjectInterface $proxy,
string $method,
array $parameters,
&$initializer,
\Closure|null &$initializer,
array $properties
) use ($sdk, $classMetadata, $id, $proxyModelName) {
$isAllowedMethod =
6 changes: 3 additions & 3 deletions src/UnitOfWork.php
Original file line number Diff line number Diff line change
@@ -194,11 +194,11 @@ private function getDirtyFields(
/**
* add defined identifiers to given model
*
* @param mixed $newSerializedModel
* @param ?mixed $idSerializedKey
* @param array $newSerializedModel
* @param ?string $idSerializedKey
*/
private function addIdentifiers(
$newSerializedModel,
array $newSerializedModel,
array $dirtyFields,
$idSerializedKey = null
): array {