PHP Framework • AOP • ObjectQuel ORM • Legacy Compatible

Run Canvas Alongside
Your Legacy Code

Full-featured PHP MVC with annotation-based routing, aspect-oriented programming, and ObjectQuel ORM. Production-ready for greenfield projects. Uniquely equipped for legacy modernization with deterministic fallthrough — Canvas routes first, existing files run unchanged.

🎨

Modernize Without Starting Over

Canvas routes first, falls through to legacy files. One system, two execution paths, zero rewrites required.

Legacy Fallthrough

Canvas checks routes first, then falls through to existing files with deterministic mapping (/usersusers.php). Preserves header(), exit(), direct output — migration blockers solved.

Aspect-Oriented Programming

Extract authentication, logging, validation into reusable aspects. Before/Around/After execution with full inheritance - business logic stays clean.

ObjectQuel ORM

QUEL-inspired queries: range of p is Post, traverse relationships with via, native regex support. Replace raw SQL gradually.

Annotation-Based Routing

Routes live on methods: @Route("/posts/{id:int}"). Built-in parameter validation, HTTP method constraints, no separate config files.

Secure by Default

Built-in CSRF protection, security headers, input hardening. Stop manually adding security checks to every endpoint.

Powerful Inspector

Visual debugging with database queries, request analysis, custom panels. Legacy mysqli/PDO calls auto-instrumented — observability without code changes.

Complete Coexistence Model

Two execution paths in one system — Canvas routes and legacy files run side-by-side with service bridging

Legacy Fallthrough - Use Canvas Features in Existing Files

Step 1: Legacy file keeps working

// legacy/products.php - still works unchanged
session_start();
if (!isset($_SESSION['user']) || 
    $_SESSION['role'] !== 'admin') {
    header('Location: /login');
    exit;
}

$conn = mysqli_connect(/*...*/);
$id = (int)$_GET['id'];
$result = mysqli_query($conn, 
    "SELECT * FROM products WHERE id = $id");
$product = mysqli_fetch_assoc($result);

log_admin_action($_SESSION['user'], 
    'view_product', $id);

include 'templates/product.php';

Step 2: Bridge to Canvas services (no DI required)

// legacy/products.php - modernize piece by piece
session_start();
if (!isset($_SESSION['user']) || 
    $_SESSION['role'] !== 'admin') {
    header('Location: /login');
    exit;
}

// Start using ObjectQuel in legacy file
$em = canvas('EntityManager');
$product = $em->find(
    Product::class, 
    (int)$_GET['id']
);

log_admin_action($_SESSION['user'], 
    'view_product', $product->id);

include 'templates/product.php';

Step 3: Move to controller (one URL at a time, when ready)

/**
 * @InterceptWith(RequireAuthAspect::class)
 * @InterceptWith(RequireAdminAspect::class)
 * @InterceptWith(AuditLogAspect::class)
 */
class ProductController extends BaseController {
    
    /**
     * @Route("/products/{id:int}")
     */
    public function show(int $id) {
        $product = $this->em()->find(Product::class, $id);
        return $this->render('product.tpl', $product);
    }
}

// Canvas handles this route now - legacy file no longer called

Why Canvas Exists

After 25 years modernizing PHP codebases, I kept hitting the same wall: frameworks that force you to rewrite everything or stay stuck in legacy hell. Most frameworks claiming "legacy support" just mean you can require old files somewhere. Canvas defines a complete migration and coexistence model — deterministic routing, side-effect compatibility, service bridging, and automatic instrumentation. No compatibility hacks. No forced rewrites. Just steady improvement.

— Floris, Quellabs

No Stack Changes Required

Canvas doesn't force you to abandon your current tools and setup

Your Template Engine

Canvas ships with Smarty support, but you can use Twig, Blade, or plain PHP templates.

Your Database Structure

No schema changes required. ObjectQuel maps to your existing tables as-is.

Your Deployment Process

Same servers, same deployment pipeline. Just add Canvas via Composer.

🚀

Get Started in Minutes

Install Canvas via Composer and start building immediately

New Project
# Create a new Canvas project with skeleton
composer create-project quellabs/canvas-skeleton my-app
Existing Project
# Add Canvas to existing codebase
composer require quellabs/canvas
View on GitHub
Repository (⭐ 13)
View on Packagist
Package
Documentation
Read Docs

Two Execution Paths, One System

Start modernizing your legacy PHP codebase today.

GitHub Page (⭐ 13) Get Started Now