定制 taujor/phpssg 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

taujor/phpssg

最新稳定版本:1.1.1

Composer 安装命令:

composer require taujor/phpssg

包简介

A lightweight static site generator, utilizing composable templates in pure PHP. This project uses a presenter -> view invokable component classes pattern to provide clean, composable, vanilla, php developer experience without a third-party templating engine.

README 文档

README

💖 PHPSSG is open source and free.
If you find it useful, please consider sponsoring me to support continued development.

PHPSSG (Personal Home Page Static Site Generator)

A lightweight, PHP-native static site generator for building composable templates.
PHPSSG uses invokable component classes, output buffering, and plain PHP templates to provide a clean developer experience — no third-party templating engine required, but you are welcome to use one.

Features

  • Plain PHP templates – no special syntax to learn.
  • Invokable components – use components like $header().
  • Component based routingBuildable components get methods able to write the html they generate to a file.
  • Hooks – Hooks are available to easily inject your own custom code to manipulate content during the component lifecycle.
  • Centralized render helper – avoids repeated ob_start() / ob_get_clean().
  • Nesting & composition – layouts can include multiple components.
  • PHAR Support – easily ran as a phar if desired.
  • Slots & data passing – inject content into templates easily.
  • Incremental builds – files are rewritten only if content changes (xxh3 hash comparison).
  • Caching – file paths and contents are hashed and cached to disk prioritizing thread safety.
  • Native debugging – works seamlessly with standard PHP tools.
  • PSR-4 compliant – fully autoloadable via Composer.
  • Abstract ClassesRenderable, Composable, Buildable help define component APIs, they are fundamental to PHPSSG.
  • IDE-friendly – docblocks provide autocomplete, type hints, and method signatures.
  • Highly portable – works in any PHP 8.1+ environment.
  • Flexible structure – There are no rules that force you to structure your project a certain way, I have made suggestions and set some reconfigurable defaults but beyond that I leave everything completely up to you.
  • Fast content comparison – via xxh3 hashing.

Requirements

  • PHP 8.1+ (xxh3 hashing)
  • Composer (autoloading)
  • PHP-DI (dependency injection)

Quick start

Clone the skeleton template repo or visit the repo and click "Use this template"

git clone https://github.com/Taujor/php-static-site-generator-skeleton

Installation

Install via Composer:

composer require taujor/phpssg

Run a local development server:

php -S localhost:8080 public/index.html

Usage

Layouts

Layouts are typically Renderables:

// src/presenters/layouts/Base.php
use Taujor\PHPSSG\Contracts\Renderable;

class Base extends Renderable
{
    public function __invoke(string $content): string
    {
        // the directory specified here is relative to your views directory ("src/views" by default)
        return $this->render("layouts/base", ["content" => $content]);
    }
}

Components

Components are typically Renderables (like layouts) or Composables :

// src/presenters/components/Heading.php
use Taujor\PHPSSG\Contracts\Composable;

class Heading extends Composable {
    function __construct(private Title $title, private Subtitle $subtitle) {}
    function __invoke(): string {
        return ($this->title)() . ($this->subtitle)();
    }
}

Title and Subtitle are Renderables (in this example) they have their own respective template files, however our Heading class simply takes these two components and "composes" them into a new component (via string concatenation in this case). Hence it does not require its own template file but can still be invoked just the same as any other presenter.

<!-- src/views/layouts/base.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <?= $content ?>
</body>
</html>

The render method of Renderable extracts variables into the template and returns the output as a string.

Pages

Pages are typically Buildables, like Composables they are self-contained, and do not need separate view templates. They inherit the compile and build methods which output html files to the build directory (public by default):

// src/presenters/pages/Post.php
use Taujor\PHPSSG\Contracts\Buildable;

class Post extends Buildable
{
    public function __construct(private Base $base, private Title $title, private Body $body) {}

    public function __invoke(object $data): string
    {
        return ($this->base)(
            ($this->title)($data->title) . ($this->body)($data->content)
        );
    }
}

Pages often combine components and layouts. The $data argument is passed to the page during the build process. $data is then passed to components via their respective __invoke() methods. Then finally the layout wraps the combined HTML content.

Utilities

Utilities are typically helper classes that provide additional functionality to phpssg. You can use utility classes to implement things like alternative renderers (twig for example), provide custom methods to your components, etc.

// src/utilities/Locate.php
<?php namespace Taujor\PHPSSG\Utilities;

use Phar;
use Composer\Factory;

class Locate {
    private static ?string $root = null;

