composer require okapi/aop
AOP :アスペクト指向プログラミング - クロスカットの懸念の分離を可能にすることでモジュール性を高めることを目的とするプログラミングパラダイム。
アスペクト:ターゲットクラスに適用したいロジックを実装するクラス。アスペクトは、 #[Aspect]
属性で注釈を付けなければなりません。
アドバイス:ターゲットクラスに適用したいロジック。アドバイス方法は#[Before]
、 #[Around]
、または#[After]
属性で注釈を付けなければなりません。
参加ポイント:アドバイスを適用できるターゲットクラスの実行のポイント。結合ポイントは、 #[Before]
、 #[Around]
、または#[After]
属性によって定義されます。
PointCut :アドバイスを適用できる一連の結合ポイント。ポイントカットは、 #[Pointcut]
属性によって定義されます。
織り:ターゲットクラスにアドバイスを適用するプロセス。
暗黙の側面:ターゲットクラスに変更することなく、側面が適用されます。アスペクト自体は、適用されるクラスまたはメソッドを指定します。
クラスレベルの明示的な側面:ターゲットクラスを変更することにより、ターゲットクラスを変更することにより、アスペクトは適用されます。
メソッドレベルの明示的側面:ターゲットクラスを変更することにより、ターゲットクラスを変更することにより、アスペクトが適用されます。
<?php
use Okapi Aop AopKernel ;
// Extend from the "AopKernel" class
class MyKernel extends AopKernel
{
// Define a list of aspects
protected array $ aspects = [
DiscountAspect ::class,
PaymentProcessorAspect ::class,
];
}
// Discount Aspect
<?php
use Okapi Aop Attributes Aspect ;
use Okapi Aop Attributes After ;
use Okapi Aop Invocation AfterMethodInvocation ;
// Aspects must be annotated with the "Aspect" attribute
#[ Aspect ]
class DiscountAspect
{
// Annotate the methods that you want to intercept with
// "Before", "Around" or "After" attributes
#[ After (
// Use named arguments
// You can also use Wildcards (see Okapi/Wildcards package)
class: Product ::class . ' | ' . Order ::class,
method: ' get(Price|Total) ' ,
// When using wildcards you can also use some of these options:
onlyPublicMethods: false , // Intercepts only public methods and ignores protected and private methods (default: false)
interceptTraitMethods: true , // Also intercepts methods from traits (default: true)
)]
public function applyDiscount ( AfterMethodInvocation $ invocation ): void
{
// Get the subject of the invocation
// The subject is the object class that contains the method
// that is being intercepted
$ subject = $ invocation -> getSubject ();
$ productDiscount = 0.1 ;
$ orderDiscount = 0.2 ;
if ( $ subject instanceof Product ) {
// Get the result of the original method
$ oldPrice = $ invocation -> proceed ();
$ newPrice = $ oldPrice - ( $ oldPrice * $ productDiscount );
// Set the new result
$ invocation -> setResult ( $ newPrice );
}
if ( $ subject instanceof Order ) {
$ oldTotal = $ invocation -> proceed ();
$ newTotal = $ oldTotal - ( $ oldTotal * $ orderDiscount );
$ invocation -> setResult ( $ newTotal );
}
}
}
// PaymentProcessor Aspect
<?php
use InvalidArgumentException ;
use Okapi Aop Attributes After ;
use Okapi Aop Attributes Around ;
use Okapi Aop Attributes Aspect ;
use Okapi Aop Attributes Before ;
use Okapi Aop Invocation AroundMethodInvocation ;
use Okapi Aop Invocation AfterMethodInvocation ;
use Okapi Aop Invocation BeforeMethodInvocation ;
#[ Aspect ]
class PaymentProcessorAspect
{
#[ Before (
class: PaymentProcessor ::class,
method: ' processPayment ' ,
)]
public function checkPaymentAmount ( BeforeMethodInvocation $ invocation ): void
{
$ payment = $ invocation -> getArgument ( ' amount ' );
if ( $ payment < 0 ) {
throw new InvalidArgumentException ( ' Invalid payment amount ' );
}
}
#[ Around (
class: PaymentProcessor ::class,
method: ' processPayment ' ,
)]
public function logPayment ( AroundMethodInvocation $ invocation ): void
{
$ startTime = microtime ( true );
// Proceed with the original method
$ invocation -> proceed ();
$ endTime = microtime ( true );
$ elapsedTime = $ endTime - $ startTime ;
$ amount = $ invocation -> getArgument ( ' amount ' );
$ logMessage = sprintf (
' Payment processed for amount $%.2f in %.2f seconds ' ,
$ amount ,
$ elapsedTime ,
);
// Singleton instance of a logger
$ logger = Logger :: getInstance ();
$ logger -> log ( $ logMessage );
}
#[ After (
class: PaymentProcessor ::class,
method: ' processPayment ' ,
)]
public function sendEmailNotification ( AfterMethodInvocation $ invocation ): void
{
// Proceed with the original method
$ result = $ invocation -> proceed ();
$ amount = $ invocation -> getArgument ( ' amount ' );
$ message = sprintf (
' Payment processed for amount $%.2f ' ,
$ amount ,
);
if ( $ result === true ) {
$ message .= ' - Payment successful ' ;
} else {
$ message .= ' - Payment failed ' ;
}
// Singleton instance of an email queue
$ mailQueue = MailQueue :: getInstance ();
$ mailQueue -> addMail ( $ message );
}
}
// Product
<?php
class Product
{
private float $ price ;
public function getPrice (): float
{
return $ this -> price ;
}
}
// Order
<?php
class Order
{
private float $ total = 500.00 ;
public function getTotal (): float
{
return $ this -> total ;
}
}
// PaymentProcessor
<?php
class PaymentProcessor
{
public function processPayment ( float $ amount ): bool
{
// Process payment
return true ;
}
}
// Initialize the kernel early in the application lifecycle
// Preferably after the autoloader is registered
<?php
use MyKernel ;
require_once __DIR__ . ' /vendor/autoload.php ' ;
// Initialize the AOP Kernel
$ kernel = MyKernel :: init ();
<?php
// Just use your classes as usual
$ product = new Product ();
// Before AOP: 100.00
// After AOP: 90.00
$ productPrice = $ product -> getPrice ();
$ order = new Order ();
// Before AOP: 500.00
// After AOP: 400.00
$ orderTotal = $ order -> getTotal ();
$ paymentProcessor = new PaymentProcessor ();
// Invalid payment amount
$ amount = - 50.00 ;
// Before AOP: true
// After AOP: InvalidArgumentException
$ paymentProcessor -> processPayment ( $ amount );
// Valid payment amount
$ amount = 100.00 ;
// Value: true
$ paymentProcessor -> processPayment ( $ amount );
$ logger = Logger :: getInstance ();
$ logs = $ logger -> getLogs ();
// Value: Payment processed for amount $100.00 in 0.00 seconds
$ firstLog = $ logs [ 0 ];
$ mailQueue = MailQueue :: getInstance ();
$ mails = $ mailQueue -> getMails ();
// Value: Payment processed for amount $100.00 - Payment successful
$ firstMail = $ mails [ 0 ];
クラスレベルの明示的なアスペクトには、実行時に自動的に登録されるため、カーネルにカスタムアスペクトを追加する必要はありません。
// Logging Aspect
<?php
use Attribute ;
use Okapi Aop Attributes Aspect ;
use Okapi Aop Attributes Before ;
use Okapi Aop Invocation BeforeMethodInvocation ;
// Class-Level Explicit Aspects must be annotated with the "Aspect" attribute
// and the "Attribute" attribute
#[ Attribute ]
#[ Aspect ]
class LoggingAspect
{
// The "class" argument is not required
// The "method" argument is optional
// Without the argument, the aspect will be applied to all methods
// With the argument, the aspect will be applied to the specified method
#[ Before ]
public function logAllMethods ( BeforeMethodInvocation $ invocation ): void
{
$ methodName = $ invocation -> getMethodName ();
$ logMessage = sprintf (
" Method '%s' executed. " ,
$ methodName ,
);
$ logger = Logger :: getInstance ();
$ logger -> log ( $ logMessage );
}
#[ Before (
method: ' updateInventory ' ,
)]
public function logUpdateInventory ( BeforeMethodInvocation $ invocation ): void
{
$ methodName = $ invocation -> getMethodName ();
$ logMessage = sprintf (
" Method '%s' executed. " ,
$ methodName ,
);
$ logger = Logger :: getInstance ();
$ logger -> log ( $ logMessage );
}
}
// Inventory Tracker
<?php
// Custom Class-Level Explicit Aspect added to the class
#[ LoggingAspect ]
class InventoryTracker
{
private array $ inventory = [];
public function updateInventory ( int $ productId , int $ quantity ): void
{
$ this -> inventory [ $ productId ] = $ quantity ;
}
public function checkInventory ( int $ productId ): int
{
return $ this -> inventory [ $ productId ] ?? 0 ;
}
}
// Initialize the kernel early in the application lifecycle
// Preferably after the autoloader is registered
// The kernel must still be initialized, even if it has no Aspects
<?php
use MyKernel ;
require_once __DIR__ . ' /vendor/autoload.php ' ;
// Initialize the AOP Kernel
$ kernel = MyKernel :: init ();
<?php
// Just use your classes as usual
$ inventoryTracker = new InventoryTracker ();
$ inventoryTracker -> updateInventory ( 1 , 100 );
$ inventoryTracker -> updateInventory ( 2 , 200 );
$ countProduct1 = $ inventoryTracker -> checkInventory ( 1 );
$ countProduct2 = $ inventoryTracker -> checkInventory ( 2 );
$ logger = Logger :: getInstance ();
// Value:
// Method 'updateInventory' executed. (4 times)
// Method 'checkInventory' executed. (2 times)
$ logs = $ logger -> getLogs ();
カスタムアスペクトをカーネルに追加することは、実行時に自動的に登録されるため、メソッドレベルの明示的な側面には必要ありません。
// Performance Aspect
<?php
use Attribute ;
use Okapi Aop Attributes Around ;
use Okapi Aop Invocation AroundMethodInvocation ;
use Okapi Aop Attributes Aspect ;
// Method-Level Explicit Aspects must be annotated with the "Aspect" attribute
// and the "Attribute" attribute
#[ Attribute ]
#[ Aspect ]
class PerformanceAspect
{
// The "class" argument is not required
// The "method" argument is optional
// Without the argument, the aspect will be applied to all methods
// With the argument, the aspect will be applied to the specified method
#[ Around ]
public function measure ( AroundMethodInvocation $ invocation ): void
{
$ start = microtime ( true );
$ invocation -> proceed ();
$ end = microtime ( true );
$ executionTime = $ end - $ start ;
$ class = $ invocation -> getClassName ();
$ method = $ invocation -> getMethodName ();
$ logMessage = sprintf (
" Method %s::%s executed in %.2f seconds. " ,
$ class ,
$ method ,
$ executionTime ,
);
$ logger = Logger :: getInstance ();
$ logger -> log ( $ logMessage );
}
}
// Customer Service
<?php
class CustomerService
{
#[ PerformanceAspect ]
public function createCustomer (): void
{
// Logic to create a customer
}
}
// Initialize the kernel early in the application lifecycle
// Preferably after the autoloader is registered
// The kernel must still be initialized, even if it has no Aspects
<?php
use MyKernel ;
require_once __DIR__ . ' /vendor/autoload.php ' ;
// Initialize the AOP Kernel
$ kernel = MyKernel :: init ();
<?php
// Just use your classes as usual
$ customerService = new CustomerService ();
$ customerService -> createCustomer ();
$ logger = Logger :: getInstance ();
$ logs = $ logger -> getLogs ();
// Value: Method CustomerService::createCustomer executed in 0.01 seconds.
$ firstLog = $ logs [ 0 ];
アドバイスタイプ: 「前」、「周り」、「後」
「プライベート」および「保護された」メソッドを傍受する(IDEのエラーが表示される)
主題の「プライベート」および「保護された」プロパティと方法にアクセスします(IDESのエラーが表示されます)
「最終的な」メソッドとクラスを傍受します
カーネル内の「Okapi/Code-Transformer」パッケージからの変圧器を使用して、ロードされたPHPクラスのソースコードを変更および変換します(詳細については、「Okapi/Code-Transformer」パッケージを参照)
このパッケージは、依存関係の注入とAOP機能を備えた「Okapi/Code-Transformer」パッケージを拡張します
AopKernel
複数のサービスを登録します
TransformerManager
サービスは、アスペクトとその構成のリストを保存します
CacheStateManager
サービスは、キャッシュ状態を管理します
StreamFilter
サービスは、PHPによってロードされる前にソースコードを変更できるPHPストリームフィルターを登録します
AutoloadInterceptor
サービスは、クラスの負荷を処理する作曲家オートローダーに過負荷になります
AutoloadInterceptor
サービスは、クラスの負荷を傍受します
AspectMatcher
、クラスとメソッドの名前をアスペクトのリストとその構成と一致させます
クラスとメソッドの名前がアスペクトと一致する場合、キャッシュ状態を照会して、ソースコードがすでにキャッシュされているかどうかを確認します
キャッシュが有効かどうかを確認します。
キャッシュが有効な場合は、プロキシクラスをキャッシュからロードします
そうでない場合は、 AutoloadInterceptor
サービスにストリームフィルターパスを返します
StreamFilter
アスペクトを適用してソースコードを変更します
composer run-script test
実行しますcomposer run-script test-coverage
実行します <?php
use Okapi Aop Attributes After ;
use Okapi Aop Attributes Aspect ;
use Okapi Aop Invocation AfterMethodInvocation ;
#[ Aspect ]
class EverythingAspect
{
#[ After (
class: ' * ' ,
method: ' * ' ,
)]
public function everything ( AfterMethodInvocation $ invocation ): void
{
echo $ invocation -> getClassName () . "n" ;
echo $ invocation -> getMethodName () . "n" ;
}
}
このプロジェクトがあなたを助けてくれたなら!
Copyright©2023 Valentin Wotschel。
このプロジェクトはMITライセンスです。