Skip to content

Commit

Permalink
Merge pull request #4 from Crell/prep
Browse files Browse the repository at this point in the history
Prep for 1.0.0.
  • Loading branch information
Crell committed Oct 31, 2023
2 parents 88d6f34 + b26c9d8 commit 6c85064
Show file tree
Hide file tree
Showing 34 changed files with 231 additions and 151 deletions.
File renamed without changes.
File renamed without changes.
23 changes: 0 additions & 23 deletions .github/workflows/phpstan.yaml

This file was deleted.

24 changes: 0 additions & 24 deletions .github/workflows/phpunit.yaml

This file was deleted.

41 changes: 41 additions & 0 deletions .github/workflows/quality-assurance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: Quality assurance
on:
push:
branches: ['master']
pull_request: ~

jobs:
phpunit:
name: PHPUnit tests on ${{ matrix.php }} ${{ matrix.composer-flags }}
runs-on: ubuntu-latest
strategy:
matrix:
php: [ '8.1', '8.2', '8.3' ]
composer-flags: [ '' ]
phpunit-flags: [ '--coverage-text' ]
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
tools: composer:v2
- run: composer install --no-progress ${{ matrix.composer-flags }}
- run: vendor/bin/phpunit ${{ matrix.phpunit-flags }}
phpstan:
name: PHPStan checks on ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
matrix:
php: [ '8.1', '8.2', '8.3' ]
composer-flags: [ '' ]
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
tools: composer:v2
- run: composer install --no-progress ${{ matrix.composer-flags }}
- run: vendor/bin/phpstan
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ build
composer.lock
vendor
.phpunit.result.cache
.phpunit.cache
/vendor-bin/**/vendor
.env
profiles
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ Note that if a multi-value sub-attribute is `Inheritable`, ancestor classes will

Note: In order to make use of multi-value sub-attributes, the attribute class itself must be marked as "repeatable" as in the example above or PHP will generate an error. However, that is not sufficient for the Analyzer to parse it as multi-value. That's because attributes may also be multi-value when implementing scopes, but still only single-value from the Analzyer's point of view. See the section on Scopes below.

### Finalizing an attribute

Attributes that opt-in to several functional interfaces may not always have an easy time of knowing when to do default handling. It may not be obvious when the attribute setup is "done." Attribute classes may therefore opt in to the [`Finalizable`](src/Finalizable.php) interface. If specified, it is guaranteed to be the last method called on the attribute. The attribute may then do whatever final preparation is appropriate to consider the object "ready."

### Caching

The main `Analyzer` class does no caching whatsoever. However, it implements a `ClassAnalyzer` interface which allows it to be easily wrapped in other implementations that provide a caching layer.
Expand All @@ -342,10 +346,6 @@ Wrappers may also compose each other, so the following would be an entirely vali
$analyzer = new MemoryCacheAnalyzer(new Psr6CacheAnalyzer(new Analyzer(), $psr6CachePool));
```

## Finalizing an attribute

Attributes that opt-in to several functional interfaces may not always have an easy time of knowing when to do default handling. It may not be obvious when the attribute setup is "done." Attribute classes may therefore opt in to the [`Finalizable`](src/Finalizable.php) interface. If specified, it is guaranteed to be the last method called on the attribute. The attribute may then do whatever final preparation is appropriate to consider the object "ready."

## Advanced features

There are a couple of other advanced features also available. These are less frequently used, but in the right circumstances they can be very helpful.
Expand Down Expand Up @@ -473,7 +473,7 @@ class MyClass implements ParseProperties


#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)]
class FancyName implements Transitive
class FancyName implements TransitiveProperty
{
public function __construct(public readonly string $name = '') {}
}
Expand Down Expand Up @@ -606,11 +606,12 @@ class Alias implements Multivalue

#[Alias(first: 'Bruce', last: 'Wayne')]
#[Alias(first: 'Bat', last: 'Man')]
class Something
class Hero
{
// ...
}

$names = $analyzer->analyze(Something::class, Names::class);
$names = $analyzer->analyze(Hero::class, Names::class);

foreach ($names as $name) {
print $name->fullName() . PHP_EOL;
Expand Down Expand Up @@ -687,6 +688,7 @@ class Alias implements Name
#[Alias('The Caped Crusader')]
class Hero
{
// ...
}
```

