Oct 31, 2026

phossil Is Now a Composer Package

phossil started as a repository I cloned for every new site. It is now two packages on Packagist: a library that installs into vendor/, and a starter project that sets up everything a site owns with one composer create-project.


Introduction

In Write It in PHP, Ship It as HTML, I introduced phossil, the kit I use to write sites with typed PHP views and components and then crawl them into static HTML. At the end of that article, phossil was a repository I cloned for each new site. That worked, but every site carried its own copy of the framework code, so a fix in one site never reached the others.

I said the next step I was considering was splitting it into a Composer package. That step is done. phossil is now two packages on Packagist:

The first is the library: the router, the components, the build. The second is a starter project you never depend on; it gets copied once to become your site. This article walks through how I split them and what a new site looks like now.

Requirements

When splitting phossil up, I had some requirements for myself:

  1. One command to start a site - a new site should come from a single composer create-project, not a clone followed by a cleanup.
  2. Framework code lives in vendor/ - the router, components and build install as a dependency and update with composer update, so a fix reaches every site.
  3. The site owns the site - routes, views, the layout, the 404 page, the Docker config, the CSS and JS all belong to the site and are never overwritten by an update.

Two packages, two jobs

The split comes down to who owns each file after install.

matthewaprice/phossil is a regular Composer library under the Phossil\ namespace. It holds the code every site shares: the router and RouteType, the View and Component base classes, the kit components (Card, Button, Dialog and friends), optional controllers, the static build, the Vite helper, and the e(), asset() and vite() helpers. A site requires it like any other dependency:

{
    "require": {
        "php": ">=8.4",
        "matthewaprice/phossil": "^0.1"
    }
}

matthewaprice/phossil-starter is a Composer project under the App\ namespace. It is a working sample site: a home page, an about page, an articles section with controllers, a Layout, a NotFound view, the Docker Compose file, Vite and Tailwind config, and PHPUnit tests. Once it is copied, it is yours to change or delete.

I also renamed the site folder from phossil/ to app/ along the way. “phossil” now names the package in vendor/, and a site folder with the same name made the site’s own code look like framework code.

Starting a site

A new site is now one command, plus the same two I already used to get running:

composer create-project matthewaprice/phossil-starter my-site
cd my-site
pnpm install
docker compose up --build

create-project copies the starter into my-site/ and installs the phossil library into vendor/. When it finishes, it asks whether to start a git repository with a first commit, and what to call the default branch (Enter gives main). That question comes from a small Composer script callback that ships with the library:

{
    "scripts": {
        "post-create-project-cmd": [
            "Phossil\\Composer\\CreateProject::offerGitInit"
        ]
    }
}

It only asks when someone is at the keyboard, so --no-interaction and CI skip it, and it never touches a folder that is already a repository. It also checks the branch name against git’s rules and asks again if git would reject it, instead of failing after the fact.

Updating a site

With the framework in vendor/, a fix is a version bump and one command in each site:

composer update matthewaprice/phossil

Conclusion

Looking back at the requirements I set out at the start:

  1. One command to start a site - composer create-project matthewaprice/phossil-starter my-site copies the starter, installs the library, and offers to start the git repository.
  2. Framework code lives in vendor/ - The router, components, controllers and build are the matthewaprice/phossil package, and composer update brings fixes to every site.
  3. The site owns the site - Routes, views, the layout, the 404 page, Docker and the assets all live in app/ under App\, and an update never touches them.

In the last article I asked whether anyone would want phossil as a Composer dependency. It is one now. If you build something with it, or if something in the starter gets in your way, let me know. The full documentation is at phossil.dev, and both packages are on GitHub.


✌️ Matt
Share this