    public static function root(): string {
        if(self::$root === null) Phar::running() ? self::$root = getcwd() : self::$root = dirname(Factory::getComposerFile());
        return self::$root;
    }
}

This utility class can be used to locate the root of a composer project, this example is derrived from the Locate utility class used throughout this project, see the "build scripts" section for details on how Locate is used for configuration.

You can even extend an abstract class such as Renderable to support entirely different templating systems such as twig or markdown and using your extended class in your components instead of the defaults. In a future release there will be separate offically maintained integrations you can install via composer that will extend PHPSSG's core features this way, but don't let that stop you from creating your own that are specfic to your individual project.

Views

Stored in src/views (by default). Only Renderables have view templates. Composables and Buildables don't require view templates. Views are plain php templates, I recommend their directory structure mirrors that of the Renderables in your presenters/ directory for example:

src/
└── presenters/
    ├── components/
    │   └── Title.php
    ├── layouts/
    │   └── Base.php
    ├── pages/
    │   └── Home.php
    views/
    ├── components/
    │   └── title.php
    └── layouts/
        └── base.php

Hooks

Hooks provide lifecycle entry points for customizing the behavior of your components.
They are defined in the base Composable, Renderable, and Buildable contracts, and are automatically called at key points in the rendering or build process.

Hooks are optional — override only those you need.
They are always protected and are intended for internal subclass use only.

Composable Hooks

Composable components are pure PHP objects that produce HTML by combining other components.

Hook Signature Called When Common Use Cases
_beforeInvoke() void When the component is instantiated Set up shared state, inject defaults, or run dependency checks before the component is first used.

Example:

class Hero extends Composable {
    protected function _beforeInvoke(): void {
        $this->timestamp = time();
    }

    public function __invoke(): string {
        return "<p>Generated at {$this->timestamp}</p>";
    }
}

Renderable Hooks

Renderable components generate HTML from PHP view templates.

Hook Signature Called When Common Use Cases
_beforeInvoke() void On component instantiation Initialize configurations, register filters, or set default view variables.
_beforeExtract() (array &$data, string &$path) Before variables are extracted into the template Sanitize or transform $data, or modify $path to dynamically choose a template.
_afterRender() (array &$data, string &$html) After the template has been rendered Perform post-processing (minification, analytics injection, DOM mutation, etc.).

Example:

class Title extends Renderable {
    protected function _beforeExtract(array &$data, string &$path): void {
        $data['text'] = htmlspecialchars($data['text']);
    }

    protected function _afterRender(array &$data, string &$html): void {
        $html = trim($html) . "\n";
    }

    public function __invoke(string $text): string {
        return $this->render('components/title', ['text' => $text]);
    }
}

Buildable Hooks

Buildable components handle static file generation and support a full pre/post lifecycle around rendering and writing output.

Hook Signature Called When Common Use Cases
_beforeInvoke() void When the component is instantiated Initialize caches or configuration before any builds begin.
_beforeRender() `(array object &$data)` Before rendering HTML
_afterRender() `(array object &$data, string &$html)` After HTML is generated
_beforeWrite() `(array object &$data, string &$file)` Before writing to disk
_afterWrite() `(string &$file, int false &$bytes)` After the file has been written

Example:

class Post extends Buildable {
    protected function _beforeRender(array|object &$data): void {
        $data->slug = strtolower(str_replace(' ', '-', $data->title));
    }

    protected function _afterRender(array|object &$data, string &$html): void {
        $html .= "\n<!-- Built on " . date('c') . " -->";
    }

    protected function _afterWrite(string &$file, int|false &$bytes): void {
        echo "✅ Wrote {$bytes} bytes to $file\n";
    }

    public function __invoke(array|object $data): string {
        return "<h1>{$data->title}</h1><p>{$data->content}</p>";
    }
}

Build scripts

This is the entry point of the application often placed in the scripts directory at the root of your project. They call on Buildable presenters to generate html using the compile or build static methods. The compile method takes a target path relative to your build directory, the generated html will be sent to this path. The second parameter supports either an array or object containing data you would like to __invoke the Buildable with. The build method simply iterates over your dataset and runs compile on each item. You can also use placeholder syntax to use any top level value in your dataset to generate unique filenames. For example:

<?php

require dirname(__DIR__) . "/vendor/autoload.php";

use Presenters\Pages\Post;

