Apologies for the long post, feel like I have gone down a rabbit hole here and struggling to find a solution which works.
I'm building a Symfony (2.6) application which I'd like to support multiple stores each with unique products for example using subdomains.
http://stores.com - Primary store landing page
http://stores.com/first-product - Product only available on store.com
http://nicks.stores.com - Nicks store landing page
http://nicks.stores.com/first-product - Different product from the above example, only available on nicks.stores.com
Handling subdomains
I successfully have store landing pages working using host matching on the default Symfony router (http://symfony.com/doc/current/components/routing/hostname_pattern.html).
With an event listener which picks up the store subdomain and adds it into a service (idea taken from http://knpuniversity.com/screencast/question-answer-day/symfony2-dynamic-subdomains).
Store service (note I use JMSDiExtraBundle to define services using annotations):
/**
* @Service("store_service")
*/
class StoreService
{
protected $storeCurrent;
public function getCurrentStore()
{
return $this->storeCurrent;
}
public function setCurrentStore(Store $store)
{
$this->storeCurrent = $store;
return $this;
}
}
Event listener for extracting subdomain from request and adding into the service if it exists
/**
* @Service
*/
class CurrentStoreListener
{
/**
* @var EntityManager
*/
protected $em;
/**
* @var StoreService
*/
protected $storeService;
/**
* @DI\InjectParams({
* "em" = @DI\Inject("doctrine.orm.entity_manager"),
* "storeService" = @DI\Inject("store_service")
* })
*/
public function __construct(EntityManager $em, StoreService $storeService)
{
$this->em = $em;
$this->storeService = $storeService;
}
/**
* @Observe("kernel.request", priority=31)
*/
public function onKernelRequest(GetResponseEvent $event)
{
$store = $this->em->getRepository('StoreBundle:Store')
->findByDomainNotDeleted($event->getRequest()->getHost());
if (!$store) {
throw new NotFoundHttpException('No such store exists');
}
$this->storeService->setCurrentStore($store);
}
}
Example controller action with route using the SensioFrameworkExtraBundle:
class StoreController extends Controller
{
/**
* @DI\Inject("doctrine.orm.entity_manager")
* @var EntityManager
*/
private $em;
/**
* @DI\Inject("store_service")
* @var StoreService
*/
protected $storeService;
/**
* @Route("/", name="store_index")
* @Template
*/
public function indexAction()
{
$store = $this->storeService->getCurrentStore();
$products = $this->em->getRepository('StoreBundle:Product')->findAllForStoreInOrder($store);
return [
'store' => $store,
'products' => $products
];
}
}
This all works perfectly. :)
Handling dynamic product routes for stores
This is where I'm starting to have issues because the event listener (as shown above) is not being called early enough so it's available for the dynamic router below.
This dynamic router worked just fine when the application was a single store, the below has been built using the CMF Dynamic router (http://symfony.com/doc/current/cmf/book/routing.html)
# app/config/config.yml
cmf_routing:
dynamic:
enabled: true
route_provider_service_id: store.product_router
chain:
routers_by_id:
router.default: 32
cmf_routing.dynamic_router: 30
/**
* @Service("store.product_router")
*/
class ProductRouter implements RouteProviderInterface
{
/**
* @var EntityManager
*/
protected $em;
/**
* @var StoreService
*/
protected $storeService;
/**
* @DI\InjectParams({
* "em" = @DI\Inject("doctrine.orm.entity_manager"),
* "storeService" = @DI\Inject("store_service")
* })
*/
public function __construct(EntityManager $em, StoreService $storeService)
{
$this->em = $em;
$this->storeService = $storeService;
}
/**
* @param Request $request
* @return RouteCollection
*/
public function getRouteCollectionForRequest(Request $request)
{
$collection = new RouteCollection();
$product = $this->em->getRepository('StoreBundle:Product')
->findOneBySlugForStore(substr($request->getRequestUri(), 1), $this->storeService->getCurrentStore());
if (empty($product)) {
return $collection;
}
$route = new Route(
'/' . $product->getSlug(),
[
'_controller' => 'StoreBundle:Product:view',
'slug' => $product->getSlug()
]
);
$collection->add($product->getSlug(), $route);
return $collection;
}
public function getRouteByName($name, $params = [])
{
}
public function getRoutesByNames($names)
{
}
}
I have tried to rearrange the priorities of the services, to me the following priorities should work:
32 - Default router
31 - Current store listener
30 - Dynamic router
Any suggestions on how I can get access to the current store inside my dynamic router?
Understand I could hack this and just extract the host request and pass that into the repository on the dynamic router but thats an extra table join I'd rather not do seeing as I should already have the store entity available.