phastasf/framework

A lightweight PHP framework for building CLI, web and API applications

Installs: 42

Dependents: 1

Suggesters: 0

Security: 0

Stars: 2

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/phastasf/framework


README

Latest Version License PHP Version

A lightweight, modern PHP framework for building CLI, web, and API applications. Built on PSR standards with a clean, intuitive API.

Features

  • PSR Standards: PSR-7, PSR-11, PSR-15, PSR-3, PSR-6, PSR-16, PSR-20
  • Dependency Injection: Automatic dependency resolution
  • Routing & Middleware: Fast routing with PSR-15 middleware pipeline
  • Database & ORM: Multi-database support (MySQL, PostgreSQL, SQLite, SQL Server) with Datum ORM
  • Migrations: Schema versioning with Kram
  • Views: Template engine with layout inheritance
  • Authentication: JWT-based authentication
  • Queue System: Job queues with Redis and ElasticMQ/SQS
  • Caching: Multiple backends (File, Memory, Redis, Predis, Memcache)
  • Logging: Flexible logging with multiple backends
  • Email: SMTP, Mailgun, and Resend transports
  • Validation: Powerful input validation
  • Console Commands: Built-in CLI commands and generators

Requirements

  • PHP 8.2+
  • Composer

Installation

composer require phastasf/framework

Quick Start

Web Application

Create public/index.php:

<?php

require __DIR__.'/../vendor/autoload.php';

define('BASE_PATH', __DIR__.'/..');

$framework = new Phast\Framework;

$framework->getWebEntrypoint()->handle();

Console Application

Create console:

#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';

define('BASE_PATH', __DIR__);

$framework = new Phast\Framework;

exit($framework->getConsoleEntrypoint()->run());
chmod +x console

Configuration

Configuration files are in config/. The framework loads defaults from the package and merges your project's config/ overrides.

Application

// config/app.php
return [
    'debug' => env('APP_DEBUG', false),
    'controllers' => ['namespace' => 'App\\Controllers'],
    'models' => ['namespace' => 'App\\Models'],
    'jobs' => ['namespace' => 'App\\Jobs'],
];

Database

// config/database.php
return [
    'driver' => env('DB_DRIVER', 'mysql'),
    'migrations' => BASE_PATH.'/database/migrations',
    'mysql' => [
        'host' => env('DB_HOST', 'localhost'),
        'database' => env('DB_DATABASE', ''),
        'username' => env('DB_USERNAME', 'root'),
        'password' => env('DB_PASSWORD', ''),
    ],
];

Session

Configure session cookie settings:

// config/session.php
return [
    'cookie' => [
        'name' => env('SESSION_COOKIE', 'PHPSESSID'),
        'lifetime' => (int) env('SESSION_LIFETIME', 7200),
        'path' => env('SESSION_PATH', '/'),
        'domain' => env('SESSION_DOMAIN', null),
        'secure' => env('SESSION_SECURE', false),
        'httponly' => env('SESSION_HTTPONLY', true),
        'samesite' => env('SESSION_SAMESITE', 'Lax'), // 'Strict', 'Lax', or 'None'
    ],
];

Trusted Proxies

When running behind a reverse proxy or load balancer, configure trusted proxies for accurate client IP detection:

// config/proxies.php
return [
    'trusted' => [
        '10.0.0.0/8',      // Private network
        '172.16.0.0/12',  // Docker network
        '192.168.0.0/16', // Private network
        '127.0.0.1',      // Localhost IPv4
        '::1',            // Localhost IPv6
        // Add your production proxy IPs here
        // '203.0.113.0/24', // Your load balancer IP range
    ],
    'headers' => [
        'Forwarded',
        'X-Forwarded-For',
        'X-Real-Ip',
        'Client-Ip',
    ],
];

Routing

// routes/web.php
return function (Router $router) {
    $router->get('/', 'HomeController@index');
    $router->get('/users/{id}', 'UserController@show');
    $router->post('/users', 'UserController@store');
};

Middleware

Middleware is configured in config/middleware.php:

