Directory Structure
Understanding ZephyrPHP's directory structure will help you organize your application effectively. Everything has its place.
Project Structure
A fresh ZephyrPHP installation has the following structure:
my-app/
├── app/
│ ├── Controllers/ # HTTP controllers
│ ├── Middleware/ # HTTP middleware
│ └── Models/ # Database models (optional)
├── bootstrap/
│ └── app.php # Bootstrap configuration (optional)
├── config/
│ ├── app.php # Application configuration
│ ├── modules.php # Module enable/disable
│ └── exceptions.php # Custom exception messages (optional)
├── pages/
│ ├── layouts/ # Twig layout templates
│ ├── errors/ # Error pages
│ └── *.twig # View templates
├── public/
│ ├── assets/ # Managed assets
│ │ ├── css/
│ │ └── js/
│ ├── css/ # Static CSS
│ ├── js/ # Static JavaScript
│ ├── .htaccess # Apache rewrite rules
│ └── index.php # Application entry point
├── routes/
│ └── web.php # Web routes
├── storage/
│ ├── cache/ # Application cache
│ ├── logs/ # Log files
│ └── compiled/ # Compiled Twig templates (auto-created)
├── tests/ # PHPUnit tests
├── vendor/ # Composer dependencies
├── .env # Environment configuration
├── .env.example # Environment template
├── composer.json # Project dependencies
├── composer.lock # Locked dependencies
├── craftsman # CLI executable
└── phpunit.xml # PHPUnit configuration
The App Directory
The app/ directory contains the core code of your application. This is where you'll spend most of your time developing.
Controllers (app/Controllers/)
Controllers handle incoming HTTP requests, process data, and return responses. All controllers should extend ZephyrPHP\Core\Controllers\Controller.
<?php
namespace App\Controllers;
use ZephyrPHP\Core\Controllers\Controller;
class HomeController extends Controller
{
public function index()
{
return $this->render('home', [
'title' => 'Welcome to ZephyrPHP'
]);
}
}
The base Controller class provides convenient methods:
render($template, $vars)- Render a Twig templatejson($data, $status)- Return JSON responsexml($data, $status)- Return XML responsehtml($content, $status)- Return HTML responsetext($content, $status)- Return text response
Models (app/Models/)
Models represent your database tables and business logic. This directory is only used if you've installed the zephyrphp/database module.
<?php
namespace App\Models;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 255)]
private string $name;
#[ORM\Column(type: 'string', length: 255, unique: true)]
private string $email;
// Getters and setters...
}
Middleware (app/Middleware/)
Middleware filters or modifies HTTP requests before they reach your controllers. Create custom middleware here.
<?php
namespace App\Middleware;
use ZephyrPHP\Core\Middleware\MiddlewareInterface;
use ZephyrPHP\Core\Http\Request;
use ZephyrPHP\Core\Http\Response;
class AdminMiddleware implements MiddlewareInterface
{
public function handle(Request $request, \Closure $next): Response
{
if (!$request->user()->isAdmin()) {
abort(403, 'Unauthorized');
}
return $next($request);
}
}
The Bootstrap Directory
The bootstrap/ directory contains app.php, which is executed before the application runs. Use it for:
- Registering custom exception handlers
- Configuring error messages
- Setting up application-wide behaviors
<?php
use ZephyrPHP\Core\Application;
use ZephyrPHP\Core\Exceptions\Handler;
return function (Application $app) {
// Register custom exception handler
$app->getHandler()->registerHandler(
\PDOException::class,
function ($e) {
return 'Database connection failed. Please try again later.';
}
);
};
The bootstrap/app.php file is optional. If it doesn't exist, the application will run normally without it.
The Config Directory
The config/ directory contains all your application's configuration files. Each file returns a PHP array.
| File | Purpose | Required |
|---|---|---|
app.php |
Core application settings (name, URL, debug, timezone, locale) | Yes |
modules.php |
Module enable/disable configuration | Yes |
exceptions.php |
Custom exception messages (especially database errors) | No |
See the Configuration documentation for detailed information.
The Pages Directory
The pages/ directory contains your Twig view templates. Organize templates in subdirectories that mirror your controllers.
pages/
├── layouts/
│ └── app.twig # Main layout template
├── errors/
│ ├── 404.twig # Not found page
│ └── 500.twig # Server error page
├── users/
│ ├── index.twig # User list
│ ├── show.twig # User detail
│ └── form.twig # User form
├── welcome.twig # Welcome page
└── home.twig # Home page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}ZephyrPHP{% endblock %}</title>
{{ css('css/styles.css') }}
</head>
<body>
{% block content %}{% endblock %}
{{ js('js/app.js') }}
</body>
</html>
The template directory can be configured with VIEWS_PATH in your .env file (default: /pages).
The Public Directory
The public/ directory is your web server's document root. It's the only directory accessible to the web.
public/
├── assets/ # Managed assets (versioned, CDN)
│ ├── css/
│ └── js/
├── css/ # Static CSS files
│ └── styles.css
├── js/ # Static JavaScript files
│ └── app.js
├── images/ # Static images
├── .htaccess # Apache URL rewriting (auto-created)
└── index.php # Application entry point
Always point your web server's document root to public/, not the project root. This prevents direct access to sensitive files like .env, configuration files, and application code.
Static vs Managed Assets
public/css/andpublic/js/- Static assets accessed directlypublic/assets/- Managed assets with versioning, CDN support, and SRI hashes
The Routes Directory
The routes/ directory contains your route definitions. Routes map URLs to controllers.
| File | Purpose | Middleware |
|---|---|---|
web.php |
Web routes for your application | Session, CSRF protection |
<?php
use ZephyrPHP\Core\Routing\Route;
use App\Controllers\HomeController;
use App\Controllers\UserController;
// Home page
Route::get('/', [new HomeController(), 'index'])->name('home');
// User routes
Route::get('/users', [new UserController(), 'index'])->name('users.index');
Route::get('/users/{id}', [new UserController(), 'show'])->name('users.show');
Route::post('/users', [new UserController(), 'store'])->name('users.store');
The Storage Directory
The storage/ directory stores generated files, logs, and uploads. This directory must be writable by your web server.
storage/
├── cache/ # Application cache
├── logs/ # Application logs
│ └── app.log
└── compiled/ # Compiled Twig templates (auto-created)
Directory Permissions
# Linux/macOS
chmod -R 775 storage
# With www-data user
sudo chown -R www-data:www-data storage
sudo chmod -R 775 storage
The storage/compiled/ directory is created automatically when Twig compiles templates. In production, templates are cached here for performance.
The Tests Directory
The tests/ directory contains your PHPUnit tests. Organize tests to mirror your application structure.
tests/
├── Unit/
│ └── ExampleTest.php
└── Feature/
└── HomeTest.php
The Vendor Directory
The vendor/ directory contains your Composer dependencies, including the ZephyrPHP framework itself.
Never modify files in the vendor/ directory manually. All changes will be lost when you run composer update. This directory is managed by Composer.
Root Files
| File | Purpose | Version Control |
|---|---|---|
.env |
Environment-specific configuration (database, keys, etc.) | ❌ Never commit (in .gitignore) |
.env.example |
Example environment file (template for others) | ✅ Commit this |
.gitignore |
Files to exclude from Git | ✅ Commit this |
composer.json |
PHP dependencies and autoload configuration | ✅ Commit this |
composer.lock |
Locked dependency versions | ✅ Commit this |
craftsman |
CLI executable for code generation and commands | ✅ Commit this |
phpunit.xml |
PHPUnit testing configuration | ✅ Commit this |
Namespace Structure
ZephyrPHP uses PSR-4 autoloading. Your application code uses the App\ namespace:
| Directory | Namespace | Example Class |
|---|---|---|
app/Controllers/ |
App\Controllers |
App\Controllers\UserController |
app/Models/ |
App\Models |
App\Models\User |
app/Middleware/ |
App\Middleware |
App\Middleware\AdminMiddleware |
{
"autoload": {
"psr-4": {
"App\\": "app/",
"ZephyrPHP\\": "vendor/zephyrphp/framework/src/ZephyrPHP/"
}
}
}
Customizing the Structure
While ZephyrPHP provides a conventional structure, you can customize it:
Change Template Directory
# .env
VIEWS_PATH=/resources/views
Add Custom Directories
You can add custom directories for services, repositories, etc.:
app/
├── Controllers/
├── Models/
├── Middleware/
├── Services/ # Business logic
├── Repositories/ # Data access layer
└── Events/ # Event classes
Update your composer.json for PSR-4 autoloading:
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
Then run:
composer dump-autoload
Next Steps
Now that you understand the directory structure:
- Learn about Routing in
routes/web.php - Create Controllers in
app/Controllers/ - Build Views in
pages/ - Understand Configuration in
config/
ZephyrPHP follows "convention over configuration." Following the standard directory structure reduces the amount of configuration needed and makes your code more predictable for other developers.