$data = [
    (object) [
        'id' => 1,
        'slug' => 'first-post',
        'title' => 'First Post',
        'content' => 'Hello'
    ],
    (object) [
        'id' => 2,
        'slug' => 'second-post',
        'title' => 'Second Post',
        'content' => 'World'
    ],
];

// {{}} are the default delimiters for placeholders you can set any delimiters you like separated by whitespace
// creates two files in the build directory "posts/post-1.html" and "posts/post-2.html"
Post::build("/posts/post-{{id}}.html", $data, "{{ }}"); 

// compile also supports placeholders
Post::compile("/posts/post-3.html", (object)[
    'id' => 3,
    'slug' => 'third-post',
    'title' => 'Third Post',
    'content' => 'Hello Again'
]);

Project Structure

There are no mandated directory names or conventions, you can decide how you like to structure your project. However I have provided some sane default directories you can see in the next section that will work with zero configuration.

Suggested Structure

There are some directories PHPSSG needs to have around, those being a views, build, and cache directory (marked as REQUIRED). You are free to reconfigure them as needed but if they are missing the project will not work as expected. The cache and it's sub-directories are automatically created during runtime if they don't already exist.

  • config/ – bootstrap and environment setup.
  • public/REQUIRED build directory, CSS, JS, and other web-facing assets.
  • scripts/ – build scripts and/or other tooling.
  • presenters/ – invokable PHP classes for logic (can be Buildable, Composable, or Renderable).
  • presenters/components/ – reusable UI blocks typically Renderables or Composables.
  • presenters/layouts/ – reusable page skeletons typically Renderables.
  • presenters/pages/ – page-level composables or buildables.
  • views/REQUIRED plain php templates.
  • views/components/ – templates for Renderable components.
  • views/layout/ – templates for Renderable layouts.
  • utilities/ – helpers like Locate, TwigRenderer, PrettyPrint. Make them do anything!

Configuration

At the entry point of the PHPSSG application (commonly the build script) you can use the Locate class to tinker with your project layout.

<?php

require dirname(__DIR__) . "/vendor/autoload.php";

use Presenters\Pages\Post;

$rootDir = Locate::root(__DIR__);
$viewsDir = Locate::views("/src/custom_views"); // relative to root - returns __DIR__ . "/src/custom_views"
$cacheDir = Locate::cache("/custom_cache"); // relative to root - returns "/src/custom_views/custom_cache"
$hashDir = Locate::hashes("/custom_hashes"); // relative to cache - returns "/src/custom_views/custom_cache/custom_hashes"
$proxyDir = Locate::proxies("/custom_proxies"); // relative to cache - returns "/src/custom_views/custom_cache/custom_proxies"
$buildDir = Locate::build("/custom_build"); // relative to root - returns __DIR__ . "/src/custom_views"
$templateExt = Locate::engine(".twig"); // configures the file extenstion expected by the render method of Renderable

Post::compile("/posts/post-1.html", (object)[
    "id" => 1,
    "slug" => "first-post",
    "title" => "First Post",
    "content" => "Hello World"
]); 

As you can see above the Locate utility class provides static methods that can both override and return the configured location. Once an override is set further overrides will be ignored for the duration of the runtime, you can bypass this using Locate::reset() which will revert all changes to default.

Locate is used througout PHPSSG to get configuration data, it works lazily, inteligently setting defaults the first time it is called and caching them in memory, subsequent calls always return the cached location rather than whatever is in the $override argument or expected by default. Calling Locate::reset() clears this cache, setting each static variable to null. Then when a method of Locate is called again it will either cache and return it's defaults or use the supplied override.

Here is a list of Locate's default settings:

Default Configuration

Method Default Path Relative To
root() (project root)
views() /src/views root()
cache() /cache root()
hashes() /hashes cache()
proxies() /proxies cache()
build() /public root()
engine() .php

Contributing

Contributions are welcome! Philosophy:

  • Minimalism First – lightweight and simple, avoid heavy libraries.
  • Developer-Friendly – components should be easy to understand and compose.
  • Consistency – follow presenters/, views/, utilities/ structure.
  • Backward Compatibility – avoid breaking APIs unless clearly beneficial.

Planned Features

  • Packagist Release - use composer to install phpssg with ease.
  • Hooks - add extensibility to the build process.
  • Caching – reduce build times for large projects.
  • Templates – premade templates to start projects quickly.
  • Documentation Website – phpssg.com for guides and community resources.
  • Tutorials – step-by-step guides on using PHPSSG effectively.

统计信息

  • 总下载量: 32
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 59
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 59
  • Watchers: 2
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-10-12