# Gacela in production: Phel

<p>
  <a href="https://phel-lang.org/" target="_blank" rel="noreferrer" aria-label="Phel Lang (opens in a new tab)">
    <svg width="150" height="128" viewBox="0 0 200 170" fill="none" stroke="currentColor" stroke-width="4" aria-hidden="true">
      <path d="M6 66l95 96h36V93l-36-74H42L6 56v106h36l24-35"/>
      <path d="M137 93l58-3-12-58-32-26h-34l-16 13-23 9-12 22 46 52z"/>
      <path d="M195 90l-12 57h-13l-2-56m2 56l-13-11h12"/>
    </svg>
  </a>
</p>

[Phel](https://phel-lang.org/) is a functional language that compiles to PHP. Its compiler, CLI, formatter, language
server, REPL, filesystem, and tooling are organized as Gacela modules in one actively maintained codebase.

<div class="stat-row">
  <div class="stat-row__stat"><strong>17+</strong><span>application modules</span></div>
  <div class="stat-row__stat"><strong>PHP 8.4</strong><span>declared platform</span></div>
  <div class="stat-row__stat"><strong>MIT</strong><span>open-source license</span></div>
</div>

## Why Gacela fits Phel

Phel has many subsystems but needs one coherent application. Gacela gives each subsystem a recognizable public boundary
and makes cross-module dependencies explicit:

- Callers enter through a Facade instead of depending on compiler internals.
- Factories construct application and domain services inside their module.
- Providers translate concrete Facades into the interfaces another module expects.
- Framework-created Symfony commands can still resolve typed Gacela services.
- Module health checks can be collected into operational diagnostics.

## Real code walkthrough

Every excerpt is Phel's own code, shortened where unrelated lines would obscure the Gacela pattern. The
**Sources** below the tabs link the complete production files.

The first four tabs follow one call:

1. **Bootstrap** starts Gacela once, then calls the Facade. The entry point never learns what a namespace runner is.
2. **Facade** is the only public door into the Run module. It delegates, it holds no logic.
3. **Factory** builds what the Facade asked for, wiring services that stay inside the module.
4. **Provider** is where another module arrives, as the interface the Factory asks for rather than a concrete class.

**Symfony command** and **Health check** are not later steps. They are two more callers entering that same boundary, one
created by the framework, one by operational tooling.

::: code-group

```php [Bootstrap]
// phel-lang: src/php/Phel.php

use Gacela\Framework\Gacela;
use Phel\Run\RunFacade;

public static function run(string $projectRootDir, string $namespace): void
{
    self::bootstrap($projectRootDir);
    (new RunFacade())->runNamespace($namespace);
}

public static function bootstrap(string $projectRootDir): void
{
    $configPath = $projectRootDir . '/' . self::PHEL_CONFIG_FILE_NAME;

    Gacela::bootstrap(
        $projectRootDir,
        self::configFn(self::readAppModulePaths($configPath)),
    );
}
```

```php [Facade]
// phel-lang: src/php/Run/RunFacade.php

final class RunFacade extends AbstractFacade implements RunFacadeInterface
{
    public function runNamespace(string $namespace): void
    {
        $this->getFactory()
            ->createNamespaceRunner()
            ->run($namespace);
    }

    public function getNamespaceFromFile(string $path): NamespaceInformation
    {
        return $this->getFactory()
            ->getBuildFacade()
            ->getNamespaceFromFile($path);
    }
}
```

```php [Factory]
// phel-lang: src/php/Run/RunFactory.php

class RunFactory extends AbstractFactory
{
    public function createNamespaceRunner(): NamespaceRunnerInterface
    {
        return new NamespaceRunner(
            $this->getCommandFacade(),
            $this->getBuildFacade(),
        );
    }

    public function getCommandFacade(): CommandFacadeInterface
    {
        return $this->getProvidedDependency(CommandFacadeInterface::class);
    }

    public function getBuildFacade(): BuildFacadeInterface
    {
        return $this->getProvidedDependency(BuildFacadeInterface::class);
    }
}
```

```php [Provider]
// phel-lang: src/php/Run/RunProvider.php

final class RunProvider extends AbstractProvider
{
    #[Provides(CommandFacadeInterface::class)]
    public function commandFacade(Container $container): CommandFacadeInterface
    {
        return $container->getLocator()->getRequired(CommandFacade::class);
    }

    #[Provides(BuildFacadeInterface::class)]
    public function buildFacade(Container $container): BuildFacadeInterface
    {
        return $container->getLocator()->getRequired(BuildFacade::class);
    }
}
```

```php [Symfony command]
// phel-lang: src/php/Run/Infrastructure/Command/CompileCommand.php

#[ServiceMap(method: 'getFacade', className: RunFacade::class)]
#[ServiceMap(method: 'getFactory', className: RunFactory::class)]
final class CompileCommand extends Command
{
    use ServiceResolverAwareTrait;

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $stderr = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;

        $this->getFacade()->loadPhelNamespaces();

        $source = $this->resolveSource(ScalarCoercion::toString($input->getArgument('source') ?? null));

        $ok = $this->getFactory()
            ->createCompileExecutor()
            ->execute(
                $source,
                static fn(string $chunk) => $output->write($chunk),
                static fn(string $chunk) => $stderr->write($chunk),
            );

        return $ok ? self::SUCCESS : self::FAILURE;
    }
}
```

```php [Health check]
// phel-lang: src/php/Build/Application/BuildHealthCheck.php

final readonly class BuildHealthCheck implements ModuleHealthCheckInterface
{
    public function checkHealth(): HealthStatus
    {
        if (is_dir($this->cacheDir) && !is_writable($this->cacheDir)) {
            return HealthStatus::unhealthy(
                sprintf('Cache dir not writable: %s', $this->cacheDir),
                ['path' => $this->cacheDir],
            );
        }

        return HealthStatus::healthy('Build directories are ready');
    }
}
```

:::

**Sources:** [bootstrap](https://github.com/phel-lang/phel-lang/blob/f173cf522d1b492cf12fb5404fa56c6b4bd454a4/src/php/Phel.php), [RunFacade](https://github.com/phel-lang/phel-lang/blob/f173cf522d1b492cf12fb5404fa56c6b4bd454a4/src/php/Run/RunFacade.php), [RunFactory](https://github.com/phel-lang/phel-lang/blob/f173cf522d1b492cf12fb5404fa56c6b4bd454a4/src/php/Run/RunFactory.php), [RunProvider](https://github.com/phel-lang/phel-lang/blob/f173cf522d1b492cf12fb5404fa56c6b4bd454a4/src/php/Run/RunProvider.php), [CompileCommand](https://github.com/phel-lang/phel-lang/blob/f173cf522d1b492cf12fb5404fa56c6b4bd454a4/src/php/Run/Infrastructure/Command/CompileCommand.php), and [BuildHealthCheck](https://github.com/phel-lang/phel-lang/blob/f173cf522d1b492cf12fb5404fa56c6b4bd454a4/src/php/Build/Application/BuildHealthCheck.php).

## What to copy into your project

The useful pattern is the direction of dependencies, not Phel's exact filenames:

```text
entry point → Facade → Factory → application/domain service
                         ↓
                     Provider → another module's Facade interface
```

Start a new module with the [Quickstart](https://gacela-project.com/docs/quickstart.md), then use [Getting dependencies](https://gacela-project.com/docs/getting-dependencies.md)
when it needs to communicate with another boundary.

## Explore Phel

- [Phel website](https://phel-lang.org/)
- [Source repository](https://github.com/phel-lang/phel-lang)
- [Phel on Packagist](https://packagist.org/packages/phel-lang/phel-lang)
