PHP Classes

How to Use a PHP Service Class to Invoke Services Using Laravel and CakePHP Frameworks with the Package PHP Service Runner: Invoke services using payload data as parameters

Recommend this page to a friend!
     
  Info   Example   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2026-04-24 (8 days ago) RSS 2.0 feedNot yet rated by the usersTotal: Not yet counted Not yet ranked
Version License PHP version Categories
service-runner 1.0MIT/X Consortium ...8.4Libraries, Language, Design Patterns, P...
Description 

Author

This package can invoke services using payload data as parameters.

It provides a base service class that can get the parameters from a payload class object and executes the service by calling a given runner object.

The package also provides middleware classes to:

- Store the payload data

- Resolve the runner class that will execute the service

Picture of Jonatas Matheus Gino de Souza
Name: Jonatas Matheus Gino de ... <contact>
Classes: 2 packages by
Country: Brazil Brazil

Instructions

This package implements a handy pipeline architecture (or command bus design, if you prefer) PHP approach to achieve the DDD approach even on an n-tier framework or vanilla architecture.

Please read this document to learn how to set up and run a service passing payload parameters.

Example

<?php

declare(strict_types=1);

require
__DIR__ . '/../vendor/autoload.php';

use
Samples\VanillaPhp\Data\CreateUserData;
use
Samples\VanillaPhp\Middleware\HashPassword;
use
Samples\VanillaPhp\Middleware\PersistUser;
use
Samples\VanillaPhp\Middleware\ValidateEmail;
use
Samples\VanillaPhp\Service\CreateUserService;
use
ServiceRunner\Middleware\BasicResolver;
use
ServiceRunner\Middleware\Runner;
use
ServiceRunner\Middleware\ServicePayload;

$dbPath = '/app/database.sqlite';
$pdo = new PDO("sqlite:{$dbPath}");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    password TEXT NOT NULL
)'
);

$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

header('Content-Type: application/json');

if (
$path === '/users' && $method === 'POST') {
   
$body = json_decode(file_get_contents('php://input'), true) ?? [];

   
$name = $body['name'] ?? '';
   
$email = $body['email'] ?? '';
   
$password = $body['password'] ?? '';

   
$middlewares = [
        new
PersistUser($pdo),
        new
HashPassword(),
        new
ValidateEmail(),
    ];

   
$runner = new Runner($middlewares, new BasicResolver());
   
$service = new CreateUserService([], $runner);

   
$result = $service->run(new ServicePayload(new CreateUserData(
       
name: $name,
       
email: $email,
       
password: $password,
    )));

    if (
$result && $result->getAttribute('error')) {
       
http_response_code(422);
        echo
json_encode(['error' => $result->getAttribute('error')]);
    } elseif (
$result) {
       
http_response_code(201);
        echo
json_encode([
           
'id' => $result->getAttribute('user_id'),
           
'name' => $result->getAttribute('name'),
           
'email' => $result->getAttribute('email'),
        ]);
    } else {
       
http_response_code(400);
        echo
json_encode(['error' => 'Could not process request']);
    }
} elseif (
$path === '/users' && $method === 'GET') {
   
$stmt = $pdo->query('SELECT id, name, email FROM users ORDER BY id');
   
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

    echo
json_encode($users);
} else {
   
http_response_code(404);
    echo
json_encode(['error' => 'Not found']);
}


Details

Service Runner

A middleware pipeline runner for PHP application services. Compose use cases from ordered steps over an immutable payload, following the Pipeline pattern and Command Bus design.

Framework-agnostic at its core, with first-class integration for Laravel 11/12/13 and CakePHP 5.

Requirements

  • PHP 8.4+
  • Docker (for running tests and sample apps)
  • PSR-11 Container (optional, for dependency injection)

Installation

composer require sjonatas/service-runner

Core Concepts

PayloadData (DTO)

Input data for a service is defined as a readonly DTO implementing the PayloadData marker interface. Public properties are extracted automatically -- no boilerplate needed.

