power-modules/framework

Framework for building modular PHP applications and plugin ecosystems with encapsulated modules and PowerModuleSetup extensions

Maintainers

Package info

github.com/power-modules/framework

pkg:composer/power-modules/framework

Statistics

Installs: 102

Dependents: 6

Suggesters: 0

Stars: 10

Open Issues: 0

v2.2.2 2026-03-05 21:17 UTC

This package is auto-updated.

Last update: 2026-03-05 21:18:41 UTC


README

CI Packagist Version PHP Version License: MIT PHPStan

A general-purpose modular architecture framework for PHP. Build applications where each module has its own dependency injection container, with carefully controlled sharing through explicit import/export contracts.

๐Ÿ’ก Versatile: Works well for CLI tools, data pipelines, background processors, APIs, and complex PHP applications that benefit from clear module boundaries.

โœจ Why Modular Framework?

  • ๐Ÿ”’ True Encapsulation: Each module has its own isolated DI container
  • โšก PowerModuleSetup: Extend module functionality without breaking encapsulation
  • ๐Ÿš€ Microservice Ready: Isolated modules can easily become independent services
  • ๐Ÿ“‹ Explicit Dependencies: Import/export contracts make relationships visible
  • ๐Ÿงช Better Testing: Test modules in isolation with their own containers
  • ๐Ÿ‘ฅ Team Scalability: Different teams can own different modules
  • ๐Ÿ”Œ Plugin-Ready: Third-party modules extend functionality safely

๐Ÿš€ Architectural Vision

This framework is more than just another library โ€” it introduces a new architectural paradigm to the PHP ecosystem, built on runtime-enforced encapsulation and true modularity, inspired by mature systems like OSGi.

To understand the core innovations and how this framework differs from established solutions like Symfony and Laravel, please read our Architectural Vision Document.

Quick Start

composer require power-modules/framework
use Modular\Framework\App\ModularAppBuilder;

class OrdersModule implements PowerModule, ExportsComponents {
    public static function exports(): array {
        return [
            OrderService::class,
        ];
    }

    public function register(ConfigurableContainerInterface $container): void
    {
        $container->set(OrderRepository::class, OrderRepository::class)
            ->addArguments([DatabaseConnection::class]);
        $container->set(OrderService::class, OrderService::class)
            ->addArguments([OrderRepository::class]);
    }
}

$app = new ModularAppBuilder(__DIR__)
    ->withModules(
        \MyApp\Auth\AuthModule::class,
        \MyApp\Orders\OrdersModule::class,
    )
    ->build();

// Get any exported service
$orderService = $app->get(\MyApp\Orders\OrderService::class);
// Fully initialized, with all dependencies resolved within the module's own container

โšก PowerModuleSetup Extension System

The framework's most powerful feature - PowerModuleSetup allows extending module functionality without breaking encapsulation:

$app = new ModularAppBuilder(__DIR__)
    ->withModules(UserModule::class, OrderModule::class)
    ->withPowerSetup(
        new RoutingSetup(),  // Adds HTTP routing to modules implementing HasRoutes interface
        new EventBusSetup(), // Pulls module events and handlers into a central event bus
    )
    ->build();

Available extensions:

Coming soon:

  • power-modules/events - Event-driven architecture
  • Your own! - Create custom PowerModuleSetup implementations for your needs

๐Ÿš€ Microservice Evolution Path

Start with a modular monolith, evolve to microservices naturally:

Today: Modular monolith

class UserModule implements PowerModule, ExportsComponents {
    public static function exports(): array {
        return [
            // Expose the service directly for in-process use
            UserRepositoryInterface::class,
        ];
    }

    public function register(ConfigurableContainerInterface $container): void
    {
        $container->set(UserRepositoryInterface::class, UserService::class);
    }
}

class OrderModule implements PowerModule, ImportsComponents {
    public static function imports(): array {
        return [
            // Import the interface from UserModule for in-process communication
            ImportItem::create(UserModule::class, UserRepositoryInterface::class),
        ];
    }

    public function register(ConfigurableContainerInterface $container): void
    {
        $container->set(OrderService::class, OrderService::class)
            ->addArguments([UserRepositoryInterface::class]);
    }
}

Later: Independent microservices

class UserModule implements PowerModule, ExportsComponents, HasRoutes {
    public static function exports(): array {
        return [
            // Still export the same interface โ€” now resolved to an HTTP client rather than an in-process service
            UserRepositoryInterface::class,
        ];
    }

    public function getRoutes(): array
    {
        return [
            // Define HTTP routes for the User API
            Route::get('/', UserController::class, 'list'),
        ];
    }

    public function register(ConfigurableContainerInterface $container): void
    {
        // Implementation details remain private; OrderModule doesn't need to know anything changed
        $container->set(UserApiService::class, UserApiService::class)
            ->addArguments([Psr\Http\ClientInterface::class]);
        // Bind the interface to the API service instead of the in-process service
        $container->set(UserRepositoryInterface::class, UserApiService::class);

        $container->set(UserService::class, UserService::class);
        $container->set(UserController::class, UserController::class)
            ->addArguments([UserService::class]);
    }
}

class OrderModule implements PowerModule, ImportsComponents {
    public static function imports(): array {
        return [
            // The same import as before โ€” now backed by an HTTP client instead of an in-process service.
            // You can also drop this import and implement your own HTTP client directly in OrderModule.
            ImportItem::create(UserModule::class, UserRepositoryInterface::class),
        ];
    }

    public function register(ConfigurableContainerInterface $container): void
    {
        $container->set(OrderService::class, OrderService::class)
            ->addArguments([UserRepositoryInterface::class]);
    }
}

โ„น๏ธ Note: This example is a simplified illustration of the evolution path. In a real application, UserModule might continue to support in-process communication alongside the HTTP client, and the actual microservice implementation would likely live in its own repository.

Because modules are designed with clear boundaries from the start, splitting them into independent services is a natural next step when you're ready to scale.

๐Ÿ“š Documentation

๐Ÿ“– Complete Documentation Hub - Comprehensive guides, examples, and API reference

Quick Links:

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT License. See LICENSE for details.