composer require okapi/aop
AOP : ориентированное на аспект программирование - парадигма программирования, которая направлена на увеличение модульности, позволяя разделить перекрестные проблемы.
Аспект : класс, который реализует логику, которую вы хотите применить к вашим целевым классам. Аспекты должны быть аннотированы атрибутом #[Aspect]
.
Совет : логика, которую вы хотите применить к своим целевым классам. Методы консультаций должны быть аннотированы с атрибутами #[Before]
, #[Around]
или #[After]
.
Присоединение : пункт выполнения ваших целевых классов, где вы можете применить свой совет. Точки соединения определяются #[Before]
, #[Around]
или #[After]
атрибутов.
PointCut : набор точек соединения, где вы можете применить свой совет. Pointcuts определяются атрибутом #[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)
Доступ к «частным» и «защищенным» свойствам и методам субъекта (покажут ошибки в IDE)
Перехватить «окончательные» методы и классы
Используйте трансформаторы из пакета "Okapi/Code-Transformer" в вашем ядре, чтобы изменить и преобразовать исходный код загруженного класса PHP (см. В пакете «Okapi/Code-Transformer» для получения дополнительной информации)
Этот пакет расширяет пакет "Okapi/Code-Transformer" с инъекцией зависимостей и функциями AOP
AopKernel
регистрирует несколько сервисов
Служба TransformerManager
хранит список аспектов и их конфигурации
Служба CacheStateManager
управляет состоянием кэша
Служба StreamFilter
регистрирует фильтр потока PHP, который позволяет изменять исходный код до его загрузки PHP
Служба AutoloadInterceptor
перегружает Composer AutoLoAloader, который обрабатывает загрузку классов
Сервис 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.