// config/middleware.php
return [
    // Core framework middleware (required)
    \Phast\Middleware\ErrorHandlerMiddleware::class,
    \Phast\Middleware\SessionMiddleware::class,
    // CORS middleware (should be early in pipeline to handle preflight requests)
    \Phast\Middleware\CorsMiddleware::class,
    // Client IP detection middleware (must be before routing if behind proxy)
    // \Phast\Middleware\ClientIpMiddleware::class,
    // Add AuthMiddleware here if you want authentication
    // \Phast\Middleware\AuthMiddleware::class,
    // Add your custom middleware here (before routing)
    \App\Middleware\CustomMiddleware::class,
    \Phast\Middleware\RoutingMiddleware::class,
    \Phast\Middleware\DispatcherMiddleware::class,
];

Generate a new middleware:

php console g:middleware CustomMiddleware

Client IP Detection

When running behind a reverse proxy or load balancer, add ClientIpMiddleware to your middleware stack to correctly detect client IP addresses. The middleware reads trusted proxy configuration from config/proxies.php and extracts the real client IP from proxy headers.

CORS (Cross-Origin Resource Sharing)

Add CorsMiddleware to your middleware stack to handle cross-origin requests. The middleware automatically handles preflight OPTIONS requests and adds appropriate CORS headers to responses.

The middleware is configured via config/cors.php:

// config/cors.php
return [
    // Allowed origins ('*' for all, or array of specific origins)
    // Note: Cannot use '*' when allow_credentials is true
    'allowed_origins' => env('CORS_ALLOWED_ORIGINS', '*'),

    // Allowed HTTP methods
    'allowed_methods' => [
        'GET',
        'POST',
        'PUT',
        'PATCH',
        'DELETE',
        'OPTIONS',
    ],

    // Allowed request headers ('*' for all, or array of specific headers)
    'allowed_headers' => env('CORS_ALLOWED_HEADERS', '*'),

    // Headers that can be exposed to the client
    'exposed_headers' => [],

    // Maximum age (in seconds) for preflight request cache
    'max_age' => (int) env('CORS_MAX_AGE', 86400),

    // Allow credentials (cookies, authorization headers, etc.)
    // Note: When true, allowed_origins cannot be '*'
    'allow_credentials' => (bool) env('CORS_ALLOW_CREDENTIALS', false),

    // Paths/prefixes to include (empty = all paths)
    'include' => [],

    // Paths/prefixes to exclude
    'exclude' => [],
];

Configuration Examples

Allow all origins (development):

'allowed_origins' => '*',
'allow_credentials' => false,

Allow specific origins (production):

'allowed_origins' => [
    'https://example.com',
    'https://www.example.com',
],
'allow_credentials' => true,

Apply CORS only to API routes:

'include' => ['/api'],
'exclude' => [],

Exclude certain paths from CORS:

'include' => [],
'exclude' => ['/admin', '/internal'],

Important Notes

  • When allow_credentials is true, allowed_origins cannot be '*' (must specify exact origins)
  • The middleware automatically handles preflight OPTIONS requests
  • Path matching uses prefix-based matching (paths starting with the pattern)
  • CORS headers are only added when the request includes an Origin header

Service Providers

Service providers are configured in config/providers.php:

// config/providers.php
return [
    // ConfigProvider must be first (other providers depend on it)
    \Phast\Providers\ConfigProvider::class,
    \Phast\Providers\CacheProvider::class,
    \Phast\Providers\DatabaseProvider::class,
    // ... other framework providers
    // Add your custom providers here
    \App\Providers\CustomProvider::class,
];

Generate a new service provider:

php console g:provider CustomProvider

Service providers implement Phast\Providers\ProviderInterface with two methods:

  • provide(Container $container): Register services in the container
  • init(Container $container): Initialize services after all providers are registered
namespace App\Providers;

use Katora\Container;
use Phast\Providers\ProviderInterface;

class CustomProvider implements ProviderInterface
{
    public function provide(Container $container): void
    {
        // Register services here
        $container->set('custom.service', fn() => new CustomService);
    }

    public function init(Container $container): void
    {
        // Initialize services here (called after all providers are registered)
        $service = $container->get('custom.service');
        $service->initialize();
    }
}

