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:
- matthewaprice/phossil - “Routing, typed views and components for PHP sites that build to static HTML.”
- matthewaprice/phossil-starter - “Starter site for phossil: routes, typed views and components that build to plain static HTML.”
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:
-
One command to start a site - a new site should
come from a single
composer create-project, not a clone followed by a cleanup. -
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. - 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:
-
One command to start a site -
composer create-project matthewaprice/phossil-starter my-sitecopies the starter, installs the library, and offers to start the git repository. -
Framework code lives in vendor/ - The router,
components, controllers and build are the
matthewaprice/phossilpackage, andcomposer updatebrings fixes to every site. -
The site owns the site - Routes, views, the
layout, the 404 page, Docker and the assets all live in
app/underApp\, 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