PHP Framework • AOP • ObjectQuel ORM

Build New PHP Apps
With Clear Boundaries

Keep your application architecture clear and predictable. Define routes and shared behavior directly on controllers, keep business logic separate from persistence, and let dependency injection handle the wiring. Build new applications with confidence, or modernize existing PHP projects one route at a time.

Canvas mascot

A Clear Foundation for New PHP Projects

Keep routes, shared concerns, services, and data access easy to find as your application grows.

Controller-Based Routing

Define routes directly on controller methods, keeping endpoints and their handlers together. Typed parameters and HTTP method constraints make routing explicit and easy to follow.

Aspect-Oriented Programming

Define authentication, logging, caching, and other cross-cutting concerns directly on controllers and methods. Aspects are reusable and inherited.

ObjectQuel ORM

Keep entities free from persistence logic with a Data Mapper architecture. Write expressive, QUEL-inspired queries and traverse relationships naturally using via.

Contextual Services

Build against interfaces while Canvas automatically resolves the appropriate implementation for each context.

Secure by Default

Built-in CSRF protection, security headers, and input hardening provide a secure foundation without repetitive security boilerplate.

Powerful Inspector

Inspect requests and database queries while you build. Custom panels help you see what your application is doing.

Start with the architecture you want to keep

A new Canvas application begins with the project skeleton. Add a controller, put its route on the method, and attach shared behavior where it applies. You can introduce ObjectQuel when your application needs persistence. This makes Canvas a practical choice for teams that want explicit controller boundaries and reusable cross-cutting behavior from the first feature.

Build your first app
/**
 * @InterceptWith(RequireAuthAspect::class)
 */
class ProjectController extends BaseController {

    /**
     * @Route("/projects/{id:int}")
     */
    public function show(int $id): Response {
        return new Response("Project $id");
    }
}

Already Have a PHP App?

Canvas routes and existing files can run side by side while you move endpoints at your own pace.

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 building and modernizing PHP applications, I wanted a framework with clear routes, reusable services, and shared behavior that stays out of business logic. Canvas gives new projects that foundation from day one. Its legacy bridge also lets existing applications adopt the same architecture gradually, with deterministic routing and service access from existing files.

— 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, Handlebars, Latte or Plates.

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.

gnome

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 (⭐ 14)

View on Packagist

Package

Documentation

Read Docs

Two Execution Paths, One System

Start modernizing your legacy PHP codebase today.