use ServiceRunner\Middleware\PayloadData;

readonly class CreateUserData implements PayloadData
{
    public function __construct(
        public string $name,
        public string $email,
    ) {}
}

Payload

An immutable data carrier that flows through the middleware pipeline. It is constructed from a PayloadData DTO.

use ServiceRunner\Middleware\ServicePayload;

$payload = new ServicePayload(new CreateUserData(
    name: 'John Doe',
    email: 'john@example.com',
));

// Read attributes
$payload->getAttribute('name');               // "John Doe"
$payload->getAttribute('role', 'guest');       // "guest" (default)
$payload->getAttributes();                    // full array

// Immutable transformations (returns a new instance)
$payload = $payload->withAttribute('status', 'active');
$payload = $payload->withAttributes(['name' => 'Jane']);
$payload = $payload->withoutAttribute('email');
$payload = $payload->mergeAttribute('tags', ['admin', 'editor']);

You can also implement the Payload interface to create your own payload class.

Middleware

A single step in the pipeline. Each middleware receives the payload and a $next callable to continue the chain.

use ServiceRunner\Middleware\Middleware;
use ServiceRunner\Middleware\Payload;

class ValidateEmail implements Middleware
{
    public function __invoke(Payload $payload, callable $next): Payload
    {
        $email = $payload->getAttribute('email');

        if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $payload->withAttribute('error', 'Invalid email');
        }

        return $next($payload);
    }
}

A middleware can:

  • Transform the payload before passing it forward
  • Short-circuit the pipeline by returning without calling `$next`
  • Post-process the result by acting on the payload returned from `$next`
class LogExecution implements Middleware
{
    public function __invoke(Payload $payload, callable $next): Payload
    {
        // Before
        logger()->info('Starting pipeline');

        // Forward
        $result = $next($payload);

        // After
        logger()->info('Pipeline completed', $result->getAttributes());

        return $result;
    }
}

Service

An abstract class that represents a use case. It holds configuration options and a middleware pipeline, and exposes a run() method to execute them.

use ServiceRunner\Service;
use ServiceRunner\Middleware\Payload;

class CreateUser extends Service
{
    // Optionally override to conditionally skip execution
    public function shouldProcess(Payload $payload): bool
    {
        return $payload->getAttribute('email') !== null;
    }
}

Services receive $options (an array of configuration) and a MiddlewareRunner (the pipeline) via the constructor. When run() is called, it merges service-level options with any options already on the payload, checks shouldProcess(), and invokes the pipeline.

ServiceConfig

An interface you implement to define your services and their middleware pipelines. This is where you wire everything together.

use ServiceRunner\ServiceConfig;

class AppServiceConfig implements ServiceConfig
{
    public function services(): array
    {
        return [
            CreateUser::class => [
                'middlewares' => [
                    ValidateEmail::class,
                    HashPassword::class,
                    PersistUser::class,
                    SendWelcomeEmail::class,
                ],
                'options' => [
                    'notify' => true,
                ],
            ],
            UpdateUser::class => [
                'middlewares' => [
                    ValidateEmail::class,
                    PersistUser::class,
                ],
                'options' => [],
            ],
        ];
    }
}

Middleware Resolution

Two resolvers are provided to convert class names into middleware instances:

| Resolver | When to use | |---|---| | BasicResolver | Vanilla PHP without a DI container. Instantiates middleware with new $entry(). Works for stateless middleware with no constructor dependencies. | | Resolver | Any PSR-11 container (Laravel, CakePHP, PHP-DI, League, Symfony, etc.). Resolves middleware via $container->get($entry), supporting full dependency injection. |

Usage

Vanilla PHP (no framework)

Without dependency injection

use ServiceRunner\Middleware\BasicResolver;
use ServiceRunner\Middleware\Runner;
use ServiceRunner\Middleware\ServicePayload;

$resolver = new BasicResolver();
$runner = new Runner([
    ValidateEmail::class,
    HashPassword::class,
    PersistUser::class,
], $resolver);

