Gacela
Documentation version: 2.4.0
Colour theme

Build modular PHP applications.

Split your application into modules that talk through one door. Everything behind it stays private.

src/Checkout any module Facade Factory your services your domain Config config/*.php Provider other modules

The only door. Every other module in your application talks to this module through its Facade, and through nothing else. Change what is behind it freely: nobody outside can depend on what they cannot reach. Read about the Facade

Wires the inside. The Factory builds this module's own services and hands them their dependencies. It is where object construction lives, so your domain classes never have to know how they were made. Read about the Factory

Reaches outside. When the module needs something another module owns, the Provider resolves it. Extra-dependencies enter here and nowhere else, which keeps the coupling in one readable file. Read about the Provider

Reads the settings. The Config gives the Factory typed access to the project's configuration files, so a value can change per environment without any class inside the module learning where it came from. Read about the Config

Overview

Gacela in 60 seconds

Play video: Gacela in 60 seconds

Quickstart

A module in three files

This is the whole ceremony: a Facade in front, a Factory wiring a service behind it, and one bootstrap call at your entry point. The Facade resolves its sibling Factory automatically.

use Gacela\Framework\Gacela;
use Module\Facade;

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

Gacela::bootstrap(__DIR__);

$facade = new Facade();
echo $facade->greet('Alice'); # Hi, Alice!
namespace Module;

use Gacela\Framework\AbstractFacade;

/**
 * @method Factory getFactory()
 */
final class Facade extends AbstractFacade
{
    public function greet(string $name): string
    {
        return $this->getFactory()
            ->createGreeter()
            ->greet($name);
    }
}
namespace Module;

use Gacela\Framework\AbstractFactory;
use Module\Service\Greeter;

final class Factory extends AbstractFactory
{
    public function createGreeter(): Greeter
    {
        return new Greeter();
    }
}
namespace Module\Service;

final class Greeter
{
    public function greet(string $name): string
    {
        return "Hi, $name!";
    }
}

In practice

A real codebase, from the command line

Phel is a Lisp that compiles to PHP. Its compiler, REPL, formatter and language server are among its seventeen Gacela modules. This is what the Gacela CLI reports on it.

One shape, seventeen times

Every module has a Facade and a Factory, and most add a Config and a Provider. Read one module and you know where to look in all of them.

list:modules in the CLI reference
Terminal Output recorded at phel-lang@eea8d09
vendor/bin/gacela list:modules
┌──────────────────┬────────┬─────────┬────────┬──────────┐
│ Module namespace │ Facade │ Factory │ Config │ Provider │
├──────────────────┼────────┼─────────┼────────┼──────────┤
│ Phel\Api         │ x      │ x       │ x      │ x        │
│ Phel\Balance     │ x      │ x       │ x      │ x        │
│ Phel\Build       │ x      │ x       │ x      │ x        │
│ Phel\Command     │ x      │ x       │ x      │ x        │
│ Phel\Compiler    │ x      │ x       │ x      │ x        │
│ Phel\Console     │ x      │ x       │        │ x        │
│ Phel\Fiber       │ x      │ x       │ x      │          │
│ Phel\Filesystem  │ x      │ x       │ x      │          │
│ Phel\Formatter   │ x      │ x       │ x      │ x        │
│ Phel\Interop     │ x      │ x       │ x      │ x        │
│ Phel\Lint        │ x      │ x       │ x      │ x        │
│ Phel\Lsp         │ x      │ x       │ x      │ x        │
│ Phel\Mutate      │ x      │ x       │ x      │ x        │
│ Phel\Nrepl       │ x      │ x       │ x      │ x        │
│ Phel\Profile     │ x      │ x       │ x      │ x        │
│ Phel\Run         │ x      │ x       │ x      │ x        │
│ Phel\Watch       │ x      │ x       │ x      │ x        │
└──────────────────┴────────┴─────────┴────────┴──────────┘

Dependencies you can print

Modules reach each other through Facades, so Gacela can draw the graph. Here the console module wires fourteen others, and two modules depend on nothing.

debug:graph in the CLI reference
Terminal Output recorded at phel-lang@eea8d09
vendor/bin/gacela debug:graph
…
Phel\Console (14)
  -> Phel\Api
  -> Phel\Balance
  -> Phel\Build
  -> Phel\Compiler
…
Phel\Fiber (0)
Phel\Filesystem (0)
…

Look inside one module

See the four classes Gacela resolved for a module and every service its Provider declares with #[Provides].

debug:module in the CLI reference
Terminal Output recorded at phel-lang@eea8d09
vendor/bin/gacela debug:module 'Phel\Run'
Module: Run
  Facade    → Phel\Run\RunFacade
  Factory   → Phel\Run\RunFactory
  Config    → Phel\Run\RunConfig
  Provider  → Phel\Run\RunProvider
  Provides (#[Provides]):
    Phel\Shared\Facade\ApiFacadeInterface
    Phel\Shared\Facade\BuildFacadeInterface
    Phel\Shared\Facade\CommandFacadeInterface
    Phel\Shared\Facade\CompilerFacadeInterface
    Phel\Shared\Facade\FilesystemFacadeInterface
…

Checks that name the fix

doctor checks module paths, class names, caches and package manifests. On this run it caught a dependency that composer.json never declares, and said where it belongs.

doctor in the CLI reference
Terminal Output recorded at phel-lang@eea8d09
vendor/bin/gacela doctor
…
✓ suffix configuration
    17 module(s) use configured suffixes

✓ class filenames
    every pillar class matches its filename

…
⚠ package manifests
    phel-lang/phel-lang imports Symfony\Component\Finder\SplFileInfo, provided by symfony/finder, which its composer.json never mentions
    → add it to `require`, or to `suggest` when the dependency is optional by design
…
⚠ Doctor finished with warnings

Get started

Start your first module

composer require gacela-project/gacela:^2.2