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 ];
ประเภทคำแนะนำ: "ก่อน", "รอบ" และ "หลังจาก"
สกัดกั้นวิธีการ "ส่วนตัว" และ "ป้องกัน" (จะแสดงข้อผิดพลาดใน IDEs)
การเข้าถึงคุณสมบัติและวิธีการป้องกัน "ส่วนตัว" และ "ป้องกัน" ของเรื่อง (จะแสดงข้อผิดพลาดใน IDEs)
วิธีการสกัดกั้น "ขั้นสุดท้าย" และชั้นเรียน
ใช้ Transformers จากแพ็คเกจ "OKAPI/CODE-TRANSFORMER" ในเคอร์เนลของคุณเพื่อแก้ไขและแปลงซอร์สโค้ดของคลาส PHP ที่โหลด (ดู "OKAPI/CODE-TRANSFORMER" สำหรับข้อมูลเพิ่มเติม)
แพ็คเกจนี้ขยายแพ็คเกจ "okapi/code-transformer" ด้วยการฉีดพึ่งพาและคุณสมบัติ AOP
AopKernel
ลงทะเบียนบริการหลายบริการ
บริการ TransformerManager
เก็บรายการด้านและการกำหนดค่าของพวกเขา
บริการ CacheStateManager
จัดการสถานะแคช
บริการ StreamFilter
ลงทะเบียนตัวกรองสตรีม PHP ซึ่งอนุญาตให้แก้ไขซอร์สโค้ดก่อนที่จะโหลดโดย PHP
บริการ AutoloadInterceptor
เกินพิกัด AutoLoader นักแต่งเพลงซึ่งจัดการการโหลดคลาส
บริการ 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" ;
}
}
ให้ถ้าโครงการนี้ช่วยคุณได้!
ลิขสิทธิ์© 2023 Valentin Wotschel
โครงการนี้ได้รับใบอนุญาต MIT