Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add PHP 8 attributes support #15

Merged
merged 15 commits into from
Sep 11, 2024
Merged
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.8.0]
### Added
- Support for PHP 8 attributes
- Support for `doctrine/annotations: ^2.0`

## [1.7.0]
### Added
- Support for Symfony 6.4
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
"paysera/lib-dependency-injection": "^1.3.0",
"psr/log": "^1.0|^2.0",
"doctrine/persistence": "^1.3.8 || ^2.0.1 || ^3.0",
"doctrine/annotations": "^v1.14"
"doctrine/annotations": "^1.14 || ^2.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^9.5",
"phpunit/phpunit": "^7.5 || ^9.6",
"mockery/mockery": "^1.3.6",
"symfony/yaml": "^3.4.34|^4.3|^5.4|^6.0",
"doctrine/doctrine-bundle": "^1.12.0|^2.1",
Expand Down
110 changes: 2 additions & 108 deletions src/Annotation/Body.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,122 +3,16 @@

namespace Paysera\Bundle\ApiBundle\Annotation;

use Paysera\Bundle\ApiBundle\Entity\RestRequestOptions;
use Paysera\Bundle\ApiBundle\Exception\ConfigurationException;
use Paysera\Bundle\ApiBundle\Service\Annotation\ReflectionMethodWrapper;
use Paysera\Bundle\ApiBundle\Attribute\Body as BodyAttribute;

/**
* @Annotation
* @Target({"METHOD"})
*/
class Body implements RestAnnotationInterface
class Body extends BodyAttribute implements RestAnnotationInterface
{
/**
* @var string
*/
private $parameterName;

/**
* @var string|null
*/
private $denormalizationType;

/**
* @var string|null
*/
private $denormalizationGroup;

/**
* @var bool|null
*/
private $optional;

public function __construct(array $options)
{
$this->setParameterName($options['parameterName']);
$this->setDenormalizationType($options['denormalizationType'] ?? null);
$this->setDenormalizationGroup($options['denormalizationGroup'] ?? null);
$this->setOptional($options['optional'] ?? null);
}

/**
* @param string|null $denormalizationType
* @return $this
*/
private function setDenormalizationType($denormalizationType): self
{
$this->denormalizationType = $denormalizationType;
return $this;
}

/**
* @param string|null $denormalizationGroup
* @return $this
*/
public function setDenormalizationGroup($denormalizationGroup): self
{
$this->denormalizationGroup = $denormalizationGroup;
return $this;
}

/**
* @param string $parameterName
* @return $this
*/
private function setParameterName(string $parameterName): self
{
$this->parameterName = $parameterName;
return $this;
}

/**
* @param bool|null $optional
* @return $this
*/
private function setOptional($optional): self
{
$this->optional = $optional;
return $this;
}

public function isSeveralSupported(): bool
{
return false;
}

public function apply(RestRequestOptions $options, ReflectionMethodWrapper $reflectionMethod)
{
$options->setBodyParameterName($this->parameterName);
$options->setBodyDenormalizationType($this->resolveDenormalizationType($reflectionMethod));
$options->setBodyDenormalizationGroup($this->denormalizationGroup);
$options->setBodyOptional($this->resolveIfBodyIsOptional($reflectionMethod));
}

private function resolveDenormalizationType(ReflectionMethodWrapper $reflectionMethod): string
{
if ($this->denormalizationType !== null) {
return $this->denormalizationType;
}

try {
$typeName = $reflectionMethod->getNonBuiltInTypeForParameter($this->parameterName);
} catch (ConfigurationException $exception) {
throw new ConfigurationException(sprintf(
'Denormalization type could not be guessed for %s in %s',
'$' . $this->parameterName,
$reflectionMethod->getFriendlyName()
));
}

return $typeName;
}

private function resolveIfBodyIsOptional(ReflectionMethodWrapper $reflectionMethod): bool
{
if ($this->optional !== null) {
return $this->optional;
}

return $reflectionMethod->getParameterByName($this->parameterName)->isDefaultValueAvailable();
}
}
47 changes: 3 additions & 44 deletions src/Annotation/BodyContentType.php
Original file line number Diff line number Diff line change
@@ -1,60 +1,19 @@
<?php

declare(strict_types=1);

namespace Paysera\Bundle\ApiBundle\Annotation;

use Paysera\Bundle\ApiBundle\Entity\RestRequestOptions;
use Paysera\Bundle\ApiBundle\Service\Annotation\ReflectionMethodWrapper;
use Paysera\Bundle\ApiBundle\Attribute\BodyContentType as BodyContentTypeAttribute;

