Installation
composer require atoermer/direct-bundle
Enable the bundle in config/bundles.php:
Atoermer\DirectBundle\AtoermerDirectBundle::class => ['all' => true],
Basic Configuration
Add routing in config/routes.yaml:
direct:
resource: "@AtoermerDirectBundle/Resources/config/routing.yml"
prefix: /api
First Use Case: Exposing a Controller Action
Annotate a controller method with @Direct:
use Atoermer\DirectBundle\Annotation\Direct;
class UserController extends AbstractController
{
/**
* @Direct()
*/
public function getUsers()
{
return $this->json(['users' => User::all()]);
}
}
Client-Side Usage
Configure ExtJS to point to /api/direct.php and call:
Ext.Direct.addProvider({
url: '/api/direct.php',
type: 'remoting',
actionMap: {
'MyApp.user.getUsers': {
formHandler: true
}
}
});
Ext.Direct.MyApp.user.getUsers({}, function(response) {
console.log(response.users);
});
Annotation-Based Routing
Use @Direct on methods to expose them as ExtDirect endpoints. Supports:
/**
* @Direct(
* name="getUserById",
* returnType="array",
* params={
* "userId"={"type":"integer", "required":true}
* }
* )
*/
public function getUserById($userId) { ... }
Dependency Injection Inject services into annotated methods:
public function __construct(private UserRepository $userRepo) {}
/**
* @Direct()
*/
public function listActiveUsers() {
return $this->json($this->userRepo->findActive());
}
Param Validation Leverage Symfony’s validator for input validation:
/**
* @Direct(
* params={
* "data"={"type":"array", "validator":"@validator"}
* }
* )
*/
public function createUser(array $data, ValidatorInterface $validator) {
$errors = $validator->validate($data);
if (count($errors)) { throw new \RuntimeException('Validation failed'); }
// ...
}
Async Support
Use Symfony’s Messenger for background processing:
/**
* @Direct()
*/
public function asyncTask($payload, MessengerInterface $messenger) {
$message = new ProcessTask($payload);
$messenger->dispatch($message);
return $this->json(['status' => 'queued']);
}
Standardized Responses Return consistent JSON structures:
return $this->json([
'success' => true,
'data' => $user->toArray(),
'total' => User::count()
]);
Error Handling Throw exceptions for ExtDirect-compatible errors:
throw new \RuntimeException('User not found', 404, [
'code' => 'USER_NOT_FOUND',
'message' => 'The requested user does not exist.'
]);
CRUD Operations
Expose create, read, update, delete methods with @Direct and handle ExtJS’s Ext.data.Store operations.
Real-Time Updates
Combine with Symfony’s Mercure or WebSocket bundles for push notifications:
// Client-side
Ext.Direct.on({
scope: this,
userUpdated: function(user) {
this.updateView(user);
}
});
Authentication Secure endpoints with Symfony’s security component:
/**
* @Direct(
* security="is_granted('ROLE_ADMIN')"
* )
*/
public function adminOnlyAction() { ... }
CORS Issues
Ensure your server sends proper CORS headers. Configure in .env:
SYMFONY_TRUSTED_PROXIES=127.0.0.1
SYMFONY_TRUSTED_HOSTS=^localhost|yourdomain\.com$
Add middleware in config/packages/security.yaml:
enable_authenticator_manager: true
stateless: true
Annotation Processing
Clear the cache after adding @Direct annotations:
php bin/console cache:clear
ExtJS Version Mismatch
DirectBundle follows ExtDirect 1.0. Ensure your ExtJS version (e.g., ExtJS 4/5) is compatible. For ExtJS 6+, use Ext.app.Direct instead of Ext.Direct.
Parameter Type Casting DirectBundle does not auto-cast parameters. Explicitly type-hint or validate:
// Bad: Assumes $id is integer
public function getById($id) { ... }
// Good: Explicit or validated
public function getById(int $id) { ... }
Enable Verbose Logging
Add to config/packages/monolog.yaml:
handlers:
direct:
type: stream
path: "%kernel.logs_dir%/direct.log"
level: debug
channels: ["direct"]
Log DirectBundle events in your controller:
use Psr\Log\LoggerInterface;
public function __construct(private LoggerInterface $logger) {}
/**
* @Direct()
*/
public function debugAction() {
$this->logger->debug('Direct call received', ['params' => func_get_args()]);
return $this->json(['status' => 'ok']);
}
Check DirectBundle Events
Listen for direct.request and direct.response events in EventSubscriber:
use Atoermer\DirectBundle\Event\DirectEvent;
public function onDirectRequest(DirectEvent $event) {
$request = $event->getRequest();
$this->logger->info('Direct call', [
'action' => $request->get('_action'),
'params' => $request->request->all()
]);
}
Custom Serializers Override the default JSON serializer by binding your own:
# config/services.yaml
services:
App\Serializer\DirectSerializer:
tags: ['direct.serializer']
Middleware Integration
Add middleware to DirectBundle’s pipeline in config/packages/direct.yaml:
direct:
middleware:
- App\DirectBundle\Middleware\AuthMiddleware
- App\DirectBundle\Middleware\LoggingMiddleware
Dynamic Action Mapping Register actions programmatically (e.g., for API versioning):
use Atoermer\DirectBundle\Direct\DirectRegistry;
public function __construct(private DirectRegistry $registry) {}
public function boot() {
$this->registry->addAction('v2.getUsers', [
'controller' => [UserController::class, 'getUsersV2'],
'params' => ['version' => ['type' => 'string', 'default' => 'v2']]
]);
}
Batch Processing
Use Ext.data.Store with pageSize and start params to paginate:
/**
* @Direct(
* params={
* "page"={"type":"integer", "default":1},
* "limit"={"type":"integer", "default":20}
* }
* )
*/
public function getPaginatedUsers($page, $limit) {
return $this->json([
'data' => User::paginate($limit, ['*'], 'page', $page)->items(),
'total' => User::count()
]);
}
Caching Responses Cache frequent queries with Symfony’s cache component:
use Symfony\Contracts\Cache\CacheInterface;
public function __construct(private CacheInterface $cache) {}
/**
* @Direct()
*/
public function getCachedData() {
return $this->json($this->cache->get('direct_data', function() {
return User::all()->toArray();
}));
}
Routing Overrides
DirectBundle’s direct.php route is non-negotiable. Avoid overriding it in custom routing files.
Parameter Naming DirectBundle uses `request->request->
How can I help you explore Laravel packages today?