Staticka
![Software License][ico-license]
![Coverage Status][ico-coverage]
, (*1)
A yet-another PHP-based static site generator. This converts Markdown content and PHP files into static HTML. It is inspired by popular static site generators like Hugo and Jekyll., (*2)
Installation
Installing Staticka
is possible via Composer:, (*3)
``` bash
$ composer require staticka/staticka, (*4)
## Basic Usage
The following guides below showcase the functionalities of `Staticka`. If a specified requirement wants to create a static blog site using a terminal or wants to create pages with a web-based user interface, kindly check [Console](https://github.com/staticka/console) or [Expresso](https://github.com/staticka/expresso) respectively.
### Simple HTML from string
`Staticka` can convert simple Markdown content from string into HTML:
``` php
// index.php
use Staticka\Page;
use Staticka\Parser;
// Creates a new page with the specified body -----
$page = (new Page)->setName('Hello world!');
$page->setBody("# {NAME}\nThis is a sample page.");
// ------------------------------------------------
// Converts the page into an HTML ---
echo (new Parser)->parsePage($page);
// ----------------------------------
``` bash
$ php index.php, (*5)
Hello world!
This is a sample template., (*6)
In the example above, the `{NAME}` is a placeholder to insert the `name` value from the `Page` class to the body.
### Using `.md` files
`Staticka` also supports converting the Markdown-based files (`.md`files) by adding the path of the `.md` file to the `Page` class:
``` md
# Hello World!
This is a sample **Markdown** file!
``` php
// index.php, (*7)
// ..., (*8)
// Specify the path of the Markdown file -----
$file = DIR . '/app/pages/hello-world.md';, (*9)
$page = new Page($file);
// -------------------------------------------, (*10)
// ..., (*11)
``` bash
$ php index.php
Hello World!
This is a sample Markdown file!, (*12)
Adding Front Matter, additional data
Staticka
supports Front Matter in which can add predefined variables in a specific content., (*13)
``` md
, (*14)
link: hello-world
Hello World!
The link is {LINK}., (*15)
``` bash
$ php index.php
Hello World!
The link is hello-world., (*16)
To use the variable inside a content, use the curly brackets ({}
) format (e.g., {NAME}
)., (*17)
Building HTML pages
Multiple Page
classes can be converted into .html
files with their respective directory names using the Site
class:, (*18)
``` php
// index.php, (*19)
// ..., (*20)
use Staticka\Site;, (*21)
// ..., (*22)
// Builds the site with its pages ------------
$site = new Site($parser);, (*23)
$file = DIR . '/app/pages/hello-world.md';
$site->addPage(new Page($file));, (*24)
$site->build(DIR . '/app/web');
// -------------------------------------------, (*25)
``` bash
$ php index.php
``` html
, (*26)
Hello World!
The link is hello-world., (*27)
The `Site` class can also empty a specified directory or copy a directory with its files. This is usable if the output directory needs CSS and JS files:
app/
├─ web/
│ ├─ index.html
styles/
├─ index.css, (*28)
``` php
// ...
// Empty the "output" directory ---
$output = __DIR__ . '/app/web';
$site->emptyDir($output);
// --------------------------------
// Copy the "styles" directory to "output" ---
$styles = __DIR__ . '/styles';
$site->copyDir($styles, $output);
// -------------------------------------------
// ...
Adding data that can be applied to all pages is also possible in the same Site
class:, (*29)
``` php
// ..., (*30)
$data = array('ga_code' => '12345678');
$data['website'] = 'https://roug.in';, (*31)
$site->setData($data);, (*32)
// ..., (*33)
> [!NOTE]
> The data provided in the `Site` class can also be accessed inside a content by using the curly braces format (`{}`).
### Adding template engines
Building HTML pages from Markdown files only returns the content itself. By adding a third-party template engine, it makes it easier to add partials (e.g., layouts) or provide additional styling to each page. To add a template engine, a `Render` class must be used inside the `Parser` class:
``` md
---
name: Hello world!
link: hello-world
plate: main.php
---
# This is a hello world!
The link is **{LINK}**. And this is to get started...
The plate
property specifies the layout file to be used when parsing the page. In this example, the main.php
is the layout to be used in the hello-world.md
file:, (*34)
``` html
, (*35)
, (*36)
> [!NOTE]
> The `$html` variable from the example above is a predefined variable for returning the content from the page that is parsed. The additional data from the `Page` class are also predefined as a variable (e.g., `$link` from `link`, `$name` from `name`).
After creating the template file (e.g., `main.php`), specify the `Parser` class to render templates using the `Render` class:
``` php
// index.php
// ...
use Staticka\Parser;
use Staticka\Render;
// ...
// Path where the "main.php" can be located ---
$path = __DIR__ . '/app/plates';
// --------------------------------------------
// Sets the Render and Parser ---
$render = new Render($path);
$parser = new Parser($render);
// ------------------------------
// Render may be added to Parser after ---
$parser->setRender($render);
// ---------------------------------------
// ...
Then running the script will convert the .md
file to a compiled .html
with the defined template:, (*37)
``` bash
$ php index.php, (*38)
``` html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello world!</title>
</head>
<body>
This is a hello world!
The link is hello-world. And this is to get started..., (*39)
</body>
</html>
To implement a custom template engine to Staticka
, implement the said engine to the RenderInterface
:, (*40)
``` php
namespace Staticka\Render;, (*41)
interface RenderInterface
{
/**
* Renders a file from a specified template.
*
* @param string $name
* @param array<string, mixed> $data
*
* @return string
* @throws \InvalidArgumentException
*/
public function render($name, $data = array());
}, (*42)
### Setting layouts
A `Layout` class allows a `Page` class to use various filters and helpers. It can also be passed as a `class-string` in the `.md` file:
``` php
// index.php
use Staticka\Layout;
// ...
$pages = __DIR__ . '/app/pages';
// Define the layout with the name "main.php" ---
$layout = (new Layout)->setName('main.php');
// ----------------------------------------------
// ...
// Set the layout into the page -------------
$page = new Page($pages . '/hello-world.md');
$site->addPage($page->setLayout($layout));
// ------------------------------------------
[!NOTE]
If a name is specified from the Layout
, there is no need to specify the plate
property from the .md
file., (*43)
It is also possible to specify a Layout
class in the .md
file by specifying it to the plate
property. Using this approach requires a RenderInterface
attached to the Parser
class:, (*44)
``` php
namespace App\Layouts;, (*45)
use Staticka\Layout;, (*46)
class HomeLayout extends Layout
{
/**
* Specifies the plate to be used as the layout.
*
* @var string
*/
protected $name = 'home.php';
}, (*47)
``` md
---
name: Hello world!
link: hello-world
plate: App\Layouts\HomeLayout
---
# This is a hello world!
The link is **{LINK}**. And this is to get started...
If the Layout
class requires complex dependencies, the Parser
class must specify a container to easily identify the specified layout instance:, (*48)
``` php
namespace App\Layouts;, (*49)
use App\Complex;
use Staticka\Layout;, (*50)
class ComplexLayout extends Layout
{
protected $complex;, (*51)
public function (Complex $complex)
{
$this->complex = $complex;
}
// ...
}, (*52)
``` php
// index.php
use App\Layouts\ComplexLayout;
use Rougin\Slytherin\Container\Container;
use App\Complex;
// ...
// Define the complex layout... ---------
$layout = new ComplexLayout(new Complex);
// --------------------------------------
// ...then add it to the container... ---
$container = new Container;
$name = ComplexLayout::class;
$container->set($name, $layout);
// --------------------------------------
// ...
// ...and set the container to the parser ---
$parser->setContainer($container);
// ------------------------------------------
// ...
[!NOTE]
If the container is not specified, the ReflectionContainer
from Slytherin is initialized by default., (*53)
Extending and customization
With the philosophy of Staticka
to be an extensible and scalable static site generator, the following guides below are use-cases for Staticka
turning it into a content management system (CMS) or a fully functional static blog website:, (*54)
Modifying with filters
A Filter
allows a page to be modified after being parsed:, (*55)
``` php
// index.php, (*56)
use Staticka\Filter\HtmlMinifier;, (*57)
// ..., (*58)
// Set the layout class for "main.php" ---
$layout = new Layout;, (*59)
$layout->setName('main.php');
// ---------------------------------------, (*60)
// Minifies the HTML after parsing the page ---
$layout->addFilter(new HtmlMinifier);
// --------------------------------------------, (*61)
// ..., (*62)
``` bash
$ php index.php
``` html
, (*63)
, (*64)
Hello world!
This is a hello world!
The link is hello-world. And this is to get started..., (*65)
, (*66)
To create a custom filter, implement it using the `FilterInterface`:
``` php
namespace Staticka\Filter;
interface FilterInterface
{
/**
* Filters the specified code.
*
* @param string $code
*
* @return string
*/
public function filter($code);
}
[!TIP]
Please see FILTERS page for showcasing the list of available filters., (*67)
Custom methods using helpers
A Helper
provides additional methods inside template files:, (*68)
``` php
// index.php, (*69)
use Staticka\Helper\LinkHelper;, (*70)
// ..., (*71)
// Set the layout class for "main.php" ---
$layout = new Layout;, (*72)
$layout->setName('main.php');
// ---------------------------------------, (*73)
// Add a "$url" variable in templates ---
$url = new LinkHelper('https://roug.in');, (*74)
$layout->addHelper($url);
// --------------------------------------, (*75)
// ..., (*76)
``` html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<a href="<?php echo $url->set($link); ?>"></a>
</body>
</html>
``` bash
$ php index.php, (*77)
``` html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello world!</title>
</head>
<body>
This is a hello world!
The link is hello-world. And this is to get started..., (*78)
<a href="https://roug.in/hello-world">Hello world!</a>
</body>
</html>
To create a template helper, implement the said code in HelperInterface
:, (*79)
``` php
namespace Staticka\Helper;, (*80)
interface HelperInterface
{
/**
* Returns the name of the helper.
*
* @return string
*/
public function name();
}, (*81)
> [!TIP]
> Check the [HELPERS][link-helpers] page for a list of available helpers.
### Adding filters to Parser
By design, the filters under `FilterInterface` should be executed after parsing is completed by Parser. However, there may be scenarios that the body of a page must undergo a filter prior to its parsing process:
``` php
namespace App\Filters;
use Staticka\Filter\FilterInterface;
class WorldFilter implements FilterInterface
{
public function filter($code)
{
return str_replace('Hello', 'World', $code);
}
}
``` php
// index.php, (*82)
use App\Filters\WorldFilter;, (*83)
// ..., (*84)
// Replaces "Hello" string to "World" ---
$parser->addFilter(new WorldFilter);
// --------------------------------------, (*85)
// ..., (*86)
### Using the `Package` class
There may be a requirement wherein `Staticka` must be built to any PHP application such as a static blog platform or a hybrid content management system. To achieve this, the `Package` and `System` classes may be used for the specified requirement:
``` php
// index.php
use Rougin\Slytherin\Container\Container;
use Rougin\Slytherin\Integration\Configuration;
use Staticka\Package;
// Create paths such as "pages", "plates", and ---
// "build" from "__DIR__ . '/app'" directory -----
$root = __DIR__ . '/app';
$app = new Package($root);
$app->setPathsFromRooth();
// -----------------------------------------------
// Return the "System" instance from "Package" ---
$container = new Container;
$config = new Configuration;
$result = $app->define($container, $config);
/** @var \Staticka\System */
$app = $result->get(System::class);
// -----------------------------------------------
For more information on how to implement this for new ideas and projects, kindly see the codebase of Console and Expresso respectively for their implementations., (*87)
Using the PageDepot
class
Staticka
also contains a PageDepot
class for providing CRUD operations for pages:, (*88)
``` php
// index.php, (*89)
use Staticka\Depots\PageDepot;, (*90)
// ..., (*91)
/** @var \Staticka\System $app */
$depot = new PageDepot($app);, (*92)
The specified depot has a functionality to create a new page based on provided payload:
``` php
// index.php
// ...
$data = array('name' => 'Hello!');
$data['link'] = '/hello';
/** @var \Staticka\Page */
$page = $depot->create($data);
// Return the unix timestamp as its identifier ---
$id = $page->getId();
// -----------------------------------------------
[!NOTE]
A page's identifier is specified by its timestamp (e.g., 20240101000000_hello.md
returns a 1704067200
as its identifier)., (*93)
After the new page is created successfully, it can now be updated its details using update
:, (*94)
``` php
// index.php, (*95)
// ..., (*96)
// Return the "/hello" link ---
echo $page->getLink();
// ----------------------------, (*97)
$data = array('link' => '/hello-world');, (*98)
/** @var \Staticka\Page */
$page = $depot->update($id, $data);, (*99)
// Return the "/hello-world" link ---
echo $page->getLink();
// ----------------------------------, (*100)
To delete a specified page, the `delete` method can be used:
``` php
// index.php
// Will delete the file specified in the page ---
$depot->delete($id);
// ----------------------------------------------
Finding a specified page is also possible by its identifier, by link, or by name:, (*101)
``` php
// index.php, (*102)
// The page can be found using its ID... ---
$page = $depot->find(1704067200);
// -----------------------------------------, (*103)
// ...or by the page's link... ------
$page = $depot->findByLink('/hello');
// ----------------------------------, (*104)
// ...or by its page name -----------
$page = $depot->findByName('Hello!');
// ----------------------------------, (*105)
`PageDepot` can also return the available pages from its source directory:
``` php
// index.php
/** @var \Staticka\Page[] */
$pages = $depot->get();
// Can also return pages as data ---
$items = $depot->getAsData();
// ---------------------------------
[!NOTE]
The word Depot
is only a preference and it is best known as the Repository pattern., (*106)
Migrating to the v0.4.0
release
The new release for v0.4.0
will be having a backward compatibility break (BC break). With this, some functionalities from the earlier versions might not be working after upgrading. This was done to increase extensibility, simplicity and maintainbility. Staticka
is also mentioned in my blog post:, (*107)
I also want to extend this plan to my personal packages as well like Staticka and Transcribe. With this, I will introduce backward compatibility breaks to them initially as it is hard to migrate their codebase due to minimal to no documentation being provided in its basic usage and its internals. As I checked their code, I realized that they are also over engineered, which is a mistake that I needed to atone for when updating my packages in the future., (*108)
Please see Pull Request #5 for the files that were removed or updated in this release and the UPGRADING page for the specified breaking changes., (*109)
Changelog
Please see CHANGELOG for more information what has changed recently., (*110)
Testing
The unit tests of Staticka
can be run using the test
command:, (*111)
bash
$ composer test
, (*112)
Credits
License
The MIT License (MIT). Please see LICENSE for more information., (*113)