Modern PHP Framework & ORM

Stop Fighting Your Framework. Start Building Features.

Define routes with annotations. Separate concerns with aspects. Query with plain English. Build what matters.


5min

Setup to First Route

Zero

Config Files Required

100%

Open Source & Free

⚑ Canvas Controller - Everything in One Place
// No separate route files to maintain!
/**
 * @Route("/api/users")
 * @InterceptWith(AuthAspect::class)
 */
class UserController extends BaseController {

    /**
     * @Route("/")
     * @InterceptWith(CacheAspect::class, ttl=300)
     */
    public function index() {
        // Pure business logic - aspects handle auth & caching
        $users = $this->em->findBy(User::class, ['active' => true]);
        return $this->json($users);
    }

    /**
     * @Route("/{id}")
     * @InterceptWith(ValidateAspect::class)
     */
    public function show(int $id) {
        // ObjectQuel makes queries readable
        $user = $this->em->executeQuery("
            range of u is UserEntity
            retrieve (u) where u.id = :id
        ", ['id' => $id]);

        return $this->json($user);
    }
}
hero-image
Our tools

Integrated tools for streamlined PHP development

A unified framework, ORM, and curated toolset designed to work together.

Canvas

Modern PHP framework with annotation-based routing, contextual containers, and aspect-oriented programming.

Annotation-Based Routing

Contextual Containers

Aspect-Oriented Programming

Zero Configuration

ObjectQuel ORM Integration

ObjectQuel

Powerful ORM with Data Mapper pattern, intuitive query language, and lifecycle events.

Data Mapper Architecture

ObjectQuel Query Language

Lifecycle Events & SignalHub

Entity Relationship Management

Migration & Schema Tools

Discover

Lightweight, flexible service discovery component with automatic provider detection and advanced caching.

Framework Agnostic Design

Multiple Discovery Methods

Provider Families & Organization

Advanced Caching System

PSR-4 Utilities

SignalHub

Type-safe signal-slot system for PHP with Qt-inspired design for loose coupling between components.

Type-Safe Signal-Slot System

Flexible Connection Patterns

Signal Discovery & Registry

Object-Owned & Standalone Signals

Priority-Based Execution

Framework

Canvas

A modern, lightweight PHP framework that gets out of your way. Canvas combines annotation-based routing, contextual containers, ObjectQuel ORM, and aspect-oriented programming to create clean, maintainable code. Define routes directly in controllers with @Route annotations, query databases with intuitive ObjectQuel syntax, and add crosscutting concerns like authentication and caching without cluttering business logic.

  • Convention-over-configuration approach

  • Define routes directly in controllers

  • Contextual Containers: Intelligent service resolution based on context

  • Clean separation of crosscutting concerns

  • Automatic Discovery: Add functionality by requiring packages

Example
namespace App\Controller;

use Quellabs\Canvas\Annotations\Route;
use Quellabs\Canvas\Annotations\InterceptWith;
use App\Aspects\RequireAuthAspect;
use App\Aspects\CacheAspect;

/**
* @InterceptWith(RequireAuthAspect::class)
*/
class UserController extends BaseController {

    /**
     * @Route("/users")
     * @InterceptWith(CacheAspect::class, ttl=300)
     */
    public function index() {
        $users = $this->em->findBy(User::class,
            ['active' => true]
        );

        return $this->render('users/index.tpl', compact('users'));
    }
}

ORM

ObjectQuel

ObjectQuel is a powerful Object-Relational Mapping system built on the Data Mapper pattern, offering clean separation between entities and persistence logic. It features a purpose-built query language inspired by QUEL that feels natural to developers, combined with structured data enrichment. It's powered by CakePHP's robust database foundation.

  • Clean separation between entities and persistence logic

  • Intuitive, object-oriented query syntax with pattern matching

  • Comprehensive event system for entity lifecycle management

  • Five relationship types with advanced junction table support

  • Automated entity generation and database migration tools

