8.8 KiB
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 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:40-45
, which:
- Loads the Composer autoloader
- Checks for class existence to prevent conflicts
- Contains plugin metadata and constants definitions
Bootstrap System
The bootstrap process is handled by bootstrap.php
, which:
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 Inpsyde 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:18
) provides access to the dependency injection container:
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 modules to access services via PPCP::container()->get('service.id')
after initialization.
Module System
Module Definition
Modules are defined in modules.php:17-37
with both core and conditional modules:
$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:40-95
):
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 Inpsyde Modularity interfaces (modules/ppcp-api-client/src/ApiModule.php:32
):
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
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-webhooks: PayPal webhook handling
- 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:
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:
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:
// In modules with container access
$service = $container->get( 'service.id' );
// In WordPress hooks after plugin initialization
$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
:
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:
'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:
// 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:
// 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 );