PHPackages                             mrhdolek/slim4-boirlerplate - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. mrhdolek/slim4-boirlerplate

ActiveLibrary[Testing &amp; Quality](/categories/testing)

mrhdolek/slim4-boirlerplate
===========================

An Slim 4 Framework skeleton using AMQP,DDD,Doctrine

v1.7.0(2w ago)43[7 issues](https://github.com/MrHDOLEK/slim4-boirlerplate/issues)[1 PRs](https://github.com/MrHDOLEK/slim4-boirlerplate/pulls)MITPHPPHP ^8.4.1CI passing

Since Oct 19Pushed 2w ago1 watchersCompare

[ Source](https://github.com/MrHDOLEK/slim4-boirlerplate)[ Packagist](https://packagist.org/packages/mrhdolek/slim4-boirlerplate)[ Docs](https://github.com/MrHDOLEK/slim4-boirlerplat)[ RSS](/packages/mrhdolek-slim4-boirlerplate/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (9)Dependencies (63)Versions (14)Used By (0)

Slim 4 Framework skeleton
=========================

[](#slim-4-framework-skeleton)

[![Slim](slim.webp)](slim.webp)

[![CI](https://github.com/MrHDOLEK/slim4-boirlerplate/actions/workflows/php.yml/badge.svg?branch=main)](https://github.com/MrHDOLEK/slim4-boirlerplate/actions/workflows/php.yml)[![Codecov.io](https://camo.githubusercontent.com/1328c1e456c85d602291b993079d6d8727f78a3d68bff3f77622207c9980f807/68747470733a2f2f636f6465636f762e696f2f67682f4d7248444f4c454b2f736c696d342d626f69726c6572706c6174652f67726170682f62616467652e7376673f746f6b656e3d4b4b424d5735484a564d)](https://codecov.io/gh/MrHDOLEK/slim4-boirlerplate)[![License](https://camo.githubusercontent.com/74b24a8a75b5cd79623aefdbf3b3e3886a41930e7c1847ccaaab31a12f0d3dfb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f726f62696e696e67656c6272656368742f736c696d2d736b656c65746f6e2d6464642d616d71703f636f6c6f723d343238663765266c6f676f3d6f70656e253230736f75726365253230696e6974696174697665266c6f676f436f6c6f723d7768697465)](https://github.com/MrHDOLEK/slim4-boirlerplate/blob/master/LICENSE)[![PHPStan Enabled](https://camo.githubusercontent.com/6b08db89582f3584413651380b2c28794c66549ebf12937ed15c92acad4303b0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230352d7375636365732e7376673f6c6f676f3d706870266c6f676f436f6c6f723d776869746526636f6c6f723d333143363532)](https://phpstan.org/)[![PHP](https://camo.githubusercontent.com/585aac880456b4c6ea6d40755b7f208dd4ee508d2a5c35239f09ea4a1b161377/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6d7268646f6c656b2f736c696d342d626f69726c6572706c6174652f6465762d6d61696e3f636f6c6f723d253233373737626233266c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://php.net/)

---

#### An Slim 4 Framework skeleton using AMQP and DDD

[](#an-slim-4-framework-skeleton-using-amqp-and-ddd)

I was inspired to create this skeleton from: [robiningelbrecht](https://github.com/robiningelbrecht).

Project setup
-------------

[](#project-setup)

### Development

[](#development)

If you have problems with permissions please add sudo before make example:

- `sudo make install`
- `sudo make start`

### Run env for Mac/Linux

[](#run-env-for-maclinux)

- `make install`
- `make start`
- `make db-create`

### Run env for Windows

[](#run-env-for-windows)

Please install packages makefile for [Windows](http://gnuwin32.sourceforge.net/packages/make.htm)

- `make install`
- `make start`
- `make db-create`

### Address where the environment is available

[](#address-where-the-environment-is-available)

- `http://localhost`

Documentation for a Rest Api
----------------------------

[](#documentation-for-a-rest-api)

- `http://localhost/docs/v1`

RabbitMq dashboard
------------------

[](#rabbitmq-dashboard)

- `http://localhost:15672`

Kafka dashboard
---------------

[](#kafka-dashboard)

- `http://localhost:8080`

All commands
------------

[](#all-commands)

- `make help`

Dev tooling
-----------

[](#dev-tooling)

Static analysis and code style live in their own isolated composer projects under `tools/`, so their dependencies never enter the application's dependency graph:

ToolLocationCommandPHPStan (+ custom architecture rules)`tools/phpstan``make phpstan`PHP CS Fixer (blumilksoftware/codestyle)`tools/cs-fixer``make cs-check` / `make cs-fix`Deptrac (layer guard)`tools/deptrac``make deptrac``make install` installs them. To (re)install or bump them on their own:

- `make tools-install`
- `make tools-update`

The layer guard enforces `Infrastructure -> Application -> Domain`. Pre-existing violations are grandfathered in `deptrac.baseline.yaml`, so only new ones fail the build.

Deployment
----------

[](#deployment)

The product is packaged as a Helm chart in `.k8s` — an `app` Deployment (nginx + php-fpm), one Deployment per declared messaging consumer, a `scheduler` CronJob, and pre-sync Jobs. Every runtime value comes from a Kubernetes Secret; the image ships no `.env`.

- `make helm-lint`
- `make helm-template`
- `make helm-template-kafka`

### Consumers

[](#consumers)

Consumers are values-driven: every entry under `consumers` becomes its own Deployment running `php bin/console.php app:messaging:consume `, so a chart install can run AMQP only, Kafka only, or both at once.

```
consumers:
  user-events:
    enabled: true
    broker: kafka
    source: user-events
    replicaCount: 2
    resources:
      requests:
        cpu: 50m
        memory: 128Mi
    autoscaling:
      enabled: true
      minReplicas: 1
      maxReplicas: 4
      targetCPUUtilizationPercentage: 75
```

`broker` and `source` are required; `source` is the transport name registered by `AsAmqpQueue` or `AsKafkaTopic`, and the broker only shows up as the `messaging.slim4-app/broker` label. Every consumer gets its own optional HPA.

`.k8s/values-amqp.yaml` and `.k8s/values-kafka.yaml` are ready-made overlays that pick one broker, enable its consumer and set its env keys.

### Jobs

[](#jobs)

JobValues keyHookCommandDoctrine migrations`jobs.migrate`PreSync`vendor/bin/doctrine-migrations migrate`Avro schema registration`jobs.kafkaSchemaRegister`PreSync`app:kafka:schema:register`Fixtures`jobs.seed`PostSync`db:seed``jobs.kafkaSchemaRegister` is off by default and pushes every `resources/avro/*.avsc` to the schema registry before the new pods roll, so Kafka producers never write against an unregistered subject.

### Secret keys

[](#secret-keys)

All of these go into `env.secret` (or the Secret named by `env.existingSecret`).

ScopeKeysApp`APP_NAME`, `ENVIRONMENT`, `DISPLAY_ERROR_DETAILS`, `LOG_ERRORS`, `LOG_ERROR_DETAILS`Database`DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DOCTRINE_CACHE_TTL`, `SLOW_QUERY_THRESHOLD`Redis`REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`, `REDIS_CACHE_DB`, `REDIS_DEFAULT_DB`Messaging`EVENT_BROKER` (`amqp` or `kafka`, picks the broker domain events are published to)AMQP`RABBITMQ_HOST`, `RABBITMQ_PORT`, `RABBITMQ_USER`, `RABBITMQ_PASS`, `RABBITMQ_VHOST`Kafka`KAFKA_BROKERS`, `KAFKA_CONSUMER_GROUP`, `KAFKA_SCHEMA_REGISTRY_URL`, `KAFKA_AUTO_OFFSET_RESET`Some examples
-------------

[](#some-examples)

### Registering a new route

[](#registering-a-new-route)

```
namespace App\Application\Actions\User;

class GetAllUsersAction extends UserAction
{
    public function __construct(
        private readonly UserService $userService,
        protected LoggerInterface $logger,
    ) {
        parent::__construct($logger);
    }

    protected function action(): Response
    {
        $user = $this->userService->getAllUsers();

        return $this->respondWithJson(new UsersResponseDto($user));
    }
}
```

Head over to `config/routes.php` and add a route for your RequestHandler:

```
return function (App $app) {
        $group->get("/users", GetAllUsersAction::class)
            ->setName("getAllUsers");
};
```

### Console commands

[](#console-commands)

The console application uses the Symfony console component to leverage CLI functionality.

```
#[AsCommand(name: 'app:user:create')]
class CreateUserConsoleCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // ...
        return Command::SUCCESS;
    }
}
```

### Scheduling Commands

[](#scheduling-commands)

To schedule a command, we use the GO\\Scheduler class. This class allows us to define the timing and frequency of command execution. Here's an example of how to schedule a command to run daily:

```
$scheduler = new GO\Scheduler();
$scheduler->php('/path/to/command app:user:create')->daily();
$scheduler->run();
```

In this example, the app:user:create command is scheduled to run every day.

#### Running the Scheduler

[](#running-the-scheduler)

The scheduler should be triggered by a system cron job to ensure it runs at regular intervals. Typically, you would set up a cron job to execute a PHP script that initializes and runs the scheduler.

For instance, a cron job running every minute might look like this:

```
* * * * * ./bin/console.php schedule
```

This setup ensures that your scheduled commands are executed reliably and on time.

### Domain event and event handlers

[](#domain-event-and-event-handlers)

The framework implements the amqp protocol with handlers that allow events to be easily pushed onto the queue. Each event must have a handler implemented that consumes the event.

#### Creating a new event

[](#creating-a-new-event)

```
class UserWasCreated extends DomainEvent
{

}
```

#### Creating the corresponding event handler

[](#creating-the-corresponding-event-handler)

```
namespace App\Domain\Entity\User\DomainEvents;

#[AsEventHandler]
class UserWasCreatedEventHandler implements EventHandler
{
    public function __construct(
    ) {
    }

    public function handle(DomainEvent $event): void
    {
        assert($event instanceof UserWasCreated);

        // Do stuff.
    }
}
```

### Eventing

[](#eventing)

#### Create a new event

[](#create-a-new-event)

```
class UserWasCreated extends DomainEvent
{
    public function __construct(
        private UserId $userId,
    ) {
    }

    public function getUserId(): UserId
    {
        return $this->userId;
    }
}
```

### Async processing of events

[](#async-processing-of-events)

Both brokers are wired behind the same `App\Infrastructure\Messaging\Transport` contract, so a domain event can be routed to either one. `UserEventQueue` (RabbitMQ) and `UserEventTopic` (Kafka) are the two interchangeable bindings of the same domain concept, and both hand their messages to the same `EventQueueWorker`.

#### Registering a transport for your events

[](#registering-a-transport-for-your-events)

```
#[AsAmqpQueue(name: "user-command-queue", numberOfWorkers: 1)]
class UserEventQueue extends AmqpQueue
{
    public function __construct(
        AMQPChannelFactory $AMQPChannelFactory,
        MessageSerializer $serializer,
        FailedQueueFactory $failedQueueFactory,
        private readonly EventQueueWorker $worker,
    ) {
        parent::__construct($AMQPChannelFactory, $serializer, $failedQueueFactory);
    }

    public function getWorker(): Worker
    {
        return $this->worker;
    }
}
```

#### Publishing events

[](#publishing-events)

The domain never names a broker, it is handed an `EventPublisher` that wraps whichever transport is configured.

```
final readonly class UserEventsService
{
    public function __construct(
        private EventPublisher $eventPublisher,
    ) {}

    public function userWasCreated(User $user): void
    {
        $this->eventPublisher->publish(new UserWasCreated($user));
    }
}
```

#### Choosing the broker

[](#choosing-the-broker)

`EVENT_BROKER` decides which transport the `EventPublisher` is built on, `amqp` (default) or `kafka`. The map lives in `config/container.php`.

#### Removing a broker

[](#removing-a-broker)

Each broker is a self-contained unit. Everything a broker owns lives under its own directory, and the only shared files that name it are the two wiring files plus the settings and chart values.

To drop RabbitMQ, delete:

```
src/Infrastructure/AMQP/
tests/Infrastructure/AMQP/

```

To drop Kafka, delete:

```
src/Infrastructure/Kafka/
src/Application/Console/Utility/KafkaSchemaRegisterConsoleCommand.php
tests/Infrastructure/Kafka/
resources/avro/

```

`KafkaSchemaRegisterConsoleCommand` cannot live under `src/Infrastructure/Kafka/` because `ConsoleCommandCompilerPass` only scans `src/Application/Console`.

Then drop the broker's entry from `config/compiler-passes.php` (the transport attribute), `config/container.php`(the service definitions, the `EventPublisher` map and the `BrokerHealthCheck` map), `config/settings.php`, the matching `.env` keys and `.k8s/values-*.yaml`. Nothing under `src/Domain`, `src/Infrastructure/Events`, `src/Infrastructure/Messaging` or the other broker's tests refers to it.

#### Consuming your transport

[](#consuming-your-transport)

```
> docker-compose run --rm php bin/console.php app:messaging:consume user-command-queue
> docker-compose run --rm php bin/console.php app:messaging:consume user-events
```

### Create new entity

[](#create-new-entity)

If you have created a new entity and want to map it to a database you must create a xml in src/Infrastructure/Persistence/Doctrine/Mapping . It must be named so as to indicate where exactly the entity to be mapped is located.

### Binding of interfaces/Registry global objects

[](#binding-of-interfacesregistry-global-objects)

To register a dependency or create a single configured global instance, you need to go to config/container.php

### Mapping database data to custom objects

[](#mapping-database-data-to-custom-objects)

To map data from a database to a custom object you need to extend something from Doctrine/DBAL/Types .

```
use App\Domain\ValueObject\UserType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;

class UserTypeType extends StringType
{
    const TYPE_NAME = 'UserType';

    public function convertToPHPValue($value, AbstractPlatform $platform): ?UserType
    {
        return null !== $value ? UserType::fromString($value) : null;
    }

    public function getName()
    {
        return self::TYPE_NAME;
    }
}
```

After extending, you need to add a note in the xml that you are mapping a field to an object.

```

```

Finally, you must add the new type in config/container.php where doctrine is configured

```
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Setup;

return [
...
    EntityManager::class => function (Settings $settings): EntityManager {
        $config = Setup::createXMLMetadataConfiguration(
            $settings->get("doctrine.metadata_dirs"),
            $settings->get("doctrine.dev_mode"),
        );
        if (!Type::hasType('UserType')) {
          Type::addType('UserType', UserTypeType::class);
        }
        return EntityManager::create($settings->get("doctrine.connection"), $config);
    },
 ...
];
```

### Database migrations

[](#database-migrations)

To manage database migrations, the doctrine/migrations package is used.

```

utf8mb4_polish_ci

```

The mapping is done using a yaml which maps your entities from the domain to a structure in the database . If you change something in yaml, you can use the commands below to generate a migration based on the difference.

```
> docker-compose run --rm php vendor/bin/doctrine-migrations diff
> docker-compose run --rm php vendor/bin/doctrine-migrations migrate
```

Documentations
--------------

[](#documentations)

Learn more at these links:

- [Slim framework](https://www.slimframework.com)
- [PHP-DI](https://php-di.org/)
- [Symfony Console Commands](https://symfony.com/doc/current/console.html)
- [Doctrine migrations](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.6/)
- [Doctrine reference](https://www.doctrine-project.org/projects/doctrine-bundle/en/latest/configuration.html)
- [Twig](https://twig.symfony.com/)
- [Symfony Serializer](https://symfony.com/doc/current/serializer.html)

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance76

Regular maintenance activity

Popularity8

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 86.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~126 days

Recently: every ~245 days

Total

9

Last Release

20d ago

PHP version history (3 changes)v1.0.0PHP ^8.2

v1.6.0PHP &gt;=8.3 &lt;8.5

v1.7.0PHP ^8.4.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/ea66aa634465dd5f5208dc9de30ff4207f1952da8dc109406279ef89e700f84b?d=identicon)[MrHDOLEK](/maintainers/MrHDOLEK)

---

Top Contributors

[![MrHDOLEK](https://avatars.githubusercontent.com/u/27227621?v=4)](https://github.com/MrHDOLEK "MrHDOLEK (31 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (3 commits)")[![kamilpiech97](https://avatars.githubusercontent.com/u/29115024?v=4)](https://github.com/kamilpiech97 "kamilpiech97 (2 commits)")

---

Tags

amqpddddoctrinehactoberfesthactoberfest-acceptedhactoberfest2023microservicesphpunitrabbitmqslimslim4slim4-doctrineslim4-frameworkslim4-skeletonswoolerestrouterpsr7doctrineAMQPmicroframeworkddd

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/mrhdolek-slim4-boirlerplate/health.svg)

```
[![Health](https://phpackages.com/badges/mrhdolek-slim4-boirlerplate/health.svg)](https://phpackages.com/packages/mrhdolek-slim4-boirlerplate)
```

###  Alternatives

[azuracast/azuracast

The AzuraCast self-hosted web radio station management suite.

4.0k27.9k](/packages/azuracast-azuracast)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k6.0M779](/packages/sylius-sylius)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M675](/packages/shopware-core)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[oro/platform

Business Application Platform (BAP)

645146.3k118](/packages/oro-platform)[pimcore/pimcore

Content &amp; Product Management Framework (CMS/PIM/E-Commerce)

3.8k3.9M535](/packages/pimcore-pimcore)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
