ObjectQuel ORM

ObjectQuel ORM provides an intuitive object-relational mapping layer with a purpose-built query language, automatic relationship management, and Data Mapper pattern architecture.

explanation

Installation

Install ObjectQuel in your Canvas project via Composer:

composer require quellabs/canvas-objectquel

Configure your database connection in config/database.php:

return [
    'driver'           => 'mysql',
    'host'             => 'localhost',
    'database'         => 'database',
    'username'         => 'root',
    'password'         => '',
    'port'             => 3306,
    'charset'          => 'utf8mb4',
    'collation'        => 'utf8mb4_unicode_ci',

    // Development mode
    'development_mode' => true,

    // Entity namespace
    'entity_namespace' => 'App\\Entities',

    // Path to the entities folder
    'entity_path'      => dirname(__FILE__) . '/../src/Entities/',

    // Path to the proxy folder
    'proxy_path'       => dirname(__FILE__) . '/../storage/objectquel/proxies/',

    // Path to the migrations folder
    'migrations_path'  => dirname(__FILE__) . '/../migrations',
];

Once installed and configured, ObjectQuel is automatically discovered by Canvas. The EntityManager becomes available via $this->em() in controllers that extend BaseController.

CLI Tools (Sculpt)

ObjectQuel includes Sculpt, a CLI tool for development tasks:

# Create new entity interactively
php vendor/bin/sculpt make:entity

# Generate entity from existing table
php vendor/bin/sculpt make:entity-from-table

# Generate and run migrations
php vendor/bin/sculpt make:migrations
php vendor/bin/sculpt quel:migrate

# Rollback migrations
php vendor/bin/sculpt quel:migrate --rollback

Entity Definition

Define entities using PHP annotations:

/**
 * @Orm\Table(name="users")
 */
class UserEntity {
    /**
     * @Orm\Column(name="id", type="integer", length=11, primary_key=true)
     * @Orm\PrimaryKeyStrategy(strategy="identity")
     */
    private ?int $id = null;

    /**
     * @Orm\Column(name="name", type="string", length=255)
     */
    private string $name;

    /**
     * @Orm\Column(name="email", type="string", length=255)
     */
    private string $email;

    /**
     * @Orm\Column(name="created_at", type="datetime", nullable=true)
     */
    private ?\DateTime $createdAt = null;

    // Standard getters and setters
    public function getId(): ?int { return $this->id; }
    public function getName(): string { return $this->name; }
    public function setName(string $name): void { $this->name = $name; }
    // ...
}

Basic CRUD Operations

Standard find, persist, and remove operations:

// Find by ID
$user = $this->em()->find(UserEntity::class, $id);

// Find by criteria
$admins = $this->em()->findBy(UserEntity::class, [
    'active' => true,
    'role' => 'admin'
]);

// Create
$user = new UserEntity();
$user->setName('John Doe');
$user->setEmail('john@example.com');
$this->em()->persist($user);
$this->em()->flush();

// Update
$user->setEmail('newemail@example.com');
$this->em()->persist($user);
$this->em()->flush();

// Delete
$this->em()->remove($user);
$this->em()->flush();

ObjectQuel Query Language

Execute queries using ObjectQuel's declarative syntax:

// Basic query
$results = $this->em()->executeQuery("
    range of p is App\\Entity\\PostEntity
    retrieve (p) where p.published = :published
    sort by p.createdAt desc
", [
    'published' => true
]);

$posts = array_column($results, 'p');

// Query with relationships
$results = $this->em()->executeQuery("
    range of u is App\\Entity\\UserEntity
    range of p is App\\Entity\\PostEntity via u.posts
    retrieve (u, p) where u.id = :userId
    sort by p.createdAt desc
", [
    'userId' => $authorId
]);

Entity Autowiring

Canvas automatically resolves entity parameters in controller methods. Type-hint an entity class and Canvas fetches it from the database using the route's primary key — no manual lookup required.

/**
 * @Route("/posts/{id:int}")
 * @param PostEntity|null $post
 * @return Response
 */
public function show(?PostEntity $post): Response {
    if (!$post) {
        return $this->notFound('Post does not exist.');
    }

    return $this->render('blog/show.tpl', ['post' => $post]);
}

By convention, Canvas matches the {id} route parameter to the entity's primary key. The parameter is nullable — if no record is found, null is passed and you handle it in the controller.

Non-standard route parameters

When the route parameter is not named id, use the @ResolveEntity annotation to specify which route parameter holds the primary key. Pass the entity class using ::class syntax as the first argument:

/**
 * @Route("/posts/{slug:string}")
 * @ResolveEntity(PostEntity::class, routeParam="slug")
 * @param PostEntity|null $post
 * @return Response
 */
public function show(?PostEntity $post): Response {
    if (!$post) {
        return $this->notFound('Post does not exist.');
    }

    return $this->render('blog/show.tpl', ['post' => $post]);
}

Multiple entity parameters

When a method has more than one entity parameter, add a @ResolveEntity for each. The entity class identifies which parameter each annotation applies to:

/**
 * @Route("/users/{userId:int}/posts/{postId:int}")
 * @ResolveEntity(UserEntity::class, routeParam="userId")
 * @ResolveEntity(PostEntity::class, routeParam="postId")
 * @param UserEntity|null $user
 * @param PostEntity|null $post
 * @return Response
 */
public function show(?UserEntity $user, ?PostEntity $post): Response {
    if (!$user || !$post) {
        return $this->notFound();
    }

    return $this->render('blog/show.tpl', ['user' => $user, 'post' => $post]);
}

@ResolveEntity reference

Parameter Required Description
First argument When using multiple entity parameters or a non-standard route parameter The entity class to resolve, using ClassName::class syntax. Refactor-safe — your IDE will update it automatically on rename.
routeParam When route parameter is not named id The name of the route placeholder that contains the primary key value. Defaults to id.

Learn More

For comprehensive documentation on ObjectQuel including advanced query syntax, caching strategies, transaction management, and performance optimization, visit the ObjectQuel documentation site.