Introduction to the internal architecture of HydePHP.
Hey! My name is Caen. I am the creator of HydePHP - a new static app generator. I thought it could be useful to shed some light on the internal architecture of HydePHP and the design patterns I've used.
While this is not at all required reading for those wanting to try out Hyde, it might be interesting for those curious about the "magic" behind the framework. And of course to those who are interested in contributing to the codebase.
This article is going to be split into a few posts. This one will give a general broad overview, while I'll go into further detail on some key concepts in new posts over the next few days.
Note that Hyde is still in beta and stuff may change without notice. This post was written after the 0.24.0-beta release, thus links to source code files in examples will be pinned to that version.
Primer
Here's a refresher on Hyde:
- HydePHP is a static site generator written in PHP and is based on Laravel Zero.
- Hyde is different from many other SSGs as it offers a complete full-stack experience and has a built-in frontend.
- The key principle of Hyde is to keep things seriously simple to start with while offering the power to do anything you need it to.
The Components
Hyde is split into a few components:
HydePHP/Framework - The core project containing the brains of Hyde and is not intended to be used as a standalone. Equivalent to Laravel/Framework.
HydePHP/HydeFront - The Hyde frontend assets. Extracted to improve testing, versioning and CDN deployment.
HydePHP/Hyde - The full-stack Hyde repository acts like the glue holding everything together. This is what's used when creating a new Hyde project using Composer. Equivalent to Laravel/Laravel.
Since the Framework package is what holds the core logic, it's what this article will focus on.
The Namespace Patterns
I like code that describes what it does. That's why Hyde's core components are split into namespaces depending on what they are for.
Here is a reference over the namespaces. I'll go into detail about them in the next section.
-
Actions
-
Services
-
Models
-
Concerns
-
Contracts
Actions
Actions in Hyde are self-contained pieces of logic that are used to perform a specific singular task. They are often used to offload heavy lifting in Services. Actions are often named in a way that explains what they do. Here are some examples:
- ConvertsArrayToFrontMatter, which converts an array to a front matter string.
- GeneratesTableOfContents, which generates a table of contents from a markdown string.
- You get the idea...
Services
Services (not to be confused with ServiceProviders
) are classes containing logic for a certain feature or concept.
They are often used similarly to Laravel facades, grouping related logic related to a concept into a single interface.
Some examples of Hyde Services:
- AssetService, takes care of injecting the correct assets into the front-end.
- CollectionService, which contains helpers for working with lists and arrays.
- BuildService, which contains helpers used by static site generating components.
Deep Dive into Services
To learn more about how Services are used, take a look at the Deep Dive into Services post (launching tomorrow).
Models
At their core, Models are just PHP classes that house data defined in the class schema. In general, Models contain very little logic.* Models used to generate pages also contains instructions on what parsers to use and where the source files are stored.
*There are exceptions. For example, specialized Models that don't warrant having an entire Service, such as the
DocumentationSidebarItem
model which contains its own parsing logic.
Unlike Eloquent models in Laravel, these of course are not connected to any database. Instead, they are created on-demand when needed, often in a Service either directly or through an Action.
There are two main types of Models: General Models and Page Models. We'll take a quick look at the first one first, to get it out of the way.
General Models
These are mainly used to have a clearly defined schema for the data they contain to ensure a predictable and consistent data structure.
For example, here is the Author
model, which contains the data for an Author of a blog post.
class Author // (Stripped of content for brevity and illustration purposes){ public string $username; public ?string $name = null; public ?string $website = null; public function __construct(string $username, ?array $data = [])}
Page Models
Page Models are used to store the data for a static page type.
Note that Blade Pages are a bit different. This section covers the Markdown-based pages.
When running the static site builder, Hyde compiles each source directory at a time. The source directory a file is stored in determines what type of page it is, and subsequently which Page Model to use.
There are currently three supported Markdown page types:
- Markdown Posts
- Markdown Pages
- Documentation Pages
All of these extend the MarkdownDocument
base Model which provides common data for all Markdown pages, such as front matter, Markdown content, etc.
How page models are used to generate static pages
The page models are an integral of the static site building process.
To learn more about how the page models are used, take a look at the How Hyde knows what to do with your Markdown post.
Contracts & Concerns
These are two more namespaces that are used to provide additional functionality to the Hyde Core.
Contracts
Contracts are simply PHP Interfaces that are used to define what methods a class must implement. The namespace also includes a few Abstract Classes to define what properties a class must have.
You can tell the type of a contract by its naming convention:
- Contracts are suffixed with
Contract
. For example:ActionContract
. - Abstract Class Contracts are prefixed with
Abstract
. For example:AbstractPage
.
Concerns
Concerns are simply PHP Traits that are used to provide additional functionality to the Hyde Core.
Data Concerns
When a Concern Trait primarily adds data to a class it usually is prefixed with Has
.
For example: HasAuthor
, HasMetadata
, etc. Though I should mention that these traits often have logic pertaining to generating, parsing, and modifying the data type it adds.
For example, here is a concern that creates and stores a DateString
model from a Page model's front matter.
trait HasDateString{ public ?DateString $date = null; public function constructDateString(): void { if (isset($this->matter['date'])) { $this->date = new DateString($this->matter['date']); } }}
Logic/Action Concerns
These are similar to Actions
but whereas Actions are often part of Services
(eg. as ServiceActions), Logic and Action Concerns are Traits that perform logic that can be used in many unrelated places.
For example, ValidatesExistence
can be used to validate that a file exists. AsksToRebuildSite
can be used to ask the user if they want to rebuild the site when running a HydeCLI command.
Some internal concerns like the BuildActionRunner
may also be used to simply offload logic to extend a larger Service class while maintaining the same object interface. This also makes granular testing easier as you can mock the logic you want to test.
Conclusion
I hope you enjoyed this article and that you found it useful. This is my first time writing something so technical, and I imagine I've made some mistakes. If you spot any, please let me know so I can learn!
Next Up
Make sure to check out the Deep Dive into Services post. Also, make sure to check out the How Hyde knows what to do with your Markdown post.
Syntax highlighting by Torchlight.dev