PHPackages                             xphp-lang/xphp-symfony-bundle - 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. [Framework](/categories/framework)
4. /
5. xphp-lang/xphp-symfony-bundle

ActiveSymfony-bundle[Framework](/categories/framework)

xphp-lang/xphp-symfony-bundle
=============================

Symfony bundle to write XPHP (PHP with native generics) inside a Symfony app and deploy the auto-generated PHP.

v0.2.0(1mo ago)10MITPHPPHP ^8.4.0CI passing

Since Jun 3Pushed 1mo agoCompare

[ Source](https://github.com/xphp-lang/xphp-symfony-bundle)[ Packagist](https://packagist.org/packages/xphp-lang/xphp-symfony-bundle)[ Docs](https://github.com/xphp-lang/xphp-symfony-bundle)[ RSS](/packages/xphp-lang-xphp-symfony-bundle/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (16)Versions (4)Used By (0)

XPHP Symfony Bundle
===================

[](#xphp-symfony-bundle)

Write [XPHP](https://github.com/xphp-lang/xphp) — PHP 8.4 with native generics via compile-time monomorphization — inside a Symfony application, and have the auto-generated vanilla PHP produced as part of your normal build/deploy.

The bundle wires xphp's transpiler into the Symfony lifecycle:

- a **`xphp:compile`** console command to compile on demand during development;
- a **cache warmer** so `bin/console cache:warmup` (run by every production build) emits the generated PHP as a deterministic deploy artifact;
- a **runtime autoloader** for the generated `XPHP\Generated\*` classes, so the app boots against fresh output without a `composer dump-autoload`.

Requirements
------------

[](#requirements)

- PHP `^8.4`
- Symfony `^8.0`
- [`xphp-lang/xphp`](https://packagist.org/packages/xphp-lang/xphp) `^0.1` (pulled in automatically)

Installation
------------

[](#installation)

```
composer require xphp-lang/xphp-symfony-bundle
```

Register the bundle (Symfony Flex does this for you):

```
// config/bundles.php
return [
    // ...
    Xphp\SymfonyBundle\XphpBundle::class => ['all' => true],
];
```

Configuration
-------------

[](#configuration)

Defaults (override only what you need):

```
# config/packages/xphp.yaml
xphp:
    # Where your .xphp sources live.
    source: '%kernel.project_dir%/xphp'
    # Rewritten, vanilla .php files (generic call sites replaced).
    target: '%kernel.project_dir%/var/xphp/dist'
    # Generated specialized classes (XPHP\Generated\*).
    cache: '%kernel.project_dir%/var/xphp/generated'
    # Hex length (16-64) of the hash in specialized class names. null = xphp default.
    hash_length: null
    # Compile automatically during cache:warmup (recommended for deploys).
    compile_on_warmup: true
    # Register an SPL autoloader for XPHP\Generated\ at boot.
    register_runtime_autoloader: true
```

These map directly to the upstream CLI: `xphp compile   `.

Layout &amp; autoloading
------------------------

[](#layout--autoloading)

A `.xphp` source uses standard PSR-4 namespace-to-directory mapping. Compilation produces two outputs:

OutputNamespaceGoes toRewritten user codeoriginal (e.g. `App\…`)`target` dirSpecialized classes`XPHP\Generated\…``/Generated/`The bundle autoloads `XPHP\Generated\*` at runtime out of the box. For the **rewritten** code you choose where the application loads `App\` from. For a production deploy, point composer at the compiled output and dump an optimized classmap:

```
// composer.json
"autoload": {
    "psr-4": {
        "App\\": ["src/", "var/xphp/dist/App/"],
        "XPHP\\Generated\\": "var/xphp/generated/Generated/"
    }
}
```

```
composer dump-autoload --optimize --classmap-authoritative
```

When composer owns the classmap, you can set `register_runtime_autoloader: false`.

Usage
-----

[](#usage)

1. Put your generics under the `source` directory, e.g.:

    ```
    xphp/
    └── App/
        └── Collections/
            └── Collection.xphp     # final class Collection { ... }

    ```
2. Compile during development:

    ```
    bin/console xphp:compile
    ```

    …or just warm the cache (also runs the compiler):

    ```
    bin/console cache:warmup
    ```
3. Reference the generated classes from your app as usual; instantiations like `Collection` are monomorphized to concrete classes with native type hints and **zero runtime penalty**.

Generic services (dependency injection)
---------------------------------------

[](#generic-services-dependency-injection)

Service-like generics — `Repository`, `CachedFinder`, `Handler` — can be registered in the container and **autowired**. This is opt-in: declare each instantiation under `xphp.services.bindings`. Value-type generics (`Box`, `Collection`) are not meant for DI — keep `new`-ing those.

```
# config/packages/xphp.yaml
xphp:
  services:
    _defaults:                 # inherited by every binding below
      autowire: true
      autoconfigure: true
      public: false
      shared: true
    bindings:
      - template: 'App\Generic\CachedFinder'
        args: ['App\Entity\User']          # FQN string, or nested { template, args }
        # any of autowire/autoconfigure/public/shared/tags/alias may be set per binding
```

The bundle resolves each binding to the class the transpiler monomorphizes it into (`XPHP\Generated\…\T_`, via the transpiler's own `Registry::generatedFqn`) and registers it as a service under that class name.

**The class that consumes the generic must itself be `.xphp`** — only `.xphp` can name `CachedFinder` as a type. Write that consumer (a service, a command, a controller) in `.xphp`, register it as a service, and inject the generic directly; its compiled constructor type *is* the hash class, so autowiring matches with no seam needed:

```
// src/Command/FindCommand.xphp  (a .xphp class — register it as a service)
#[AsCommand('app:find')]
final class FindCommand extends Command
{
    public function __construct(private readonly CachedFinder $finder) {   // -> the specialization
        parent::__construct();
    }
}
```

(If a specialization *does* implement a stable interface, set `alias:` on the binding to that interface so handwritten `.php` can inject it — but a generic implementing a type-specific interface is usually a smell.)

Declaring a binding makes compilation run at **container-build time** (so the generated classes exist for autowiring/autoconfigure), and the container is rebuilt when any `.xphp` source changes. No separate `xphp:compile` step is needed.

### Two rules for DI-bound generics

[](#two-rules-for-di-bound-generics)

- **Write collaborator types fully-qualified in the template** (no `use`): monomorphization fully-qualifies type *parameters* but copies other type references verbatim, so a short `use`-imported name would resolve into the generated `XPHP\Generated\…` namespace. Write `\App\Finder\SourceInterface`, not a `use`d `SourceInterface`.
- **You don't need a call site.** A generic that's only injected is never written as `new Foo::()` anywhere, yet monomorphization is usage-driven. The bundle bridges this by synthesizing a throwaway instantiation for each declared binding at compile time (transparent; cleaned up afterwards). *(Stopgap: a future xphp release may accept specialization roots directly.)*

> Autowire the specialization (or its alias), not the bare template name — `App\Generic\CachedFinder` compiles to an empty marker interface that every specialization implements, so it is ambiguous across type arguments.

Deploy
------

[](#deploy)

A typical build step needs nothing xphp-specific beyond what you already run:

```
composer install --no-dev --optimize-autoloader
bin/console cache:warmup --env=prod   # compiles .xphp -> var/xphp/*
```

Commit `.xphp` sources; treat `var/xphp/` as generated (gitignored), regenerated on each build. If you prefer to ship precompiled PHP without xphp on the target, run `xphp:compile` in CI and include the `target`/`cache` dirs in the artifact.

The cache warmer is **mandatory and fail-loud**: if compilation fails during `cache:warmup`, the command exits non-zero and the build aborts, so a broken artifact (missing or stale `XPHP\Generated\*` classes) never ships.

### Compiling in a separate step (`compile_on_warmup: false`)

[](#compiling-in-a-separate-step-compile_on_warmup-false)

If you'd rather drive compilation explicitly — e.g. a dedicated CI stage, or to keep `cache:warmup` free of transpilation — turn the warmer off:

```
# config/packages/xphp.yaml
xphp:
    compile_on_warmup: false
```

The bundle then never compiles on its own; **you** own the step and must run it before the app is deployed (and before `composer dump-autoload`, since the optimized classmap is built from the generated output):

```
bin/console xphp:compile --env=prod   # explicit, fails the pipeline on error
composer dump-autoload --optimize --classmap-authoritative
```

`xphp:compile` is itself fail-loud — it exits non-zero on a compilation error — so a CI stage running it will stop the pipeline just like the warmer would.

With the warmer disabled, `cache:warmup` no longer regenerates `var/xphp/`, so make sure your build runs `xphp:compile` on every deploy; otherwise the app boots against whatever output happens to be on disk.

Try the demo
------------

[](#try-the-demo)

A runnable, console-only example app lives in [`demo/`](demo/). It writes `.xphp`generics alongside plain PHP in one `src/`, compiles them through this bundle, and calls the generated code from a Symfony command:

```
cd demo && composer install
bin/console xphp:compile
bin/console app:demo
```

Development
-----------

[](#development)

```
make test            # PHPUnit
make test/mutation   # Infection
```

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance92

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~6 days

Total

3

Last Release

40d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4133b1f33970c9925c7e2219f6d3e76ffa12eae30a4eacdb1b6fbd04e6be99f0?d=identicon)[math3usmartins](/maintainers/math3usmartins)

---

Top Contributors

[![math3usmartins](https://avatars.githubusercontent.com/u/585688?v=4)](https://github.com/math3usmartins "math3usmartins (9 commits)")

---

Tags

symfonybundlegenericsmonomorphizationtranspilerXPHP

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/xphp-lang-xphp-symfony-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/xphp-lang-xphp-symfony-bundle/health.svg)](https://phpackages.com/packages/xphp-lang-xphp-symfony-bundle)
```

PHPackages © 2026

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