-
Notifications
You must be signed in to change notification settings - Fork 58
Add plugin architecture doc #3623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Dinamiko
merged 4 commits into
develop
from
PCP-5171-documentation-task-plugin-architecture
Sep 1, 2025
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,307 @@ | ||
| # Plugin Architecture Documentation | ||
|
|
||
| This document provides a comprehensive overview of the WooCommerce PayPal Payments plugin architecture, explaining its modular design and how the various components work together. | ||
|
|
||
| ## Overview | ||
|
|
||
| The WooCommerce PayPal Payments plugin is built using a modular architecture powered by the [Syde Modularity](https://github.com/inpsyde/modularity) framework. This design provides: | ||
|
|
||
| - **Modular Structure**: Each feature is contained within its own module with clear boundaries | ||
| - **Dependency Injection**: PSR-11 container for service management and dependency resolution | ||
| - **Feature Flags**: Dynamic module loading based on environment variables and filters | ||
| - **Extensibility**: Well-defined extension points for customization and enhancement | ||
| - **Maintainability**: Clear separation of concerns and consistent patterns | ||
|
|
||
| ## Core Components | ||
|
|
||
| ### Main Plugin File | ||
|
|
||
| The plugin initialization begins in `woocommerce-paypal-payments.php`, which: | ||
| - Loads the Composer autoloader if needed (e.g. may be already loaded in some tests) | ||
| - Contains plugin metadata and constants definitions | ||
| - It starts the bootstrap process, in `plugins_loaded` hook. | ||
|
|
||
| ### Bootstrap System | ||
|
|
||
| The bootstrap process is handled by `bootstrap.php`, which: | ||
|
|
||
| ```php | ||
| return function ( | ||
| string $root_dir, | ||
| array $additional_containers = array(), | ||
| array $additional_modules = array() | ||
| ): ContainerInterface { | ||
| // Load modules from modules.php | ||
| $modules = ( require "$root_dir/modules.php" )( $root_dir ); | ||
|
|
||
| // Apply filters for customization | ||
| $modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules ); | ||
|
|
||
| // Initialize plugin with Syde Modularity | ||
| $properties = PluginProperties::new( "$root_dir/woocommerce-paypal-payments.php" ); | ||
| $bootstrap = Package::new( $properties ); | ||
|
|
||
| foreach ( $modules as $module ) { | ||
| $bootstrap->addModule( $module ); | ||
| } | ||
|
|
||
| $bootstrap->boot(); | ||
| return $bootstrap->container(); | ||
| }; | ||
| ``` | ||
|
|
||
| ### PPCP Container | ||
|
|
||
| The global `PPCP` class (`src/PPCP.php`) provides access to the dependency injection container: | ||
|
|
||
| ```php | ||
| class PPCP { | ||
| private static $container = null; | ||
|
|
||
| public static function container(): ContainerInterface { | ||
| if ( ! self::$container ) { | ||
| throw new LogicException( 'No PPCP container, probably called too early when the plugin is not initialized yet.' ); | ||
| } | ||
| return self::$container; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| This allows third-party access services easily, such as in `api/order-functions.php`. | ||
|
|
||
| ## Module System | ||
|
|
||
| ### Module Definition | ||
|
|
||
| Modules are defined in `modules.php` with both core and conditional modules: | ||
|
|
||
| ```php | ||
| $modules = array( | ||
| new PluginModule(), | ||
| ( require "$modules_dir/woocommerce-logging/module.php" )(), | ||
| ( require "$modules_dir/ppcp-admin-notices/module.php" )(), | ||
| ( require "$modules_dir/ppcp-api-client/module.php" )(), | ||
| // ... more core modules | ||
| ); | ||
| ``` | ||
|
|
||
| ### Feature-Flag Controlled Modules | ||
|
|
||
| Conditional modules are loaded based on environment variables and filters (`modules.php`): | ||
|
|
||
| ```php | ||
| if ( apply_filters( | ||
| 'woocommerce.feature-flags.woocommerce_paypal_payments.applepay_enabled', | ||
| getenv( 'PCP_APPLEPAY_ENABLED' ) !== '0' | ||
| ) ) { | ||
| $modules[] = ( require "$modules_dir/ppcp-applepay/module.php" )(); | ||
| } | ||
| ``` | ||
|
|
||
| This pattern allows for: | ||
| - **Environment-based control**: Use `PCP_*_ENABLED` environment variables | ||
| - **Runtime filtering**: Apply WordPress filters to override defaults | ||
| - **Graceful degradation**: Missing features don't break core functionality | ||
|
|
||
| ### Module Structure | ||
|
|
||
| Each module follows a consistent directory structure: | ||
|
|
||
| ``` | ||
| modules/ppcp-example/ | ||
| ├── module.php # Module factory function | ||
| ├── composer.json # PHP dependencies | ||
| ├── package.json # JavaScript dependencies | ||
| ├── webpack.config.js # Asset building configuration | ||
| ├── services.php # Service definitions | ||
| ├── extensions.php # Service extensions/modifications | ||
| ├── src/ # PHP source code | ||
| │ └── ExampleModule.php | ||
| ├── resources/ # Source assets | ||
| │ ├── js/ | ||
| │ └── css/ | ||
| └── assets/ # Built assets | ||
| ├── js/ | ||
| └── css/ | ||
| ``` | ||
|
|
||
| ### Module Interface Implementation | ||
|
|
||
| Most modules implement the Syde Modularity interfaces. For example in `modules/ppcp-api-client/src/ApiModule.php`: | ||
|
|
||
| ```php | ||
| class ApiModule implements ServiceModule, FactoryModule, ExtendingModule, ExecutableModule { | ||
| use ModuleClassNameIdTrait; | ||
|
|
||
| public function services(): array { | ||
| return require __DIR__ . '/../services.php'; | ||
| } | ||
|
|
||
| public function factories(): array { | ||
| return require __DIR__ . '/../factories.php'; | ||
| } | ||
|
|
||
| public function extensions(): array { | ||
| return require __DIR__ . '/../extensions.php'; | ||
| } | ||
|
|
||
| public function run( ContainerInterface $c ): bool { | ||
| // Module initialization logic | ||
| return true; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Key Modules | ||
|
|
||
| ### Core Infrastructure Modules | ||
|
|
||
| - **PluginModule** (`src/PluginModule.php`): Root module providing core services | ||
| - **woocommerce-logging**: Logging infrastructure integration | ||
| - **ppcp-api-client**: PayPal API integration, entities, and authentication | ||
| - **ppcp-session**: Session management for payment flows | ||
| - **ppcp-webhooks**: PayPal webhook handling | ||
|
|
||
| ### Payment & Checkout Modules | ||
|
|
||
| - **ppcp-button**: PayPal Smart Payment Buttons and Advanced Credit and Debit Cards functionality | ||
| - **ppcp-blocks**: WooCommerce Blocks integration | ||
| - **ppcp-wc-gateway**: WooCommerce gateway integration | ||
| - **ppcp-axo**: PayPal Fastlane (Accelerated Checkout) implementation | ||
|
|
||
| ### Feature Modules | ||
|
|
||
| - **ppcp-settings**: New React-based admin settings interface | ||
| - **ppcp-vaulting**: Saved payment methods functionality | ||
| - **ppcp-onboarding**: Merchant onboarding flow | ||
|
|
||
| ### Alternative Payment Methods | ||
|
|
||
| - **ppcp-applepay/ppcp-googlepay**: Digital wallet integrations | ||
| - **ppcp-local-alternative-payment-methods**: Regional payment options | ||
|
|
||
| ## Dependency Injection & Services | ||
|
|
||
| ### Service Definition | ||
|
|
||
| Services are defined in each module's `services.php` file using factory functions: | ||
|
|
||
| ```php | ||
| return array( | ||
| 'example.service' => static function ( ContainerInterface $container ): ExampleService { | ||
| return new ExampleService( | ||
| $container->get( 'dependency.service' ) | ||
| ); | ||
| }, | ||
|
|
||
| 'example.config' => static function (): array { | ||
| return array( | ||
| 'setting' => 'value', | ||
| ); | ||
| }, | ||
| ); | ||
| ``` | ||
|
|
||
| ### Service Extensions | ||
|
|
||
| The `extensions.php` files allow modules to modify or extend existing services: | ||
|
|
||
| ```php | ||
| return array( | ||
| 'existing.service' => static function ( ContainerInterface $container, ExistingService $service ): ExistingService { | ||
| // Modify or wrap the existing service | ||
| return new EnhancedService( $service ); | ||
| }, | ||
| ); | ||
| ``` | ||
|
|
||
| ### Container Access Patterns | ||
|
|
||
| Services can be accessed in multiple ways: | ||
|
|
||
| ```php | ||
| // In our modules/services/extensions (also often passed to hook handlers via `use`) | ||
| $service = $container->get( 'service.id' ); | ||
|
|
||
| // In third-party plugins etc. (if not adding a custom module via the `woocommerce_paypal_payments_modules` filter) | ||
| $service = PPCP::container()->get( 'service.id' ); | ||
|
|
||
| // Check for service availability | ||
| if ( $container->has( 'optional.service' ) ) { | ||
| $service = $container->get( 'optional.service' ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Asset Management | ||
|
|
||
| ### Webpack Configuration | ||
|
|
||
| Each module with JavaScript assets includes a `webpack.config.js`: | ||
|
|
||
| ```javascript | ||
| const path = require('path'); | ||
| const defaultConfig = require('@wordpress/scripts/config/webpack.config'); | ||
|
|
||
| module.exports = { | ||
| ...defaultConfig, | ||
| entry: { | ||
| 'boot': path.resolve(process.cwd(), 'resources/js', 'boot.js'), | ||
| }, | ||
| output: { | ||
| path: path.resolve(process.cwd(), 'assets/js'), | ||
| filename: '[name].js', | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| ### Build Process | ||
|
|
||
| Assets are built using the shared configuration: | ||
|
|
||
| - **Individual builds**: `yarn run build:modules:ppcp-{module-name}` | ||
| - **Watch mode**: `yarn run watch:modules:ppcp-{module-name}` | ||
| - **All modules**: `yarn run build:modules` (parallel builds) | ||
|
|
||
| ### Asset Registration | ||
|
|
||
| Built assets are registered through module services and enqueued conditionally: | ||
|
|
||
| ```php | ||
| 'asset.example-script' => static function( ContainerInterface $container ): Asset { | ||
| return new Asset( | ||
| 'example-script', | ||
| plugin_dir_url( __DIR__ ) . 'assets/js/example.js', | ||
| array( 'wp-element' ), // dependencies | ||
| '1.0.0' | ||
| ); | ||
| }, | ||
| ``` | ||
|
|
||
| ## Extension Points | ||
|
|
||
| ### WordPress Hooks | ||
|
|
||
| The plugin provides numerous action and filter hooks: | ||
|
|
||
| ```php | ||
| // Allow modification of order request data | ||
| apply_filters( 'ppcp_create_order_request_body_data', $data ); | ||
|
|
||
| // PayPal order creation notification | ||
| do_action( 'woocommerce_paypal_payments_paypal_order_created', $order ); | ||
|
|
||
| // API cache clearing | ||
| do_action( 'woocommerce_paypal_payments_flush_api_cache' ); | ||
| ``` | ||
|
|
||
| ### Module Filters | ||
|
|
||
| Modules can be modified via filters: | ||
|
|
||
| ```php | ||
| // Add or remove modules | ||
| $modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules ); | ||
|
|
||
| // Feature flag overrides | ||
| apply_filters( 'woocommerce.feature-flags.woocommerce_paypal_payments.applepay_enabled', $default ); | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe should add that it starts the bootstrap process, in
plugins_loadedhook.