PHP Framework • AOP • ObjectQuel ORM

Build New PHP Apps
With Clear Boundaries

Start a new project with routes defined on controllers, reusable aspects for authentication and other shared concerns, contextual dependency injection, and expressive ObjectQuel queries. Canvas also lets existing PHP apps adopt the same architecture 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 beside the controller methods that handle them. Typed parameters and HTTP method constraints make each endpoint's contract visible.

Aspect-Oriented Programming

Declare reusable authentication, audit logging, or caching aspects on controllers and methods. Before, around, and after hooks keep shared behavior out of each action.

ObjectQuel ORM

Use a Data Mapper with QUEL-inspired queries, relationship traversal via via, and entities that stay separate from persistence logic.

Contextual Services

Code against interfaces and let Canvas resolve implementations for the current context. Keep application services focused and reusable.

Secure by Default

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

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

The Quellabs Ecosystem

A family of open-source tools built around the same philosophy.

Two Execution Paths, One System

Start modernizing your legacy PHP codebase today.

GitHub Page (⭐ 14) Read Docs Sponsor on GitHub