composer require okapi/aop
AOP : Aspektorientierte Programmierung - Ein Programmierparadigma, das darauf abzielt, die Modularität zu erhöhen, indem es die Trennung von Übergreifungsbedenken ermöglicht.
Aspekt : Eine Klasse, die die Logik implementiert, die Sie auf Ihre Zielklassen anwenden möchten. Aspekte müssen mit dem Attribut #[Aspect]
kommentiert werden.
Ratschläge : Die Logik, die Sie für Ihre Zielkurse anwenden möchten. Beratungsmethoden müssen mit den Attributen #[Before]
, #[Around]
oder #[After]
kommentiert werden.
Jungteile : Ein Punkt in der Ausführung Ihrer Zielklassen, an dem Sie Ihren Rat anwenden können. Join -Punkte werden durch die Attribute #[Before]
, #[Around]
oder #[After]
definiert.
Pointcut : Eine Reihe von Join -Punkten, an denen Sie Ihren Rat anwenden können. Pointcuts werden durch das Attribut #[Pointcut]
definiert.
Weben : Der Prozess der Anwendung Ihrer Ratschläge auf Ihre Zielkurse.
Implizite Aspekte : Die Aspekte werden ohne Änderung der Zielklassen angewendet. Der Aspekt selbst gibt die Klassen oder Methoden an, auf die er angewendet wird.
Explizite Aspekte auf Klassenebene : Die Aspekte werden angewendet, indem die Zielklassen geändert werden, typischerweise durch Hinzufügen des Aspekts als Attribut zur Zielklasse.
Explizite Aspekte auf Methodenebene : Die Aspekte werden angewendet, indem die Zielklassen geändert werden, typischerweise durch Hinzufügen des Aspekts als Attribut zur Zielmethode.
<?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 ];
Das Hinzufügen des benutzerdefinierten Aspekts zum Kernel ist für explizite Aspekte auf Klassenebene nicht erforderlich, da sie zur Laufzeit automatisch registriert sind.
// 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 ();
Das Hinzufügen des benutzerdefinierten Aspekts zum Kernel ist für explizite Aspekte auf Methodenebene nicht erforderlich, da sie zur Laufzeit automatisch registriert sind.
// 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 ];
Beratungstypen: "vor", "um" und "Nachher"
Abfangen "private" und "geschützte" Methoden (werden Fehler in IDEs zeigen)
Greifen Sie auf "private" und "geschützte" Eigenschaften und Methoden des Subjekts (zeigen Fehler in IDEs).
Abfangen "endgültige" Methoden und Klassen
Verwenden Sie Transformatoren aus dem Paket "Okapi/Code-Transformator" in Ihrem Kernel, um den Quellcode einer geladenen PHP-Klasse zu ändern und zu transformieren (siehe "Okapi/Code-Transformator" -Paket für weitere Informationen)
Dieses Paket erweitert das Paket "Okapi/Code-Transformator" mit Abhängigkeitsinjektion und AOP-Funktionen
Die AopKernel
registriert mehrere Dienste
Der TransformerManager
-Service speichert die Liste der Aspekte und deren Konfiguration
Der CacheStateManager
-Service verwaltet den Cache -Status
Der StreamFilter
-Service registriert einen PHP -Stream -Filter, mit dem der Quellcode geändert werden kann, bevor er von PHP geladen wird
Der AutoloadInterceptor
-Service überlädt den Komponisten -Autoloader, der das Laden von Klassen abwickelt
Der AutoloadInterceptor
-Service fängt das Laden einer Klasse ab
Der AspectMatcher
entspricht den Namens- und Methodennamen mit der Liste der Aspekte und ihrer Konfiguration
Wenn die Klasse- und Methodennamen mit einem Aspekt übereinstimmen, fragen Sie den Cache -Status, um festzustellen, ob der Quellcode bereits zwischenstrahlt ist
Überprüfen Sie, ob der Cache gültig ist:
Wenn der Cache gültig ist, laden Sie die Proxied -Klasse aus dem Cache
Wenn nicht, geben Sie einen Stream -Filterpfad in den AutoloadInterceptor
-Dienst zurück
Der StreamFilter
verändert den Quellcode, indem die Aspekte angewendet werden
composer run-script test
auscomposer run-script test-coverage
aus <?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" ;
}
}
Geben Sie ein, wenn dieses Projekt Ihnen geholfen hat!
Copyright © 2023 Valentin Wotschel.
Dieses Projekt ist MIT lizenziert.