Skip to content

Commit

Permalink
feat: PHP 8 attributes (#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarlovi authored Dec 2, 2021
1 parent b8ea305 commit 589588c
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 9 deletions.
18 changes: 17 additions & 1 deletion 7.3-phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
includes:
- phpstan.neon.dist
- /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-deprecation-rules/rules.neon
- /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon
- /tools/.composer/vendor-bin/phpstan/vendor/phpstan/phpstan-phpunit/extension.neon
parameters:
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
tmpDir: %currentWorkingDirectory%/var/phpstan
level: max
paths:
- ./
excludePaths:
- var/
- vendor/
- Test/Model/ProductWithAttributes.php

# false positive: PHPStan loads the first file from this one (?)
- Test/Functional/FunctionalTestCase.php
ignoreErrors:
# PHPStan does not process version guards correctly
- '#Call to an undefined method.*getAttributes#'
60 changes: 60 additions & 0 deletions 7.3-psalm.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0"?>
<psalm xmlns="https://getpsalm.org/schema/config"
cacheDirectory="var/psalm"
errorLevel="1"
findUnusedVariablesAndParams="true">
<projectFiles>
<directory name="."/>
<ignoreFiles>
<directory name="var/"/>
<directory name="vendor/"/>
</ignoreFiles>
</projectFiles>

<issueHandlers>
<!-- TODO: false positive -->
<MixedArgument>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedArgument>
<MixedInferredReturnType>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedInferredReturnType>
<MixedMethodCall>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedMethodCall>
<MixedReturnStatement>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedReturnStatement>
<UndefinedMethod>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</UndefinedMethod>
<!-- /TODO: false positive -->

<MissingConstructor>
<errorLevel type="info">
<directory name="Annotation/" />
<directory name="Test/" />
</errorLevel>
</MissingConstructor>
<RedundantCondition>
<errorLevel type="info">
<file name="Metadata/Annotation/AnnotationDriver.php" />
</errorLevel>
</RedundantCondition>
<PropertyNotSetInConstructor>
<errorLevel type="info">
<directory name="Test/" />
</errorLevel>
</PropertyNotSetInConstructor>
</issueHandlers>
</psalm>
8 changes: 8 additions & 0 deletions 7.4-phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
includes:
- phpstan.neon.dist
parameters:
ignoreErrors:
# PHPStan does not process version guards correctly
- '#Call to an undefined method.*getAttributes#'
excludePaths:
- Test/Model/ProductWithAttributes.php
60 changes: 60 additions & 0 deletions 7.4-psalm.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0"?>
<psalm xmlns="https://getpsalm.org/schema/config"
cacheDirectory="var/psalm"
errorLevel="1"
findUnusedVariablesAndParams="true">
<projectFiles>
<directory name="."/>
<ignoreFiles>
<directory name="var/"/>
<directory name="vendor/"/>
</ignoreFiles>
</projectFiles>

<issueHandlers>
<!-- TODO: false positive -->
<MixedArgument>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedArgument>
<MixedInferredReturnType>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedInferredReturnType>
<MixedMethodCall>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedMethodCall>
<MixedReturnStatement>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</MixedReturnStatement>
<UndefinedMethod>
<errorLevel type="suppress">
<file name="Metadata/Annotation/AnnotationDriver.php"/>
</errorLevel>
</UndefinedMethod>
<!-- /TODO: false positive -->

<MissingConstructor>
<errorLevel type="info">
<directory name="Annotation/" />
<directory name="Test/" />
</errorLevel>
</MissingConstructor>
<RedundantCondition>
<errorLevel type="info">
<file name="Metadata/Annotation/AnnotationDriver.php" />
</errorLevel>
</RedundantCondition>
<PropertyNotSetInConstructor>
<errorLevel type="info">
<directory name="Test/" />
</errorLevel>
</PropertyNotSetInConstructor>
</issueHandlers>
</psalm>
1 change: 1 addition & 0 deletions Annotation/ArrayReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

/**
* @Annotation
* @Target({"PROPERTY"})
*/
final class ArrayReference
{
Expand Down
8 changes: 8 additions & 0 deletions Annotation/ColumnReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@

/**
* @Annotation
* @Target({"ANNOTATION", "PROPERTY"})
* @NamedArgumentConstructor
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class ColumnReference implements Reference
{
/**
* @var string
*/
public $column;

public function __construct(string $column)
{
$this->column = $column;
}
}
8 changes: 8 additions & 0 deletions Annotation/HeaderReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@

/**
* @Annotation
* @Target({"ANNOTATION", "PROPERTY"})
* @NamedArgumentConstructor
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class HeaderReference implements Reference
{
/**
* @var string
*/
public $header;

public function __construct(string $header)
{
$this->header = $header;
}
}
11 changes: 11 additions & 0 deletions Annotation/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

/**
* @Annotation
* @Target({"CLASS"})
* @NamedArgumentConstructor
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
final class Options
{
/**
Expand All @@ -37,4 +40,12 @@ final class Options
* @var bool
*/
public $reverse;

