Zack! requirements are:
Composer --no-dev
requirements are:
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.
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.
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
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',
]);
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!
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);
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!
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',
]);
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);
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:
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.
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:
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.
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.
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.
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.
The generic route handler is a handler that supports the following file types:
---------------------------------------------------
file extension content type
---------------------------------------------------
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.
TBD
Zack! ships with the following events:
Zack! supports the following Symfony HttpKernel events:
Read Built-in Symfony Events for more information.
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
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
Fix coding style issues using PHP-CS-Fixer
./bin/fix-coding-style.sh
Analyse code using PHPStan
./bin/analyse-code.sh
Run functional tests using Hurl
./bin/test-code.sh localhost:9330
Run website tests using Hurl
./bin/test-website.sh localhost:9331