1. Go to this page and download the library: Download quellabs/canvas library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
quellabs / canvas example snippets
use Quellabs\Canvas\Kernel;
use Symfony\Component\HttpFoundation\Request;
ernel->handle($request);
$response->send();
namespace App\Controllers;
use Quellabs\Canvas\Annotations\Route;
use Quellabs\Canvas\Controllers\BaseController;
class BlogController extends BaseController {
/**
* @Route("/")
*/
public function index() {
return $this->render('home.tpl');
}
/**
* @Route("/posts")
*/
public function list() {
$posts = $this->em->findBy(Post::class, ['published' => true]);
return $this->render('posts.tpl', $posts);
}
/**
* @Route("/posts/{id:int}")
*/
public function show(int $id) {
$post = $this->em->find(Post::class, $id);
return $this->render('post.tpl', $post);
}
}
// Basic ObjectQuel query
$results = $this->em->executeQuery("
range of p is App\\Entity\\Post
retrieve p where p.published = true
sort by p.publishedAt desc
");
// Queries with relationships and parameters
$techPosts = $this->em->executeQuery("
range of p is App\\Entity\\Post
range of u is App\\Entity\\User via p.authorId
retrieve (p, u.name) where p.title = /^Tech/i
and p.published = :published
sort by p.publishedAt desc
", [
'published' => true
]);
// public/index.php
use Quellabs\Canvas\Kernel;
use Symfony\Component\HttpFoundation\Request;
path' => __DIR__ . '/../' // Path to your existing files
]);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
// legacy/users.php - existing file, now enhanced with Canvas
use Quellabs\Canvas\Legacy\LegacyBridge;
// Access Canvas services in legacy code
$em = canvas('EntityManager');
$users = $em->findBy(User::class, ['active' => true]);
// Use ObjectQuel for complex queries
$recentUsers = $em->executeQuery("
range of u is App\\Entity\\User
retrieve u where u.active = true and u.createdAt > :since
sort by u.createdAt desc
limit 10
", ['since' => date('Y-m-d', strtotime('-30 days'))]);
echo "Found " . count($users) . " active users<br>";
foreach ($recentUsers as $user) {
echo "<h3>{$user->name}</h3>";
echo "<p>Joined: " . $user->createdAt->format('Y-m-d') . "</p>";
}
// src/Legacy/CustomFileResolver.php
use Quellabs\Canvas\Legacy\FileResolverInterface;
class CustomFileResolver implements FileResolverInterface {
public function resolve(string $path): ?string {
// Handle WordPress-style routing
if ($path === '/') {
return $this->legacyPath . '/index.php';
}
// Map URLs to custom file structure
if (str_starts_with($path, '/blog/')) {
$slug = substr($path, 6);
return $this->legacyPath . "/wp-content/posts/{$slug}.php";
}
// Handle custom admin structure
if (str_starts_with($path, '/admin/')) {
$adminPath = substr($path, 7);
return $this->legacyPath . "/backend/modules/{$adminPath}.inc.php";
}
return null; // Fall back to default behavior
}
}
// Register with kernel
$kernel->getLegacyHandler()->addResolver(new CustomFileResolver);
class ProductController extends BaseController {
/**
* @Route("/products/{id:int}")
*/
public function show(int $id) {
// Only matches numeric IDs
}
/**
* @Route("/files/{path:**}")
*/
public function files(string $path) {
// Matches: /files/css/style.css → path = "css/style.css"
return $this->serveFile($path);
}
}
/**
* @RoutePrefix("/api/v1")
*/
class ApiController extends BaseController {
/**
* @Route("/users") // Actual route: /api/v1/users
*/
public function users() {
return $this->json($this->em->findBy(User::class, []));
}
}
namespace App\Aspects;
use Quellabs\Contracts\AOP\BeforeAspect;
use Quellabs\Contracts\AOP\AroundAspect;
use Quellabs\Contracts\AOP\AfterAspect;
use Symfony\Component\HttpFoundation\Response;
// Before Aspects - Execute before the method, can stop execution
class RequireAuthAspect implements BeforeAspect {
public function __construct(private AuthService $auth) {}
public function before(MethodContext $context): ?Response {
if (!$this->auth->isAuthenticated()) {
return new RedirectResponse('/login');
}
return null; // Continue execution
}
}
// Around Aspects - Wrap the entire method execution
class CacheAspect implements AroundAspect {
public function around(MethodContext $context, callable $proceed): mixed {
$key = $this->generateCacheKey($context);
if ($cached = $this->cache->get($key)) {
return $cached;
}
$result = $proceed(); // Call the original method
$this->cache->set($key, $result, $this->ttl);
return $result;
}
}
// After Aspects - Execute after the method, can modify response
class AuditLogAspect implements AfterAspect {
public function after(MethodContext $context, Response $response): void {
$this->logger->info('Method executed', [
'controller' => get_class($context->getTarget()),
'method' => $context->getMethodName(),
'user' => $this->auth->getCurrentUser()?->id
]);
}
}
/**
* @InterceptWith(RequireAuthAspect::class)
* @InterceptWith(AuditLogAspect::class)
*/
class UserController extends BaseController {
// All methods automatically get authentication and audit logging
/**
* @Route("/users")
* @InterceptWith(CacheAspect::class, ttl=300)
*/
public function index() {
// Gets: RequireAuth + AuditLog (inherited) + Cache (method-level)
return $this->em->findBy(User::class, ['active' => true]);
}
}
class BlogController extends BaseController {
/**
* @Route("/posts")
* @InterceptWith(CacheAspect::class, ttl=600)
* @InterceptWith(RateLimitAspect::class, limit=100, window=3600)
*/
public function list() {
// Method gets caching and rate limiting
return $this->em->findBy(Post::class, ['published' => true]);
}
}
/**
* @InterceptWith(CacheAspect::class, ttl=3600, tags={"reports", "admin"})
* @InterceptWith(RateLimitAspect::class, limit=10, window=60)
*/
public function expensiveReport() {
// Cached for 1 hour with tags, rate limited to 10 requests per minute
}
class CacheAspect implements AroundAspect {
public function __construct(
private CacheInterface $cache,
private int $ttl = 300,
private array $tags = []
) {}
}
namespace App\Controllers;
use Quellabs\Canvas\Annotations\Route;
use Quellabs\Canvas\Annotations\InterceptWith;
use Quellabs\Canvas\Validation\ValidateAspect;
use App\Validation\UserValidation;
class UserController extends BaseController {
/**
* @Route("/users/create", methods={"GET", "POST"})
* @InterceptWith(ValidateAspect::class, validate=UserValidation::class)
*/
public function create(Request $request) {
if ($request->isMethod('POST')) {
// Check validation results set by ValidateAspect
if ($request->attributes->get('validation_passed', false)) {
// Process valid form data
$user = new User();
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$user->setPassword(password_hash($request->request->get('password'), PASSWORD_DEFAULT));
$this->em->persist($user);
$this->em->flush();
return $this->redirect('/users');
}
// Validation failed - render form with errors
return $this->render('users/create.tpl', [
'errors' => $request->attributes->get('validation_errors', []),
'old' => $request->request->all()
]);
}
// Show empty form for GET requests
return $this->render('users/create.tpl');
}
}
namespace App\Validation;
use Quellabs\Canvas\Validation\ValidationInterface;
use Quellabs\Canvas\Validation\Rules\NotBlank;
use Quellabs\Canvas\Validation\Rules\Email;
use Quellabs\Canvas\Validation\Rules\Length;
class UserValidation implements ValidationInterface {
public function getRules(): array {
return [
'name' => [
new NotBlank(['message' => 'Name is w Length(['min' => 8, 'message' => 'Password must be at least {{min}} characters'])
]
];
}
}
/**
* @Route("/api/users", methods={"POST"})
* @InterceptWith(ValidateAspect::class, validate=UserValidation::class, auto_respond=true)
*/
public function createUser(Request $request) {
// For API requests, validation failures automatically return JSON:
// {
// "message": "Validation failed",
// "errors": {
// "email": ["Please enter a valid email address"],
// "password": ["Password must be at least 8 characters"]
// }
// }
// This code only runs if validation passes
$user = $this->createUserFromRequest($request);
return $this->json(['success' => true, 'user_id' => $user->getId()]);
}
// Different template engines
$twig = $this->container->for('twig')->get(TemplateEngineInterface::class);
$blade = $this->container->for('blade')->get(TemplateEngineInterface::class);
// Different cache backends
$redis = $this->container->for('redis')->get(CacheInterface::class);
$file = $this->container->for('file')->get(CacheInterface::class);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.