Skip to content

Commit

Permalink
Merge pull request #203 from koriym/support_doctrine_annotation_v2
Browse files Browse the repository at this point in the history
Remove doctrine annotation reader
  • Loading branch information
koriym authored Sep 11, 2023
2 parents 918e6f5 + eb89d47 commit 0961e4a
Show file tree
Hide file tree
Showing 55 changed files with 955 additions and 646 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
# Configure diff output for .php and .phar files.
*.php diff=php
*.phar -diff

/vendor-bin/**/composer.lock binary
44 changes: 31 additions & 13 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@ $bind = (new Bind)->bind(RealBillingService::class, [$pointcut]);
$billing = (new Weaver($bind, $tmpDir))->newInstance(RealBillingService::class, [$arg1, $arg2]);
```

マッチャーはdoctrineアノテーション、またはPHP8アトリビュートの読み込みをサポートします。

```php
public function matchesClass(\ReflectionClass $class, array $arguments) : bool
{
assert($class instanceof \Ray\Aop\ReflectionClass);
$classAnnotation = $class->getAnnotation(Foo::class); // @Foo or #[Foo]
// ...
}

public function matchesMethod(\ReflectionMethod $method, array $arguments) : bool
{
assert($method instanceof \Ray\Aop\ReflectionMethod);
$methodAnnotation = $method->getAnnotation(Bar::class);
}
```

## パフォーマンス

`Weaver`オブジェクトはキャッシュ可能です。コンパイル、束縛、アノテーション読み込みコストを削減します。
Expand Down Expand Up @@ -242,11 +259,6 @@ $class = $invocation->getMethod()->getDeclaringClass();

このメソッドインターセプターのAPIは[AOPアライアンス](http://aopalliance.sourceforge.net/doc/org/aopalliance/intercept/MethodInterceptor.html)の部分実装です。

## 要件

* PHP 5.6+
* hhvm

## インストール

Ray.Aopの推奨インストール方法は、[Composer](https://github.com/composer/composer)でのインストールです。
Expand All @@ -256,18 +268,24 @@ Ray.Aopの推奨インストール方法は、[Composer](https://github.com/comp
$ composer require ray/aop ~2.0
```

## Ray.Aopのテスト
## パフォーマンス

AOPクラスのコンパイルにより、Ray.Aopは高速に動作します。アノテーションの読み込みは初回コンパイル時のみなので、ランタイムのパフォーマンスに影響を与えません。開発段階や最初の実行時にも、ファイルのタイムスタンプを利用してPHPファイルがキャッシュされ、通常はアノテーション生成のコストを気にする必要はありませんが、アプリケーションのブートストラップでアノテーションリーダーの設定を行うことで、初回コンパイル時のパフォーマンスが向上します。特に大規模なアプリケーションでこの設定は役立ちます。

Ray.Aopをソースからインストールし、ユニットテストとデモを実行するには次のようにします。
### APCu

```bash
git clone https://github.com/ray-di/Ray.Aop.git
cd Ray.Aop
composer install
vendor/bin/phpunit
php demo/run.php
```php
SevericeLocator::setReader(new PsrCachedReader(new Reader(), $apcuCache));
```

### アトリビュートのみ使用(推奨)

```php
SevericeLocator::setReader(new AttributeReader);`
```

## DI Framework