$service = new CreateUser([], $runner);

$result = $service->run(new ServicePayload(new CreateUserData(
    name: 'John Doe',
    email: 'john@example.com',
)));

With a PSR-11 container (e.g. PHP-DI)

use DI\ContainerBuilder;
use ServiceRunner\Middleware\Resolver;
use ServiceRunner\Middleware\Runner;
use ServiceRunner\Middleware\ServicePayload;

$container = (new ContainerBuilder())->build();
$resolver = new Resolver($container);

$runner = new Runner([
    ValidateEmail::class,
    HashPassword::class,
    PersistUser::class,
], $resolver);

$service = new CreateUser([], $runner);

$result = $service->run(new ServicePayload(new CreateUserData(
    name: 'John Doe',
    email: 'john@example.com',
)));

Using ServiceConfig to centralize definitions

use ServiceRunner\Middleware\BasicResolver;
use ServiceRunner\Middleware\Runner;
use ServiceRunner\Middleware\ServicePayload;

$config = new AppServiceConfig();
$resolver = new BasicResolver();

foreach ($config->services() as $serviceClass => $definition) {
    $runner = new Runner($definition['middlewares'], $resolver);
    $service = new $serviceClass($definition['options'], $runner);

    // Register in your app however you prefer
}

Laravel (11 / 12 / 13)

The package auto-discovers its service provider via Composer -- no manual registration needed.

1. Create your ServiceConfig implementation:

// app/Services/AppServiceConfig.php
namespace App\Services;

use ServiceRunner\ServiceConfig;

class AppServiceConfig implements ServiceConfig
{
    public function services(): array
    {
        return [
            \App\Services\CreateUser::class => [
                'middlewares' => [
                    \App\Middleware\ValidateEmail::class,
                    \App\Middleware\HashPassword::class,
                    \App\Middleware\PersistUser::class,
                ],
                'options' => ['notify' => true],
            ],
        ];
    }
}

2. Bind it in a service provider:

// app/Providers/AppServiceProvider.php
use App\Services\AppServiceConfig;
use ServiceRunner\ServiceConfig;

public function register(): void
{
    $this->app->bind(ServiceConfig::class, AppServiceConfig::class);
}

3. Resolve and run services from the container:

use App\Services\CreateUser;
use ServiceRunner\Middleware\ServicePayload;

$service = app(CreateUser::class);

$result = $service->run(new ServicePayload(new CreateUserData(
    name: 'John Doe',
    email: 'john@example.com',
)));

The provider automatically: - Registers Resolver as a singleton backed by Laravel's container - Aliases MiddlewareResolver to the concrete Resolver - Reads your ServiceConfig and binds each service to the container with its pipeline wired up

CakePHP 5

1. Create your ServiceConfig implementation:

// src/Services/AppServiceConfig.php
namespace App\Services;

use ServiceRunner\ServiceConfig;

class AppServiceConfig implements ServiceConfig
{
    public function services(): array
    {
        return [
            \App\Services\CreateUser::class => [
                'middlewares' => [
                    \App\Middleware\ValidateEmail::class,
                    \App\Middleware\HashPassword::class,
                    \App\Middleware\PersistUser::class,
                ],
                'options' => ['notify' => true],
            ],
        ];
    }
}

2. Register in your Application:

// src/Application.php
use Cake\Core\ContainerInterface;
use App\Services\AppServiceConfig;
use ServiceRunner\ServiceConfig;
use ServiceRunner\Framework\CakePHP\ServiceRunnerProvider;

public function services(ContainerInterface $container): void
{
    $container->add(ServiceConfig::class, AppServiceConfig::class);
    $container->addServiceProvider(new ServiceRunnerProvider());
}

3. Use in controllers or anywhere the container is available:

use App\Services\CreateUser;
use ServiceRunner\Middleware\ServicePayload;

