Experience how Around plugins wrap and control Magento methods
$subject
$proceed()
return $result
aroundSave()
public function aroundSave($subject, $proceed) { // Pre-execution logic $result = $proceed(); // Wrapped! // Post-execution logic }
How to Use Around Plugin Magento 2 in E-Stores?
Answer these questions to find the perfect plugin type for your needs
Modify input parameters before execution
Complete control over method execution
Transform return values after execution
Follow these 9 steps to create a working Around plugin in Magento 2
Visualize how Around plugins affect your Magento 2 call stack and execution time
ProductController->saveAction()
Product->save()
ResourceModel->save()
ProductController->saveAction()
Interceptor->save()
Plugin->aroundSave()
$proceed() -> Product->save()
ResourceModel->save()
Interactive checklist to help you avoid common pitfalls and find better alternatives
If you only need to modify input values, an around plugin adds unnecessary complexity
public function aroundSave($subject, $proceed, $data) { $data['modified'] = true; return $proceed($data); }
public function beforeSave($subject, $data) { $data['modified'] = true; return [$data]; }
Why: Before plugins are simpler, faster, and easier to debug. They're specifically designed for input modification.
Around plugins significantly increase call stack depth and execution time
Use Magento events for decoupled logic
Implement via service layer instead
Incorrect $proceed() usage can break core functionality silently
Better approach: Start with before/after plugins. Only use around when you absolutely need conditional execution control.
Multiple around plugins on the same method create complex chains
Solution: Use explicit sortOrder values and document plugin interactions. Consider using preferences or service layer extensions instead.
Around plugins make code flow harder to understand for new developers
Always try Before/After plugins first - they solve 90% of use cases
Use Around plugins only for conditional execution control
Document thoroughly and test all execution paths
Explore real Around plugin implementations with interactive annotations
namespace Vendor\Module\Plugin; class ProductAttributesUpdater { public function __construct( \Psr\Log\LoggerInterface $logger ) { $this->logger = $logger; } public function aroundSave( \Magento\Catalog\Model\Product $subject, callable $proceed ) { // Pre-execution: Log before save $this->logger->info('Before save: ' . $subject->getName()); // Call original method $result = $proceed(); // Post-execution: Log after save $this->logger->info('After save: ' . $subject->getId()); // Always return the result return $result; } }
public function aroundSave( \Magento\Catalog\Model\Product $subject, callable $proceed ) { // Complex validation logic if (!$this->validateProduct($subject)) { $this->logger->warning('Invalid product data'); return false; // Skip save } // Modify data before save $originalPrice = $subject->getPrice(); if ($this->shouldApplyDiscount($subject)) { $subject->setPrice($originalPrice * 0.9); } // Conditional execution try { $result = $proceed(); } catch (\Exception $e) { $this->logger->error($e->getMessage()); $subject->setPrice($originalPrice); // Rollback throw $e; } // Post-save actions if ($result && $this->isNewProduct) { $this->notificationService->sendNewProductAlert($subject); $this->cacheManager->clean(['catalog_product']); } return $result; }
/** * Real-world example: Facebook Product Integration */ class FacebookProductIntegration { private $facebookApi; private $configHelper; private $logger; public function aroundSave( \Magento\Catalog\Model\Product $subject, callable $proceed ) { // Check if Facebook integration is enabled if (!$this->configHelper->isEnabled()) { return $proceed(); } // Capture original data for comparison $originalData = $subject->getOrigData(); $isNew = !$subject->getId(); // Validate Facebook requirements if (!$this->validateFacebookData($subject)) { $this->logger->warning( 'Product missing Facebook requirements', ['sku' => $subject->getSku()] ); } // Execute original save $result = $proceed(); // Sync to Facebook if save was successful if ($result) { try { if ($isNew) { $this->facebookApi->createProduct($subject); } else { $this->facebookApi->updateProduct($subject, $originalData); } } catch (\Exception $e) { // Don't break Magento save if FB fails $this->logger->error($e->getMessage()); } } return $result; } }
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!-- Plugin Configuration --> <type name="Magento\Catalog\Model\Product"> <plugin name="vendor_facebook_product_integration" type="Vendor\Facebook\Plugin\FacebookProductIntegration" sortOrder="100" disabled="false"/> </type> <!-- Plugin Dependencies --> <type name="Vendor\Facebook\Plugin\FacebookProductIntegration"> <arguments> <argument name="facebookApi" xsi:type="object"> Vendor\Facebook\Api\Client </argument> <argument name="configHelper" xsi:type="object"> Vendor\Facebook\Helper\Config </argument> </arguments> </type> </config>
class FacebookProductIntegrationTest extends \PHPUnit\Framework\TestCase { public function testAroundSaveWhenDisabled() { // Setup mocks $configHelper = $this->createMock(Config::class); $configHelper->method('isEnabled')->willReturn(false); $plugin = new FacebookProductIntegration($configHelper); // Test that proceed is called directly $proceed = function() { return true; }; $result = $plugin->aroundSave($product, $proceed); $this->assertTrue($result); // Verify Facebook API was not called } public function testAroundSaveWithFacebookError() { // Test that Magento save succeeds even if FB fails $facebookApi = $this->createMock(Client::class); $facebookApi->method('createProduct') ->willThrowException(new \Exception('FB Error')); // Assert save still returns true $this->assertTrue($result); } }
Interactive comparison of Around, Before, and After plugins in Magento 2
Criteria | Around Plugin | Before Plugin | After Plugin |
---|---|---|---|
Execution Control
Ability to control method execution
|
Full Control
Can skip or modify execution
|
No Control
Method always executes
|
No Control
Method always executes
|
Performance Impact
Effect on execution speed
|
+2 call stack, ~60% slower
|
Minimal overhead
|
Minimal overhead
|
Implementation Complexity
Difficulty to implement correctly
|
Complex
Requires proceed() management
|
Simple
Straightforward implementation
|
Simple
Straightforward implementation
|
Best Use Cases
When to use each type
|
|
|
|
Debug Difficulty
How hard to debug issues
|
Hard
Complex stack traces
|
Easy
Clear execution path
|
Easy
Clear execution path
|
Need to modify inputs? Use Before Plugin
Need to modify outputs? Use After Plugin
Need conditional execution or both? Use Around Plugin
Track your Around plugin implementation with this interactive checklist
Always call $proceed() exactly once per execution path
Never use Around plugins in performance-critical loops
Lower sortOrder values execute first in the plugin chain