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.

app/Controllers/HomeController.php
<?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 template
  • json($data, $status) - Return JSON response
  • xml($data, $status) - Return XML response
  • html($content, $status) - Return HTML response
  • text($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.

app/Models/User.php
<?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.

app/Middleware/AdminMiddleware.php
<?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
bootstrap/app.php
<?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.';
        }
    );
};
Optional Bootstrap

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
pages/layouts/app.twig
<!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>
Template Path

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
Security - Web Root

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/ and public/js/ - Static assets accessed directly
  • public/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
routes/web.php
<?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
Compiled Templates

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.

Do Not Modify

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
composer.json (PSR-4 autoloading)
{
    "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:

Conventions

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.