/**
* @Annotation
* @Target({"METHOD"})
*/
class BodyContentType implements RestAnnotationInterface
class BodyContentType extends BodyContentTypeAttribute implements RestAnnotationInterface
{
/**
* @var array
*/
private $supportedContentTypes;

/**
* @var bool
*/
private $jsonEncodedBody;

public function __construct(array $options)
{
$this->setSupportedContentTypes($options['supportedContentTypes']);
$this->setJsonEncodedBody($options['jsonEncodedBody'] ?? false);
}

/**
* @param array $supportedContentTypes
* @return $this
*/
private function setSupportedContentTypes(array $supportedContentTypes): self
{
$this->supportedContentTypes = $supportedContentTypes;
return $this;
}

/**
* @param bool $jsonEncodedBody
* @return $this
*/
private function setJsonEncodedBody(bool $jsonEncodedBody): self
{
$this->jsonEncodedBody = $jsonEncodedBody;
return $this;
}

public function isSeveralSupported(): bool
{
return false;
}

public function apply(RestRequestOptions $options, ReflectionMethodWrapper $reflectionMethod)
{
$options->setSupportedContentTypes($this->supportedContentTypes, $this->jsonEncodedBody);
}
}
115 changes: 3 additions & 112 deletions src/Annotation/PathAttribute.php
Original file line number Diff line number Diff line change
@@ -1,128 +1,19 @@
<?php

declare(strict_types=1);

namespace Paysera\Bundle\ApiBundle\Annotation;

use Paysera\Bundle\ApiBundle\Entity\PathAttributeResolverOptions;
use Paysera\Bundle\ApiBundle\Entity\RestRequestOptions;
use Paysera\Bundle\ApiBundle\Exception\ConfigurationException;
use Paysera\Bundle\ApiBundle\Service\Annotation\ReflectionMethodWrapper;
use Paysera\Bundle\ApiBundle\Attribute\PathAttribute as PathAttributeAttribute;

/**
* @Annotation
* @Target({"METHOD"})
*/
class PathAttribute implements RestAnnotationInterface
class PathAttribute extends PathAttributeAttribute implements RestAnnotationInterface
{
/**
* @var string
*/
private $parameterName;

/**
* @var string
*/
private $pathPartName;

/**
* @var string|null
*/
private $resolverType;

/**
* @var bool|null
*/
private $resolutionMandatory;

public function __construct(array $options)
{
$this->setParameterName($options['parameterName']);
$this->setPathPartName($options['pathPartName']);
$this->setResolverType($options['resolverType'] ?? null);
$this->setResolutionMandatory($options['resolutionMandatory'] ?? null);
}

/**
* @param string $parameterName
* @return $this
*/
private function setParameterName(string $parameterName): self
{
$this->parameterName = $parameterName;
return $this;
}

/**
* @param string $pathPartName
* @return $this
*/
private function setPathPartName(string $pathPartName): self
{
$this->pathPartName = $pathPartName;
return $this;
}

/**
* @param string|null $resolverType
* @return $this
*/
private function setResolverType($resolverType): self
{
$this->resolverType = $resolverType;
return $this;
}

/**
* @param bool|null $resolutionMandatory
* @return $this
*/
private function setResolutionMandatory($resolutionMandatory): self
{
$this->resolutionMandatory = $resolutionMandatory;
return $this;
}

public function isSeveralSupported(): bool
{
return true;
}

public function apply(RestRequestOptions $options, ReflectionMethodWrapper $reflectionMethod)
{
$options->addPathAttributeResolverOptions(
(new PathAttributeResolverOptions())
->setParameterName($this->parameterName)
->setPathPartName($this->pathPartName)
->setPathAttributeResolverType($this->resolvePathAttributeResolverType($reflectionMethod))
->setResolutionMandatory($this->resolveIfResolutionIsMandatory($reflectionMethod))
);
}

private function resolvePathAttributeResolverType(ReflectionMethodWrapper $reflectionMethod): string
{
if ($this->resolverType !== null) {
return $this->resolverType;
}

try {
return $reflectionMethod->getNonBuiltInTypeForParameter($this->parameterName);
} catch (ConfigurationException $exception) {
throw new ConfigurationException(sprintf(
'Denormalization type could not be guessed for %s in %s',
'$' . $this->parameterName,
$reflectionMethod->getFriendlyName()
));
}
}

private function resolveIfResolutionIsMandatory(ReflectionMethodWrapper $reflectionMethod): bool
{
if ($this->resolutionMandatory !== null) {
return $this->resolutionMandatory;
}

$parameter = $reflectionMethod->getParameterByName($this->parameterName);

return !$parameter->isDefaultValueAvailable();
}
}
Loading