DIとAOPを統合したDIフレームワーク[Ray.Di](https://github.com/ray-di/Ray.Di)もご覧ください。

* この文書の大部分は [Guice/AOP](https://github.com/google/guice/wiki/AOP) から借用しています。
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ $bind = (new Bind())->bind(RealBillingService::class, [$pointcut]);
$billing = (new Weaver($bind, $tmpDir))->newInstance(RealBillingService::class, [$arg1, $arg2]);
```

The matcher supports reading doctrine annotations or PHP8 attributes.

```php
public function matchesClass(\ReflectionClass $class, array $arguments) : bool
{
assert($class instanceof \Ray\Aop\ReflectionClass);
$classAnnotation = $class->getAnnotation(Foo::class); // @Foo or #[Foo]
// ...
}

public function matchesMethod(\ReflectionMethod $method, array $arguments) : bool
{
assert($method instanceof \Ray\Aop\ReflectionMethod);
$methodAnnotation = $method->getAnnotation(Bar::class);
}
```

## Performance boost

Cached `Weaver` object can save the compiling, binding, annotation reading costs.
Expand Down Expand Up @@ -245,16 +262,20 @@ The recommended way to install Ray.Aop is through [Composer](https://github.com/
$ composer require ray/aop ^2.0
```

## Testing Ray.Aop
## Performance

Here's how to install Ray.Aop from source and run the unit tests and demos.
Compilation of the AOP class allows Ray.Aop to run faster. Annotations are only loaded at first compile time, so they do not affect runtime performance. During the development phase and even at first runtime, PHP files are cached using the file timestamps, so normally you do not need to worry about the cost of annotation generation, but by setting up an annotation reader in the application bootstrap, first-time compile time performance. This setting is especially useful for large applications.

```bash
git clone https://github.com/ray-di/Ray.Aop.git
cd Ray.Aop
composer install
composer test
php demo/run.php
### APCu

```php
SevericeLocator::setReader(new PsrCachedReader(new Reader(), $apcuCache));
```

### PHP8 attributes only (recommended)

```php
SevericeLocator::setReader(new AttributeReader);`
```

## Integrated DI framework
Expand Down
4 changes: 3 additions & 1 deletion annotation_loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

use Doctrine\Common\Annotations\AnnotationRegistry;

AnnotationRegistry::registerLoader('class_exists');
if (method_exists(AnnotationRegistry::class, 'registerLoader')) {
AnnotationRegistry::registerLoader('class_exists');
}
26 changes: 11 additions & 15 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"require": {
"php": "^7.2 || ^8.0",
"doctrine/annotations": "^1.12",
"doctrine/annotations": "^1.12 || ^2.0",
"koriym/attributes": "^1.0.3",
"nikic/php-parser": "^4.16"
},
Expand Down Expand Up @@ -42,26 +42,22 @@
"ray/di": "A dependency injection framework"
},
"scripts" :{
"bin": "echo 'bin not installed'",
"post-install-cmd": ["@composer bin all install --ansi"],
"post-update-cmd": ["@composer bin all update --ansi"],
"test": ["./vendor/bin/phpunit"],
"test": ["phpunit"],
"tests": ["@cs", "@test", "@sa"],
"coverage": ["php -dzend_extension=xdebug.so -dxdebug.mode=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"],
"pcov": ["php -dextension=pcov.so -d pcov.enabled=1 ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage --coverage-clover=coverage.xml"],
"cs": ["phpcs --standard=./phpcs.xml src tests"],
"cs-fix": ["./vendor/bin/phpcbf src tests"],
"clean": ["./vendor/bin/phpstan clear-result-cache", "./vendor/bin/psalm --clear-cache", "rm -rf tests/tmp/*.php"],
"sa": ["./vendor/bin/psalm --show-info=true", "./vendor/bin/phpstan analyse -c phpstan.neon"],
"metrics": ["./vendor/bin/phpmetrics --report-html=build/metrics --exclude=Exception src"],
"phpmd": ["./vendor/bin/phpmd src text ./phpmd.xml"],
"coverage": ["php -dzend_extension=xdebug.so -dxdebug.mode=coverage phpunit --coverage-text --coverage-html=build/coverage"],
"pcov": ["php -dextension=pcov.so -d pcov.enabled=1 phpunit --coverage-text --coverage-html=build/coverage --coverage-clover=coverage.xml"],
"cs": ["phpcs --standard=./phpcs.xml sl-src src tests"],
"cs-fix": ["phpcbf sl-src src tests"],
"clean": ["phpstan clear-result-cache", "psalm --clear-cache", "rm -rf tests/tmp/*.php"],
"sa": ["psalm --monochrome --show-info=true", "phpstan --memory-limit=-1 analyse -c phpstan.neon"],
"metrics": ["phpmetrics --report-html=build/metrics --exclude=Exception src"],
"phpmd": ["phpmd src text ./phpmd.xml"],
"build": ["@cs", "@sa", "@pcov", "@metrics"]
},
"extra": {
"bamarni-bin": {
"forward-command": true,
"bin-links": true
}
},
"minimum-stability": "alpha"
}
}
1 change: 1 addition & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ com
<!-- /Base -->
<!-- Option -->
<exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed"/>
<exclude name="SlevomatCodingStandard.Functions.RequireTrailingCommaInCall.MissingTrailingComma"/>
<!-- /Option -->
<!-- Exclude Fake files form Doctrine CS -->
</rule>
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
parameters:
level: max
paths:
- sl-src
- src
- tests
excludePaths:
Expand Down
4 changes: 3 additions & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config https://psalm.dev/schema/config"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="sl-src" />
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
Expand Down
101 changes: 101 additions & 0 deletions sl-src/Cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace Ray\ServiceLocator;

use Ray\ServiceLocator\Exception\DirectoryNotWritableException;

use function file_exists;
use function file_put_contents;
use function hash;
use function is_writable;
use function mkdir;
use function pathinfo;
use function rename;
use function serialize;
use function substr;
use function tempnam;
use function unlink;
use function unserialize;

use const DIRECTORY_SEPARATOR;
use const PATHINFO_DIRNAME;

/**
* Minimal cache
*/
final class Cache
{
/** @var string */
private $tmpDir;

public function __construct(string $tmpDir)
{
$this->tmpDir = $tmpDir;
}

/**
* @psalm-param callable():array<object> $callback
*
* @return array<object>
*
* @psalm-suppress MixedInferredReturnType
*/
public function get(string $key, callable $callback): array
{
$filename = $this->getFilename($key);
if (! file_exists($filename)) {
$value = $callback();
$this->writeFile($this->getFilename($key), serialize($value));

return $value;
}

/** @psalm-suppress MixedAssignment, MixedArgument, MixedReturnStatement */
return unserialize(require $filename); // @phpstan-ignore-line
}

private function getFilename(string $id): string
{
$hash = hash('crc32', $id);

$dir = $this->tmpDir
. DIRECTORY_SEPARATOR
. substr($hash, 0, 2);
if (! is_writable($dir)) {
mkdir($dir, 0777, true);
}

return $dir
. DIRECTORY_SEPARATOR
. $hash
. '.php';
}

private function writeFile(string $filename, string $value): void
{
$filepath = pathinfo($filename, PATHINFO_DIRNAME);
if (! is_writable($filepath)) {
// @codeCoverageIgnoreStart
throw new DirectoryNotWritableException($filepath);
// @codeCoverageIgnoreEnd
}

$tmpFile = (string) tempnam($filepath, 'swap');
$valueWithSingileQuote = "'{$value}'";

$content = '<?php return ' . $valueWithSingileQuote . ';';
if (file_put_contents($tmpFile, $content) !== false) {
if (@rename($tmpFile, $filename)) {
return;
}

// @codeCoverageIgnoreStart
@unlink($tmpFile);
}

throw new DirectoryNotWritableException($filepath);
// @codeCoverageIgnoreEnd
}
}
Loading

0 comments on commit 0961e4a

Please sign in to comment.