Controllers

namespace App\Controllers;

use Phast\Controller;

class HomeController extends Controller
{
    public function index(ServerRequestInterface $request): ResponseInterface
    {
        return $this->render('welcome', ['message' => 'Hello, Phast!']);
    }
}

Models

namespace App\Models;

use Datum\Model;

class User extends Model
{
    protected static ?string $table = 'users';
}

// Usage
$user = User::find(1);
$user = new User;
$user->name = 'John';
$user->save();

Migrations

// Generated migration
class CreateUsersTable implements MigrationInterface
{
    public function up(ConnectionInterface $connection): void
    {
        $connection->execute("CREATE TABLE users (...)");
    }

    public function down(ConnectionInterface $connection): void
    {
        $connection->execute("DROP TABLE users");
    }
}

Seeders

Seeders add fake or test data to the database. You can insert data via the connection (raw) or using models. Dependencies (e.g. ConnectionInterface) are injected via the seeder constructor and resolved by the container. Only the seeders listed in config database.seed are run when you execute the seed command.

// Generated seeder (implements Phast\Database\SeederInterface)
// Dependencies are injected via constructor and resolved by the container.
use Databoss\ConnectionInterface;
use Phast\Database\SeederInterface;

class UserSeeder implements SeederInterface
{
    public function __construct(
        private readonly ConnectionInterface $connection
    ) {}

    public function run(): void
    {
        // Raw connection
        $this->connection->insert('users', ['name' => 'John', 'email' => 'john@example.com']);

        // Or via model
        $user = new \App\Models\User;
        $user->name = 'Jane';
        $user->email = 'jane@example.com';
        $user->save();
    }
}

Configure which seeders run in config/database.php:

'seed' => [
    'DatabaseSeeder',
    'UserSeeder',
],

Queue Jobs

namespace App\Jobs;

use Qatar\Job;

class SendEmailJob extends Job
{
    public function handle(array $payload): void
    {
        // Job logic
    }

    public function retries(): int
    {
        return 3;
    }
}

Console Commands

Generators

  • g:command - Generate console command
  • g:controller - Generate controller
  • g:event - Generate event class
  • g:job - Generate job
  • g:middleware - Generate middleware class
  • g:migration - Generate migration
  • g:model - Generate model
  • g:provider - Generate service provider
  • g:seeder - Generate seeder class

Database

  • m:up - Run pending migrations
  • m:down [count] - Rollback migrations (default: 1)
  • seed - Run database seeders (only those in config database.seed)

Development

  • serve - Start development server
  • worker - Run queue worker
  • shell - Start interactive PHP shell (REPL) with container access
  • uncache - Clear cached config, routes, and application cache

Usage Examples

Caching

$cache = $container->get(CacheInterface::class);
$cache->set('key', 'value', 3600);
$value = $cache->get('key');

Logging

$logger = $container->get(LoggerInterface::class);
$logger->info('User logged in', ['user_id' => 123]);

HTTP Client

The framework includes a PSR-18 compliant HTTP client:

use Psr\Http\Client\ClientInterface;

$client = $container->get(ClientInterface::class);
$request = $requestFactory->createRequest('GET', 'https://api.example.com/data');
$response = $client->sendRequest($request);

Validation

use Filtr\Validator;

$validator = new Validator;
$validator->required('email')->email();
$validator->required('password')->min(8);
$result = $validator->validate($data);

JWT Authentication

// config/auth.php
return [
    'jwt' => [
        'secret' => env('JWT_SECRET'),
        'algorithm' => 'HS256',
    ],
    'middleware' => [
        'include' => ['/api/*'],
        'exclude' => ['/api/auth/*'],
        'required' => true,
    ],
];

Error Handling

The framework includes centralized error handling that:

  • Catches all exceptions
  • Returns JSON for API requests (Accept: application/json)
  • Renders HTML error pages for web requests
  • Logs 5xx errors automatically
  • Shows debug info when app.debug is enabled

License

MIT License - see LICENSE file.

Credits

Built on excellent PSR-compliant libraries: