Install the Bundle:
composer require devtrw/state-bridge-bundle
Add to config/bundles.php:
return [
// ...
Devtrw\StateBridgeBundle\DevtrwStateBridgeBundle::class => ['all' => true],
];
Register JSONP Request Format (Critical for AngularJS compatibility):
In config/packages/framework.yaml:
framework:
http_method_override: true
allowed_http_methods: [GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH, JSONP]
Create a custom request format listener (see Symfony docs) to handle jsonp requests.
First Use Case: Role-Based State Activation
Define states in config/packages/devtrw_state_bridge.yaml:
devtrw_state_bridge:
states:
admin:
path: /admin
roles: [ROLE_ADMIN]
template: admin.html.twig
user:
path: /user
roles: [ROLE_USER]
template: user.html.twig
Access states via /_statebridge?callback=angularCallback (JSONP endpoint).
Inject the Service:
angular.module('app').factory('StateBridge', ['$http', function($http) {
return {
fetchStates: function() {
return $http.jsonp('/_statebridge', { params: { callback: 'JSON_CALLBACK' } });
}
};
}]);
Load States Dynamically:
StateBridge.fetchStates().then(function(response) {
angular.forEach(response.data, function(state) {
if (state.roles.includes('ROLE_USER')) { // Check user role
$stateProvider.state(state.name, state.config);
}
});
});
Backend Configuration:
config/packages/devtrw_state_bridge.yaml with:
path: URL segment (e.g., /dashboard).roles: Array of Symfony roles (e.g., [ROLE_EDITOR]).template: Twig template path (optional, for server-side rendering).config: Custom AngularJS state config (merged with defaults).devtrw_state_bridge:
states:
dashboard:
path: /dashboard
roles: [ROLE_USER, ROLE_ADMIN]
config:
url: /dashboard
templateUrl: 'app/dashboard.html'
controller: 'DashboardCtrl'
resolve:
data: ['DataService', function(DataService) {
return DataService.getDashboardData();
}]
Role-Based Activation:
// src/EventListener/StateBridgeListener.php
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class StateBridgeListener {
public function __construct(TokenStorageInterface $tokenStorage) {
$this->tokenStorage = $tokenStorage;
}
public function onStatesLoad(StatesLoadEvent $event) {
$token = $this->tokenStorage->getToken();
$roles = $token ? $token->getRoles() : [];
$event->setStates(array_filter($event->getStates(), function($state) use ($roles) {
return array_intersect($state['roles'], $roles);
}));
}
}
services.yaml:
services:
App\EventListener\StateBridgeListener:
tags:
- { name: kernel.event_listener, event: devtrw_state_bridge.states_load, method: onStatesLoad }
Dynamic Template Injection:
// In your AngularJS app
$http.get('/_statebridge/template/dashboard').then(function(response) {
$compile(response.data)($scope); // Inject into DOM
});
AngularJS UI-Router Integration:
ui-router’s stateRegistry to dynamically register states:
angular.module('app').run(['$stateRegistry', 'StateBridge', function($stateRegistry, StateBridge) {
StateBridge.fetchStates().then(function(states) {
states.forEach(function(state) {
$stateRegistry.state(state.name, state.config);
});
});
}]);
Symfony Security Integration:
// src/Voter/StateVoter.php
class StateVoter extends AbstractVoter {
public function supports(string $attribute, $subject): bool {
return $attribute === 'ACCESS_STATE' && $subject instanceof State;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool {
return in_array($subject->getRole(), $token->getRoles());
}
}
Caching States:
# config/packages/devtrw_state_bridge.yaml
devtrw_state_bridge:
cache_enabled: true
cache_lifetime: 3600 # 1 hour
JSONP CORS Issues:
.htaccess:
Header set Access-Control-Allow-Origin "*"
EventListener:
$response->headers->set('Access-Control-Allow-Origin', ['https://yourdomain.com']);
State Name Collisions:
states:
app_dashboard:
path: /dashboard
$state.go('app_dashboard');
Template Path Resolution:
@ prefix for bundles:
template: '@AppBundle/Resources/views/dashboard.html.twig'
Role Hierarchy Misconfiguration:
ROLE_ADMIN implies ROLE_USER) may cause unexpected state exposure. Explicitly list all required roles:
roles: [ROLE_ADMIN, ROLE_SUPER_ADMIN]
Check State Filtering:
StatesLoadEvent:
// In your listener
dump($event->getStates());
console.log(response.data); // Check filtered states
JSONP Callback Issues:
callback (not jsonp):
$http.jsonp('/_statebridge', { params: { callback: 'JSON_CALLBACK' } });
/_statebridge?callback=testCallback
Twig Template Not Found:
php bin/console cache:clear
php bin/console debug:twig @AppBundle/Resources/views/dashboard.html.twig
Custom State Providers:
// src/StateProvider/CustomStateProvider.php
class CustomStateProvider implements StateProviderInterface {
public function getStates(): array {
return [
'custom_state' => [
'path' => '/custom',
'roles' => ['ROLE_CUSTOM'],
'config' => ['nested' => true],
],
];
}
}
services.yaml:
services:
App\StateProvider\CustomStateProvider:
tags: [devtrw_state_bridge.state_provider]
Event-Driven Extensions:
devtrw_state_bridge.states_load to modify states dynamically:
// src/EventListener/StateModifierListener.php
class StateModifierListener {
public function onStatesLoad(StatesLoadEvent $event) {
foreach ($event->getStates() as &$state) {
$state['config']['data'] = ['dynamic' => true]; // Add custom config
}
}
}
Frontend-Specific Extensions:
How can I help you explore Laravel packages today?