// In a controller action
public function add(CreateUser $createUser)
{
    $result = $createUser->run(new ServicePayload(new CreateUserData(
        name: $this->request->getData('name'),
        email: $this->request->getData('email'),
    )));
}

Architecture

flowchart TD
    A["Service::run(Payload)"] --> B{shouldProcess?}
    B -- "false ? return null" --> Z["? Skipped"]
    B -- "true" --> C["Resolver resolves middleware classes"]
    C --> D["Middleware Step 1"]
    D --> E{calls $next?}
    E -- "no ? short-circuit" --> R["? Return Payload"]
    E -- "yes" --> F["Middleware Step 2"]
    F --> G{calls $next?}
    G -- "no ? short-circuit" --> R
    G -- "yes" --> H["Middleware Step N"]
    H --> I{calls $next?}
    I -- "no ? short-circuit" --> R
    I -- "yes" --> J["? Result Payload"]

    style Z fill:#fee,stroke:#c00
    style R fill:#fef,stroke:#909
    style J fill:#efe,stroke:#090

Each middleware receives the payload and a $next callable. It can:

  • Transform the payload and pass it forward via `$next($payload)`
  • Short-circuit the pipeline by returning without calling `$next`
  • Post-process the result returned from `$next`

The Resolver converts class names in the queue into middleware instances ? either by direct instantiation (BasicResolver) or through a PSR-11 container (Resolver).

Testing

Run the test suite with code coverage via Docker:

./run-tests.sh

This generates: - Clover XML at coverage/clover.xml (for Codecov / CI integration) - HTML report at coverage/html/index.html (open in browser) - Summary printed to the console

You can pass additional PHPUnit arguments:

./run-tests.sh --filter ServicePayloadTest
./run-tests.sh --no-coverage

Sample Apps

Three runnable sample applications are included, each demonstrating the library in a different environment. All use SQLite for persistence and run in Docker containers.

| Sample | Framework | URL | Port | |---|---|---|---| | Vanilla PHP | Built-in server | localhost:8080 | 8080 | | Laravel | Artisan serve | localhost:8081 | 8081 | | CakePHP 5 | Cake server | localhost:8082 | 8082 |

Running the samples

Start all three samples at once:

docker compose up sample-vanilla sample-laravel sample-cakephp

Or start a single sample:

docker compose up sample-vanilla

Trying the API

Each sample exposes the same endpoints:

# Create a user
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "John Doe", "email": "john@example.com", "password": "secret123"}'

# List users
curl http://localhost:8080/users

Replace port 8080 with 8081 (Laravel) or 8082 (CakePHP) to test the other samples. Laravel routes are prefixed with /api, so use http://localhost:8081/api/users.

Sample structure

Each sample lives in samples/<framework>/ with its own Dockerfile, composer.json, and application code. The library is mounted as a Composer path repository, so any changes to the library source are reflected immediately.

Project Structure

src/
??? Service.php                         # Abstract service (use case)
??? ServiceConfig.php                   # Interface for service definitions
??? Middleware/
?   ??? Middleware.php                  # Middleware contract
?   ??? MiddlewareRunner.php            # Runner contract
?   ??? MiddlewareResolver.php          # Resolver contract
?   ??? Runner.php                      # Pipeline implementation
?   ??? Resolver.php                    # PSR-11 container resolver
?   ??? BasicResolver.php              # No-DI resolver
?   ??? Payload.php                    # Payload contract
?   ??? PayloadData.php               # DTO contract (input data)
?   ??? ServicePayload.php            # Default Payload implementation
??? Framework/
?   ??? Laravel/
?   ?   ??? ServiceRunnerProvider.php   # Laravel service provider
?   ??? CakePHP/
?       ??? ServiceRunnerProvider.php   # CakePHP 5 service provider
samples/
??? vanilla-php/                        # Vanilla PHP sample (port 8080)
??? laravel/                            # Laravel sample (port 8081)
??? cakephp/                            # CakePHP 5 sample (port 8082)

License

MIT


  Files folder image Files (73)  