  • Built on CakePHP's proven database layer for reliability

Example
// Natural, readable queries
$results = $em->executeQuery("
    range of p is ProductEntity
    range of c is CategoryEntity via p.categories
    range of r is ReviewEntity via p.reviews
    retrieve (p, c.name, r.rating)
    where p.price < :maxPrice
        and c.active = true
        and r.rating >= 4
    sort by r.rating desc
", ['maxPrice' => 50.00]);

// Pattern matching
$results = $em->executeQuery("
    range of u is UserEntity
    retrieve (u) where u.email = '*@company.com'
");

// Regular expressions
$results = $em->executeQuery("
    range of p is ProductEntity
    retrieve (p) where p.sku = /^[A-Z]{2}\d{4}$/
");

Tools

Discover

Quellabs Discover automatically discovers service providers across your application and its dependencies with advanced caching and lazy loading capabilities. It focuses solely on locating service providers, giving you complete control over how to use them in your application architecture. Unlike other solutions that force specific patterns, Discover is framework-agnostic and integrates into any PHP application.

  • Works with any PHP application or framework without dependencies

  • Composer configuration, directory scanning, and custom scanners

  • Organize providers into logical groups with family-based filtering

  • Lightning-fast performance with export/import caching capabilities

  • Built-in tools for namespace discovery and class finding

  • Efficient discovery using static methods without instantiation

Example
// Automatic service discovery
$discover = new Discover();
$discover->addScanner(new ComposerScanner());
$discover->addScanner(new DirectoryScanner([
    __DIR__ . '/app/Providers'
]));

// Discover services
$discover->discover();

// Get providers by family
$cacheProviders = $discover
    ->findProvidersByFamily('cache');

$databaseProviders = $discover
    ->findProvidersByFamily('database');

// Lightning-fast caching
$cacheData = $discover->exportForCache();
file_put_contents('cache/providers.json',
    json_encode($cacheData)
);

Tools

SignalHub

SignalHub brings Qt-style signal-slot programming to PHP with strong type checking and flexible connection patterns. It enables loose coupling between components while maintaining type safety through runtime validation. Features both standalone signals and object-owned signals with powerful discovery capabilities through a centralized hub system.

  • Framework-agnostic design works with any PHP application architecture

  • Runtime type checking ensures all signal-slot connections are type compatible

  • Support for direct connections and wildcard pattern matching for flexible event handling

  • Centralized SignalHub for registering, finding, and managing signals across your application

  • Control execution order of connected slots with priority-based handler execution

Example
use Quellabs\SignalHub\SignalHub;
use Quellabs\SignalHub\SignalHubLocator;

// Fetch the signal hub for registration and discovery
$hub = SignalHubLocator::getInstance();

// Create a standalone signal with a string parameter
$buttonClickedSignal = $hub->createSignal('button.clicked', ['string']);

// Connect a handler to the signal
$buttonClickedSignal->connect(function(string $buttonId) {
    echo "Button clicked: {$buttonId}\n";
});

// Emit the signal
$buttonClickedSignal->emit('submit-button');
Framework Comparison

Laravel vs Canvas Approach

See how Canvas simplifies modern PHP development with annotation-based routing and aspect-oriented programming.

πŸ”Ά Laravel Way
// routes/web.php
Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/admin/users', [AdminController::class, 'users'])
         ->middleware('cache:300');
    Route::get('/admin/reports', [AdminController::class, 'reports'])
         ->middleware('cache:3600');
});

// app/Http/Controllers/AdminController.php
class AdminController extends Controller {
    public function __construct() {
        $this->middleware('auth');
        $this->middleware('admin');
    }

    public function users(Request $request) {
        // Manual caching logic
        $users = Cache::remember('admin.users', 300, function () {
            return User::where('active', true)->get();
        });

        return view('admin.users', compact('users'));
    }
}
✨ Canvas Way
// Everything in one place - no separate route files!
/**
 * @InterceptWith(RequireAuthAspect::class)
 * @InterceptWith(RequireAdminAspect::class)
 */
class AdminController extends BaseController {

    /**
     * @Route("/admin/users")
     * @InterceptWith(CacheAspect::class, ttl=300)
     */
    public function users() {
        // Pure business logic - aspects handle everything else
        $users = $this->em->findBy(User::class, ['active' => true]);
        return $this->render('admin/users.tpl', compact('users'));
    }

    /**
     * @Route("/admin/reports")
     * @InterceptWith(CacheAspect::class, ttl=3600)
     */
    public function reports() {
        // Inherits auth + admin, adds longer cache
        return $this->render('admin/reports.tpl');
    }
}
πŸ”Ά Laravel Middleware Chain
// Multiple files to maintain
// app/Http/Middleware/AdminMiddleware.php
class AdminMiddleware {
    public function handle($request, Closure $next) {
        if (!auth()->user()->isAdmin()) {
            return redirect('/login');
        }
        return $next($request);
    }
}

// app/Http/Kernel.php
protected $routeMiddleware = [
    'admin' => \App\Http\Middleware\AdminMiddleware::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
];

// Controller method
public function sensitiveAction(Request $request) {
    // Business logic mixed with concerns
    Log::info('Admin accessed sensitive data', [
        'user_id' => auth()->id()
    ]);

    return $this->processData();
}
✨ Canvas Aspects (AOP)
// Single reusable aspect
class RequireAdminAspect implements BeforeAspect {
    public function before(MethodContext $context): ?Response {
        if (!$this->auth->getCurrentUser()?->isAdmin()) {
            return new RedirectResponse('/login');
        }
        return null;
    }
}

// Clean controller with declarative aspects
/**
 * @InterceptWith(RequireAdminAspect::class)
 * @InterceptWith(AuditLogAspect::class)
 */
class AdminController extends BaseController {

    /**
     * @Route("/admin/sensitive")
     * @InterceptWith(RateLimitAspect::class, limit=5)
     */
    public function sensitiveAction() {
        // Pure business logic - aspects handle everything
        return $this->processData();
    }
}
πŸ”Ά Laravel Eloquent
// Eloquent ORM - Active Record pattern
$posts = Post::with(['author', 'comments' => function ($query) {
             $query->where('approved', true)
                   ->orderBy('created_at', 'desc');
         }])
         ->where('published', true)
         ->where('created_at', '>=', now()->subDays(30))
         ->whereHas('author', function ($query) {
             $query->where('active', true);
         })
         ->orderBy('views', 'desc')
         ->take(10)
         ->get();

// Multiple database queries behind the scenes
// N+1 problems without careful eager loading
✨ Canvas ObjectQuel
// ObjectQuel - Data Mapper with natural syntax
$posts = $this->em->executeQuery("
    range of p is Post
    range of a is Author via p.id
    range of c is Comment via p.id
    retrieve (p, a, c)
    where p.published = true and
          p.created_at >= :monthAgo and
          a.active = true and
          c.approved = true
    sort by p.views desc
    window 0 using window_size 10
", [
    'monthAgo' => new DateTime('-30 days')
]);

// Single optimized query
// No N+1 problems - joins controlled by @RequiredRelation annotations

Why Choose Canvas?

Stop fighting your framework. Start building features.

πŸ“

Routes Live Where They Belong

Define routes directly in controllers with annotations. No separate route files to maintain or hunt through.

🎯

Business Logic Stays Pure

Aspect-oriented programming handles auth, caching, loggingβ€”your controllers focus on what they should: business logic.

πŸ”

Queries Read Like English

ObjectQuel uses natural language syntax instead of complex ORM chains. Write queries that actually make sense.

Ready to simplify your PHP development?

Get Started

Install in Seconds

Zero configuration required. Start building immediately.

1
Canvas
Install
                                composer require quellabs/canvas
                            
2
ObjectQuel
Install
                                composer require quellabs/objectquel
                            
3
Discover
Install
                                composer require quellabs/discover
                            
4
SignalHub
Install
                                composer require quellabs/signal-hub