Documentation
- Requirements
- Installation
- Folder Structure
- File-Based Routing
- Route Handler
- Configuration
- Events
- Development Environment
- Testing
#Requirements
Zack! requirements are:
- PHP: 8.2 / 8.3 / 8.4
- Composer: 2.x
Composer --no-dev requirements are:
- symfony/dependency-injection: ^7.2
- symfony/event-dispatcher: ^7.2
- symfony/finder: ^7.2
- symfony/http-foundation: ^7.2
- symfony/http-kernel: ^7.2
- symfony/routing: ^7.2
- twig/markdown-extra: ^3.21
- twig/twig: ^3.20
#Installation
Create a new project folder and change into it.
mkdir myproject
cd myproject
Install Zack! using Composer:
composer require tebe/zack:dev-main
In your myproject folder add the following folders and files:
myproject/
├─ routes/
│ └─ index.get.html
└─ web/
└─ index.php
Add the following content to the files:
routes/index.get.html
<h1>Hello World!</h1>
web/index.php
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
$config = [
'basePath' => dirname(__DIR__),
];
(new tebe\zack\Zack($config))->run();
Start PHP's built-in web server:
cd myproject
php -S localhost:8888 -t web
Open http://localhost:8888 with your preferred web browser.
#Folder Structure
A typical project folder structure looks like the following:
project/ # Project root folder on your server
├─ cache/ # Cached files
├─ config/ # Config files
├─ logs/ # Log files
├─ routes/ # Routes for your website
│ └─ index.get.html # The only route in this example
├─ vendor/ # Composer dependencies
├─ views/ # Twig templates
│ ├─ base.html.twig # Twig base layout file
│ └─ error.html.twig # Twig file for displaying errors
└─ web/ # Web server public folder
├─ assets/ # Asset files like css or js
└─ index.php # Website bootstrap file
Normally you only work in the routes and views folders.
#File-Based Routing
Zack! is using file-based routing for your routes.
Files are automatically mapped to Symfony routes.
Defining a route is as simple as creating a file inside the routes directory.
You can only define one handler per files and you can append the HTTP method to the filename to define a specific request method. If no method is specified, the route applies to all methods.
routes/
├─ api/
│ └─ test.patch.php # PATCH /api/test
├─ index.php # ANY /
├─ contact.get.php # GET /contact
└─ contact.post.php # POST /contact
You can nest routes by creating subdirectories.
routes/
├─ communities/
│ ├─ index.get.php
│ ├─ index.post.php
│ └─ [id]/
│ ├─ index.get.php
│ └─ index.post.php
├─ hello.get.php
└─ hello.post.php
#Simple Routes
First, create a file in routes directory.
The filename will be the route path.
Then, create a file that returns a JSON response. This file will be executed when the route is matched.
#routes/api/ping.php
<?php
use Symfony\Component\HttpFoundation\Response;
return new Response('{"ping": "pong"}', 200, [
'Content-Type' => 'application/json; charset=UTF-8',
]);
#Route With Params
#Single Param
To define a route with params, use the [<param>] syntax where <param> is the name of the param.
The param will be available in the $request->attributes object.
#routes/hello/[name].php
<?php
use Symfony\Component\HttpFoundation\Response;
$name = $request->attributes->get('name');
return new Response('Hello ' . $name . '!', 200);
Call the route with the param /hello/zack, you will get:
#Response
Hello zack!
#Multiple Params
You can define multiple params in a route using [<param1>]/[<param2>] syntax where each param is a folder.
You cannot define multiple params in a single filename of folder.
#routes/hello/[name]/[age].php
<?php
use Symfony\Component\HttpFoundation\Response;
$name = $request->attributes->get('name');
$age = $request->attributes->get('age');
return new Response("Hello $name! You are $age years old.", 200);
#Catch All Params
You can capture all the remaining parts of a URL using [...<param>] syntax. This will include the / in the param.
#routes/hello/[...name].php
<?php
use Symfony\Component\HttpFoundation\Response;
$name = $request->attributes->get('name');
return new Response("Hello $name!", 200);
Call the route with the param /hello/zack/is/nice, you will get:
#Response
Hello zack/is/nice!
#Specific Request Method
You can append the HTTP method to the filename to force the route to be matched only for a specific HTTP request method.
For example hello.get.php will only match for GET requests.
You can use any HTTP method you want.
Example with POST method.
# routes/users.post.php
<?php
use Symfony\Component\HttpFoundation\Response;
// Do something with body like saving it to a database
return new Response('{"updated": true}', 200, [
'Content-Type' => 'application/json; charset=UTF-8',
]);
#Catch All Route
You can create a special route that will match all routes that are not matched by any other route. This is useful for creating a default route.
To create a catch all route, create a file named [...].php in the routes directory.
#routes/[...].php
<?php
use Symfony\Component\HttpFoundation\Response;
$path = $request->attributes->get('path');
return new Response("Hello $path!", 200);
#Route Handler
You can use the file extension of a route file to force the route to be handled by a specific route handler.
routes/
├─ htm-page.htm
├─ html-page.html
├─ json-page.json
├─ md-page.md
├─ markdown-page.markdown
├─ text-page.txt
└─ php-page.php
Zack! is currently delivered with the following route handlers:
#HTML Route Handler
File extensions: htm, html
Response content-type: text/html; charset=UTF-8
The content of the HTML file is taken.
The Twig layout is determined via the layout comment <!-- layout: my-layout.html.twig --> in the HTML content.
The page title is determined by the H1-H3 headings in the HTML content.
The layout is applied and output together with the page title and the HTML content.
#Markdown Route Handler
File extensions: markdown, md
Response content-type: text/html; charset=UTF-8
The content of the Markdown file is taken. The markdown is converted to HTML using one of the following Composer packages:
- league/commonmark
- michelf/php-markdown
- erusev/parsedown
The Twig layout is determined via the layout comment <!-- layout: my-layout.html.twig --> in the HTML content.
The page title is determined by the H1-H3 headings in the HTML content.
The layout is applied and output together with the page title and the HTML content.
#PHP Route Handler
File extension: php
Response content-type: text/html; charset=UTF-8, application/json; charset=UTF-8, or other
The content-type of the response can be set explicitly in a PHP route handler.
#Echoing Content
The echoed content of the PHP file is taken.
If the HTML content contains an html element or a Doctype, the HTML content is output as it is.
Otherwise the Twig layout is determined via the layout comment <!-- layout: my-layout.html.twig --> in the HTML content.
The page title is determined by the H1-H3 headings in the HTML content.
The layout is applied and output together with the page title and the HTML content.
#Returning Response
If you want finer control over the HTTP response, you can return a string, an array or a Symfony\Component\HttpFoundation\Response object.
If the return value is a string, it is output as HTML with the content type text/html; charset=UTF-8.
The same logic is applied as for echoing content.
If the return value is an array, it is JSON encoded and output with the content-type application/json; charset=UTF-8.
If the return value is a Symfony\Component\HttpFoundation\Response object, it is output unchanged together with the underlying content type.
With returning a response object you will have full control over the HTTP response.
There are several response subclasses to help you return JSON, redirect, stream file downloads and more.
#Generic Route Handler
The generic route handler is a handler that supports the following file types:
| Content type | File extension |
|---|---|
| json | application/json; charset=UTF-8 |
| txt | text/plain; charset=UTF-8 |
| xml | application/xml; charset=UTF-8 |
The contents of the file are read and output together with the corresponding content type from the above mapping.
#Configuration
TBD
#Events
#Zack! Events
Zack! ships with the following events:
- zack.container: This event is dispatched after the container has been built.
- zack.controller: This event is dispatched just before the controller (i.e. the route handler) is determined.
- zack.routes: This event is dispatched after the routes have been built.
#HttpKernel Events
Zack! supports the following Symfony HttpKernel events:
- kernel.controller: This event is dispatched very early, before the controller is determined.
- kernel.controller_arguments: This event is dispatched after the controller has been resolved but before executing it.
- kernel.view: This event is dispatched just before a controller is called.
- kernel.response: This event is dispatched after the controller or any kernel.view listener returns a Response object.
- kernel.finish_request: This event is dispatched after the kernel.response event.
- kernel.terminate: This event is dispatched after the response has been sent (after the execution of the handle() method).
- kernel.exception: This event is dispatched as soon as an error occurs during the handling of the HTTP request.
Read Built-in Symfony Events for more information.
#Development Environment
#Create Docker Image
Create Docker image based on the latest supported PHP version
docker build -t zack https://github.com/tbreuss/zack.git
Optionally you can also use an older PHP version
docker build --build-arg PHP_VERSION=8.2 -t zack https://github.com/tbreuss/zack.git
docker build --build-arg PHP_VERSION=8.3 -t zack https://github.com/tbreuss/zack.git
#Run Website
Clone project
git clone https://github.com/tbreuss/zack.git
Change directory
cd zack
Install packages
docker run --rm -it -v .:/app zack composer install
Run website
docker run --rm -v .:/app -p 8888:8888 zack php -S 0.0.0.0:8888 -t /app/website/web
Debug website using Xdebug
docker run --rm -e XDEBUG_CONFIG="client_host=172.17.0.1" -e XDEBUG_MODE=debug -e XDEBUG_SESSION_START=true -v .:/app -p 8888:8888 zack php -S 0.0.0.0:8888 -t /app/website/web
Code Coverage
Start website using Xdebug in coverage mode using phpunit/php-code-coverage
docker run --rm -e XDEBUG_MODE=coverage -p 8888:8888 -v .:/app zack php -S 0.0.0.0:8888 -t /app/website/web
Create HTML report using phpcov
docker run --rm -it -v .:/app zack php vendor/bin/phpcov merge --html /app/.coverage/report /app/.coverage/files
Open generated HTML report in browser
docker run --rm -p 8888:8888 -v .:/app zack php -S 0.0.0.0:8888 -t /app/.coverage/report
#Testing
#Coding Style
Fix coding style issues using PHP-CS-Fixer
./bin/fix-coding-style.sh
#Static Code Analysis
Analyse code using PHPStan
./bin/analyse-code.sh
#Functional Tests
Run functional tests using Hurl
./bin/test-code.sh localhost:9330
#Website Tests
Run website tests using Hurl
./bin/test-website.sh localhost:9331