File Role Description
Files folder imagesamples (3 directories)
Files folder imagesrc (2 files, 2 directories)
Files folder imagetests (5 files, 1 directory)
Accessible without login Plain text file composer.json Data Auxiliary data
Accessible without login Plain text file docker-compose.yml Data Auxiliary data
Accessible without login Plain text file Dockerfile Data Auxiliary data
Accessible without login Plain text file LICENSE Lic. License text
Accessible without login Plain text file phpunit.xml.dist Data Auxiliary data
Accessible without login Plain text file README.md Doc. Documentation
Accessible without login Plain text file run-tests.sh Data Auxiliary data

  Files folder image Files (73)  /  samples  
File Role Description
Files folder imagecakephp (2 files, 4 directories)
Files folder imagelaravel (4 files, 6 directories)
Files folder imagevanilla-php (2 files, 2 directories)

  Files folder image Files (73)  /  samples  /  cakephp  
File Role Description
Files folder imagebin (1 file)
Files folder imageconfig (4 files)
Files folder imagesrc (1 file, 5 directories)
Files folder imagewebroot (1 file)
  Accessible without login Plain text file composer.json Data Auxiliary data
  Accessible without login Plain text file Dockerfile Data Auxiliary data

  Files folder image Files (73)  /  samples  /  cakephp  /  bin  
File Role Description
  Accessible without login Plain text file cake Example Example script

  Files folder image Files (73)  /  samples  /  cakephp  /  config  
File Role Description
  Plain text file app.php Class Class source
  Accessible without login Plain text file bootstrap.php Example Example script
  Accessible without login Plain text file paths.php Aux. Configuration script
  Plain text file routes.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  src  
File Role Description
Files folder imageConfig (1 file)
Files folder imageController (2 files)
Files folder imageData (1 file)
Files folder imageMiddleware (3 files)
Files folder imageService (1 file)
  Plain text file Application.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  src  /  Config  
File Role Description
  Plain text file AppServiceConfig.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  src  /  Controller  
File Role Description
  Plain text file AppController.php Class Class source
  Plain text file UsersController.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  src  /  Data  
File Role Description
  Plain text file CreateUserData.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  src  /  Middleware  
File Role Description
  Plain text file HashPassword.php Class Class source
  Plain text file PersistUser.php Class Class source
  Plain text file ValidateEmail.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  src  /  Service  
File Role Description
  Plain text file CreateUserService.php Class Class source

  Files folder image Files (73)  /  samples  /  cakephp  /  webroot  
File Role Description
  Accessible without login Plain text file index.php Example Example script

  Files folder image Files (73)  /  samples  /  laravel  
File Role Description
Files folder imageapp (6 directories)
Files folder imagebootstrap (2 files)
Files folder imageconfig (2 files)
Files folder imagedatabase (1 directory)
Files folder imagepublic (1 file)
Files folder imageroutes (1 file)
  Accessible without login Plain text file .env Data Auxiliary data
  Accessible without login Plain text file artisan Example Example script
  Accessible without login Plain text file composer.json Data Auxiliary data
  Accessible without login Plain text file Dockerfile Data Auxiliary data

  Files folder image Files (73)  /  samples  /  laravel  /  app  
File Role Description
Files folder imageConfig (1 file)
Files folder imageData (1 file)
Files folder imageHttp (1 directory)
Files folder imageMiddleware (3 files)
Files folder imageProviders (1 file)
Files folder imageService (1 file)

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Config  
File Role Description
  Plain text file AppServiceConfig.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Data  
File Role Description
  Plain text file CreateUserData.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Http  
File Role Description
Files folder imageControllers (1 file)

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Http  /  Controllers  
File Role Description
  Plain text file UserController.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Middleware  
File Role Description
  Plain text file HashPassword.php Class Class source
  Plain text file PersistUser.php Class Class source
  Plain text file ValidateEmail.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Providers  
File Role Description
  Plain text file AppServiceProvider.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  app  /  Service  
File Role Description
  Plain text file CreateUserService.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  bootstrap  