Expand All @@ -699,9 +701,7 @@ Note that the interface must be marked `Multivalue` so that `Analyzer` will allo
In a similar vein, it's possible to use sub-attributes to declare that a component may be marked with one of a few attributes, but only one of them.

```php
interface DisplayType
{
}
interface DisplayType {}

#[\Attribute(\Attribute::TARGET_CLASS)]
class Screen implements DisplayType
Expand Down Expand Up @@ -771,7 +771,7 @@ If you discover any security related issues, please email larry at garfieldtech
- [Larry Garfield][link-author]
- [All Contributors][link-contributors]

Development of this library is sponsored by [TYPO3 GmbH](https://typo3.com/).
Initial development of this library was sponsored by [TYPO3 GmbH](https://typo3.com/).

## License

Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
],
"require": {
"php": "~8.1",
"crell/fp": "~0.4.0"
"crell/fp": "~1.0.0"
},
"require-dev": {
"phpbench/phpbench": "^1.1",
"phpstan/phpstan": "^1.0",
"phpunit/phpunit": "~9.0",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "~10.3",
"psr/cache": "^3.0",
"psr/cache-util": "^2.0"
},
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ services:
environment:
XDEBUG_MODE: "develop,debug"
XDEBUG_CONFIG: "client_host=${HOST_IP} idekey=${IDE_KEY} client_port=${XDEBUG_PORT} discover_client_host=1 start_with_request=1"
php82:
build: ./docker/php/82
volumes:
- .:/usr/src/myapp
- ./docker/php/82/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
- ./docker/php/conf.d/error_reporting.ini:/usr/local/etc/php/conf.d/error_reporting.ini
environment:
XDEBUG_MODE: "develop,debug"
XDEBUG_CONFIG: "client_host=${HOST_IP} idekey=${IDE_KEY} client_port=${XDEBUG_PORT} discover_client_host=1 start_with_request=1"
profile:
build: ./docker/php/81
volumes:
Expand Down
11 changes: 4 additions & 7 deletions docker/php/81/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
FROM php:8.1.4-cli
FROM php:8.1-cli
WORKDIR /usr/src/myapp
CMD [ "vendor/bin/phpunit" ]

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

RUN apt-get update && apt-get install zip unzip git -y \
&& pecl install xdebug \
&& php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
&& php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
&& php composer-setup.php --install-dir=/usr/bin --filename=composer \
&& php -r "unlink('composer-setup.php');" \
&& mkdir /.composer && chmod 777 /.composer
&& pecl install pcov
8 changes: 8 additions & 0 deletions docker/php/82/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM php:8.2-cli
WORKDIR /usr/src/myapp

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

RUN apt-get update && apt-get install zip unzip git -y \
&& pecl install xdebug \
&& pecl install pcov
2 changes: 2 additions & 0 deletions docker/php/82/xdebug.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so
xdebug.output_dir=profiles
4 changes: 3 additions & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
parameters:
level: 6
level: 8
paths:
- src
checkGenericClassInNonGenericObjectType: false
ignoreErrors:
- '#Match expression does not handle remaining value#'
- '#has an uninitialized readonly property (.*). Assign it in the constructor.#'
- '#Readonly property (.*) is assigned outside of the constructor.#'
# I don't know what's up with this one. Need to come back to it and fix the docblock.
- '#Method (.*)::analyze\(\) should return T of object but returns object.#'

# Tests include an absurd number of classes that are by design not tricked-out, so don't
# bother checking those.
16 changes: 9 additions & 7 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
<exclude>
<directory suffix=".php">src/Attributes/Reflect</directory>
</exclude>
<report>
<clover outputFile="build/logs/clover.xml"/>
<html outputDirectory="build/coverage"/>
Expand All @@ -21,4 +15,12 @@
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
<source>
<include>
<directory suffix=".php">src/</directory>
</include>
<exclude>
<directory suffix=".php">src/Attributes/Reflect</directory>
</exclude>
</source>
</phpunit>
24 changes: 13 additions & 11 deletions src/AttributeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ public function getInheritedAttributes(\Reflector $target, string $name): array
&& $class = $this->getPropertyClass($target))
{
return pipe($this->classAncestors($class),
// PHPStan gets confused by firstValue() and thinks $c is a string, not class-string.
// @phpstan-ignore-next-line
firstValue(fn (string $c): array => $this->getAttributes(new \ReflectionClass($c), $name)),
) ?? [];
}
Expand All @@ -216,20 +218,12 @@ protected function attributeInheritanceTree(\Reflector $subject, string $attribu
yield $subject;

if (is_a($attributeType, Inheritable::class, true)) {
// PHPStan doesn't understand that this is guaranteed type safe, so
// ignore it.
yield from match(get_class($subject)) {
// @phpstan-ignore-next-line
\ReflectionClass::class => $this->classInheritanceTree($subject),
// @phpstan-ignore-next-line
\ReflectionObject::class => $this->classInheritanceTree($subject),
// @phpstan-ignore-next-line
\ReflectionProperty::class => $this->classElementInheritanceTree($subject),
// @phpstan-ignore-next-line
\ReflectionMethod::class => $this->classElementInheritanceTree($subject),
// @phpstan-ignore-next-line
\ReflectionClassConstant::class => $this->classElementInheritanceTree($subject),
// @phpstan-ignore-next-line
\ReflectionParameter::class => $this->parameterInheritanceTree($subject),
// If it's an enum, there's nothing to inherit so just stub that out.
\ReflectionEnum::class => [],
Expand Down Expand Up @@ -271,10 +265,16 @@ protected function parameterInheritanceTree(\ReflectionParameter $subject): iter
$parameterName = $subject->getName();
$methodName = $subject->getDeclaringFunction()->name;

foreach ($this->classAncestors($subject->getDeclaringClass()->name) as $class) {
$declaringClass = $subject->getDeclaringClass()?->name;

if (!$declaringClass) {
return;
}

foreach ($this->classAncestors($declaringClass) as $class) {
$rClass = new \ReflectionClass($class);
if ($rClass->hasMethod($methodName)) {
$rMethod = $rClass->getMethod($parameterName);
$rMethod = $rClass->getMethod($methodName);
foreach ($rMethod->getParameters() as $rParam) {
if ($rParam->name === $parameterName) {
yield $rParam;
Expand Down Expand Up @@ -318,11 +318,13 @@ protected function classElementInheritanceTree(\ReflectionProperty|\ReflectionMe
/**
* Returns a list of all class and interface parents of a class.
*
* @param class-string $class
* @return array<class-string>
*/
public function classAncestors(string $class, bool $includeClass = true): array
{
// These methods both return associative arrays, making + safe.
/** @var array<class-string> $ancestors */
$ancestors = class_parents($class) + class_implements($class);
return $includeClass
? [$class => $class] + $ancestors
Expand All @@ -335,7 +337,7 @@ public function classAncestors(string $class, bool $includeClass = true): array
*
* @param \ReflectionProperty $rProperty
* The property to check
* @return string|null
* @return class-string|null
* The class/interface name, or null.
*/
protected function getPropertyClass(\ReflectionProperty $rProperty): ?string
Expand Down
5 changes: 5 additions & 0 deletions src/Attributes/Reflect/CollectClassConstants.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ trait CollectClassConstants
/** @var ReflectClassConstant[] */
public readonly array $constants;

/**
*
*
* @param ReflectClassConstant[] $constants
*/
public function setConstants(array $constants): void
{
$this->constants = $constants;
Expand Down
3 changes: 3 additions & 0 deletions src/Attributes/Reflect/CollectEnumCases.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ trait CollectEnumCases
/** @var ReflectEnumCase[] */
public readonly array $cases;

/**
* @param ReflectEnumCase[] $cases
*/
public function setCases(array $cases): void
{
$this->cases = $cases;
Expand Down
3 changes: 3 additions & 0 deletions src/Attributes/Reflect/CollectMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ trait CollectMethods
/** @var ReflectMethod[] */
public readonly array $methods;

/**
* @param ReflectMethod[] $methods
*/
public function setMethods(array $methods): void
{
$this->methods = $methods;
Expand Down
3 changes: 3 additions & 0 deletions src/Attributes/Reflect/CollectParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ trait CollectParameters
/** @var ReflectParameter[] */
public readonly array $parameters;

/**
* @param ReflectParameter[] $parameters
*/
public function setParameters(array $parameters): void
{
$this->parameters = $parameters;
Expand Down

0 comments on commit 6c85064

Please sign in to comment.