public function __construct(int $start = 0, int $end = \PHP_INT_MAX, int $header = 0, bool $reverse = false)
{
$this->start = $start;
$this->end = $end;
$this->header = $header;
$this->reverse = $reverse;
}
}
44 changes: 40 additions & 4 deletions Metadata/Annotation/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ private function getColumns(\ReflectionClass $reflectionClass): array
{
$columns = [];
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
$arrayAnnotation = $this->reader->getPropertyAnnotation(
$arrayAnnotation = $this->getPropertyAnnotationOrAttribute(
$reflectionProperty,
Annotation\ArrayReference::class
);
$columnAnnotation = $this->reader->getPropertyAnnotation(
$columnAnnotation = $this->getPropertyAnnotationOrAttribute(
$reflectionProperty,
Annotation\ColumnReference::class
);
$headerAnnotation = $this->reader->getPropertyAnnotation(
$headerAnnotation = $this->getPropertyAnnotationOrAttribute(
$reflectionProperty,
Annotation\HeaderReference::class
);
Expand Down Expand Up @@ -115,7 +115,7 @@ private function getColumns(\ReflectionClass $reflectionClass): array

private function getOptions(\ReflectionClass $reflectionClass, ?array $additionalOptions = null): array
{
$options = (array) $this->reader->getClassAnnotation($reflectionClass, Annotation\Options::class);
$options = $this->getClassAnnotationOrAttribute($reflectionClass, Annotation\Options::class);
if (null !== $additionalOptions) {
$options = array_replace($options, $additionalOptions);
}
Expand All @@ -141,4 +141,40 @@ private function createReference(Annotation\Reference $annotation)

return $reference;
}

/**
* @template T
*
* @param class-string<T> $name
*/
private function getClassAnnotationOrAttribute(\ReflectionClass $reflection, string $name): array
{
if (\PHP_VERSION_ID >= 80000) {
$attribute = current($reflection->getAttributes($name));
if ($attribute !== false) {
return $attribute->getArguments();
}
}

return (array) $this->reader->getClassAnnotation($reflection, $name);
}

/**
* @template T
*
* @param class-string<T> $name
*
* @phpstan-return T|null
*/
private function getPropertyAnnotationOrAttribute(\ReflectionProperty $reflection, string $name)
{
if (\PHP_VERSION_ID >= 80000) {
$attribute = current($reflection->getAttributes($name));
if ($attribute !== false) {
return $attribute->newInstance();
}
}

return $this->reader->getPropertyAnnotation($reflection, $name);
}
}
42 changes: 40 additions & 2 deletions Test/Functional/FunctionalTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Xezilaires\Test\FixtureTrait;
use Xezilaires\Test\IteratorMatcherTrait;
use Xezilaires\Test\Model\Product;
use Xezilaires\Test\Model\ProductWithAttributes;

/**
* @internal
Expand Down Expand Up @@ -111,6 +112,9 @@ public function testCanLoadFlatFixtureWithArrayReference(): void
], $iterator);
}

/**
* @uses \Xezilaires\Annotation\HeaderReference
*/
public function testCanLoadSparseFixtureWithHeaderReference(): void
{
$iterator = $this->createIterator(
Expand All @@ -137,6 +141,9 @@ public function testCanLoadSparseFixtureWithHeaderReference(): void

/**
* @uses \Xezilaires\Metadata\Annotation\AnnotationDriver
* @uses \Xezilaires\Annotation\ColumnReference
* @uses \Xezilaires\Annotation\HeaderReference
* @uses \Xezilaires\Annotation\Options
*
* @throws \ReflectionException
* @throws \RuntimeException
Expand All @@ -159,6 +166,37 @@ public function testCanLoadSparseFixtureWithAnnotations(): void
], $iterator);
}

/**
* @uses \Xezilaires\Metadata\Annotation\AnnotationDriver
* @uses \Xezilaires\Annotation\ColumnReference
* @uses \Xezilaires\Annotation\HeaderReference
* @uses \Xezilaires\Annotation\Options
*
* @throws \ReflectionException
* @throws \RuntimeException
* @throws \ReflectionException
*/
public function testCanLoadSparseFixtureWithNativeAttributes(): void
{
if (\PHP_VERSION_ID < 80000) {
static::markTestSkipped('Native PHP attributes available since PHP 8.0.0');
}

$driver = new AnnotationDriver();
$mapping = $driver->getMetadataMapping(ProductWithAttributes::class);

$iterator = $this->createIterator(
$this->getSpreadsheet($this->fixture('products-sparse.xlsx')),
$mapping
);

self::assertIteratorMatches([
2 => ['name' => 'The Very Hungry Caterpillar', 'price' => 6.59],
3 => ['name' => 'Brown Bear, Brown Bear, What Do You See?', 'price' => 6.51],
4 => ['name' => 'Stillhouse Lake', 'price' => 1.99],
], $iterator);
}

public function testCannotLoadFixtureWithAmbiguousHeaderReference(): void
{
$this->expectException(MappingException::class);
Expand Down Expand Up @@ -265,7 +303,7 @@ public function testCanFetchCurrentIteratorItem(): void

$current = new Product();
$current->name = 'Brown Bear, Brown Bear, What Do You See?';
$current->price = '6.51';
$current->price = 6.51;
static::assertEquals($current, $iterator->current());
}

Expand All @@ -291,7 +329,7 @@ public function testCanRewindIterator(): void
$iterator->rewind();
$current = new Product();
$current->name = 'The Very Hungry Caterpillar';
$current->price = '6.59';
$current->price = 6.59;
static::assertEquals($current, $iterator->current());
static::assertEquals(2, $iterator->key());
}
Expand Down
2 changes: 1 addition & 1 deletion Test/IteratorMatcherTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
trait IteratorMatcherTrait
{
/**
* @param array<int, array<string, array<string>|string>> $expected
* @param array<int, array<string, array<float|string>|float|string>> $expected
*/
private static function assertIteratorMatches(array $expected, \Iterator $iterator): void
{
Expand Down
2 changes: 1 addition & 1 deletion Test/Model/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class Product
public $name;

/**
* @var float|string
* @var float
*
* @Groups({"header", "product"})
* @XLS\HeaderReference(header="Price USD")
Expand Down
Loading

0 comments on commit 589588c

Please sign in to comment.