File Role Description
  Accessible without login Plain text file app.php Example Example script
  Plain text file providers.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  config  
File Role Description
  Accessible without login Plain text file app.php Aux. Configuration script
  Accessible without login Plain text file database.php Aux. Configuration script

  Files folder image Files (73)  /  samples  /  laravel  /  database  
File Role Description
Files folder imagemigrations (1 file)

  Files folder image Files (73)  /  samples  /  laravel  /  database  /  migrations  
File Role Description
  Plain text file 0001_01_01_000001_create_users_table.php Class Class source

  Files folder image Files (73)  /  samples  /  laravel  /  public  
File Role Description
  Accessible without login Plain text file index.php Example Example script

  Files folder image Files (73)  /  samples  /  laravel  /  routes  
File Role Description
  Plain text file api.php Class Class source

  Files folder image Files (73)  /  samples  /  vanilla-php  
File Role Description
Files folder imagepublic (1 file)
Files folder imagesrc (3 directories)
  Accessible without login Plain text file composer.json Data Auxiliary data
  Accessible without login Plain text file Dockerfile Data Auxiliary data

  Files folder image Files (73)  /  samples  /  vanilla-php  /  public  
File Role Description
  Accessible without login Plain text file index.php Example Example script

  Files folder image Files (73)  /  samples  /  vanilla-php  /  src  
File Role Description
Files folder imageData (1 file)
Files folder imageMiddleware (3 files)
Files folder imageService (1 file)

  Files folder image Files (73)  /  samples  /  vanilla-php  /  src  /  Data  
File Role Description
  Plain text file CreateUserData.php Class Class source

  Files folder image Files (73)  /  samples  /  vanilla-php  /  src  /  Middleware  
File Role Description
  Plain text file HashPassword.php Class Class source
  Plain text file PersistUser.php Class Class source
  Plain text file ValidateEmail.php Class Class source

  Files folder image Files (73)  /  samples  /  vanilla-php  /  src  /  Service  
File Role Description
  Plain text file CreateUserService.php Class Class source

  Files folder image Files (73)  /  src  
File Role Description
Files folder imageFramework (2 directories)
Files folder imageMiddleware (9 files)
  Plain text file Service.php Class Class source
  Plain text file ServiceConfig.php Class Class source

  Files folder image Files (73)  /  src  /  Framework  
File Role Description
Files folder imageCakePHP (1 file)
Files folder imageLaravel (1 file)

  Files folder image Files (73)  /  src  /  Framework  /  CakePHP  
File Role Description
  Plain text file ServiceRunnerProvider.php Class Class source

  Files folder image Files (73)  /  src  /  Framework  /  Laravel  
File Role Description
  Plain text file ServiceRunnerProvider.php Class Class source

  Files folder image Files (73)  /  src  /  Middleware  
File Role Description
  Plain text file BasicResolver.php Class Class source
  Plain text file Middleware.php Class Class source
  Plain text file MiddlewareResolver.php Class Class source
  Plain text file MiddlewareRunner.php Class Class source
  Plain text file Payload.php Class Class source
  Plain text file PayloadData.php Class Class source
  Plain text file Resolver.php Class Class source
  Plain text file Runner.php Class Class source
  Plain text file ServicePayload.php Class Class source

  Files folder image Files (73)  /  tests  
File Role Description
Files folder imageStub (4 files)
  Plain text file BasicResolverTest.php Class Class source
  Plain text file ResolverTest.php Class Class source
  Plain text file RunnerTest.php Class Class source
  Plain text file ServicePayloadTest.php Class Class source
  Plain text file ServiceTest.php Class Class source

  Files folder image Files (73)  /  tests  /  Stub  
File Role Description
  Plain text file OptionsData.php Class Class source
  Plain text file PassthroughMiddleware.php Class Class source
  Plain text file SimpleData.php Class Class source
  Plain text file TagsData.php Class Class source

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads  
 100%
Total:0
This week:0