I just want to access the screen_shots_path parameter from FeatureContext.php file but writing $this->getMinkParameter('screen_shots_path'); doesn't work?
Anyone know how to do it?
Thanks in advance
I checked this one but the class extends BehatContext and mine extends MinkContext so I giot confused how to apply it mine.
sport/behat.yml
default:
context:
class: 'FeatureContext'
extensions:
Behat\Symfony2Extension\Extension:
mink_driver: true
kernel:
env: test
debug: true
Behat\MinkExtension\Extension:
base_url: 'http://localhost/local/sport/web/app_test.php/'
files_path: 'dummy/'
screen_shots_path: 'build/behat/'
browser_name: 'chrome'
goutte: ~
selenium2: ~
paths:
features: 'src/Football/TeamBundle/Features'
bootstrap: %behat.paths.features%/Context
sport/src/Football/TeamBundle/Features/Context/FeatureContext.php
namespace Football\TeamBundle\Features\Context;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Behat\Mink\Driver\Selenium2Driver;
class FeatureContext extends MinkContext
{
/**
* Take screen-shot when step fails.
* Works only with Selenium2Driver.
*
* #AfterStep
* #param $event
* #throws \Behat\Mink\Exception\UnsupportedDriverActionException
*/
public function takeScreenshotAfterFailedStep($event)
{
if (4 === $event->getResult()) {
$driver = $this->getSession()->getDriver();
if (! ($driver instanceof Selenium2Driver)) {
throw new UnsupportedDriverActionException(
'Taking screen-shots is not supported by %s, use Selenium2Driver instead.',
$driver
);
return;
}
#$directory = 'build/behat';
$directory = $this->getMinkParameter('screen_shots_path');
if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}
$filename = sprintf(
'%s_%s_%s.%s',
$this->getMinkParameter('browser_name'),
date('Y-m-d') . '_' . date('H:i:s'),
uniqid('', true),
'png'
);
file_put_contents($directory . '/' . $filename, $driver->getScreenshot());
}
}
}
I know you've tagged it as a Symfony question, there might be something on that side that affects it, but from the code it doesn't seem to be, so the problem is probably in the following.
Assuming you are using Mink Extension 1.x and not 2.x, screen_shots_path parameter is not on the list of the supported ones. In fact 2.x doesn't support it either, but it would throw an exception right away when it finds something illegal in the config. Perhaps 1.x doesn't do that. You can see the supported parameters here.
The most likely reason, screen_shots_path simply gets ignored when the config is normalised and hence getMinkParameter('screen_shots_path') doesn't return anything. I bet if you try the same with files_path you'll see dummy/.
If you want to keep the configuration in your behat.yml your best chances would be to pass them directly to the context, see documentation.
# behat.yml
default:
context:
class: FeatureContext
parameters:
screen_shots_path: 'build/behat/'
This will be passed to the constructor where you can initialise a local parameter. Alternatively you can use the static parameter and make it accessible through other contexts.
class FeatureContext extends MinkContext
{
protected $screenShotsPath;
public function __construct($parameters)
{
$this->screenShotsPath = isset($parameters['screen_shots_path']) ? $parameters['screen_shots_path'] : 'some/default/path';
}
public function takeScreenshotAfterFailedStep($event)
{
$directory = $this->screenShotsPath;
}
}
Related
I'm stuck on something, and it seems internet haven't had this problem (or i haven't got the right keyword to find the answer)
Keep in mind that I'm still learning Symfony 6 and I'm a bit by myself for now ... So I'm open if you tell me that everything I did is garbage.
I'm creating an application to export datas in excels for our customers.
I create a call on a database, with a specific SQL request to get my datas. Then i send the datas in a SpreadsheetService to create my spreadsheet and launch the download for the user.
<?php
namespace App\Service;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\HttpFoundation\StreamedResponse;
class SpreadsheetService {
public function export(string $title, $datas, $rsm) {
$streamedResponse = new StreamedResponse();
$streamedResponse->setCallback(function () use ($title, $datas, $rsm) {
// Generating SpreadSheet
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle($title);
// Generating First Row with column name
$sheet->fromArray($rsm->scalarMappings);
// Generating other rows with datas
$count = 2;
foreach ($datas as $data) {
$sheet->fromArray($data, null, 'A' . $count);
$count++;
}
// Write and send created spreadsheet
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
// This exit(); is required to prevent errors while opening the generated .xlsx
exit();
});
// Puting headers on response and sending it
$streamedResponse->setStatusCode(Response::HTTP_OK);
$streamedResponse->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$streamedResponse->headers->set('Content-Disposition', 'attachment; filename="' . $title . '.xlsx"');
$streamedResponse->send();
return;
}
So, this is working like a charm. BUT, my chief want it to be asynchronous.
After some research on Symfony 6 and async in Symfony, I happened to find something called symfony/messenger, which at first sounded like it was only for send messages (mail, chat, sms ...) but after some reading, sounded like the async library for Symfony 6.
So, i tried by following step by step to setup the async.
First, i created an ExportMessage.php
<?php
namespace App\Message;
class ExportMessage {
// I need a slug to know for which customer i want the export
private string $slug;
// I need this string to know if it is an export for their clients, or their candidats etc ... (Wanted to setup an enum, but failed trying for now ...)
private string $typeExport;
public function __construct(string $slug, string $typeExport) {
$this->slug = $slug;
$this->typeExport = $typeExport;
}
/**
* Get the value of slug
*/
public function getSlug() {
return $this->slug;
}
/**
* Get the value of typeExport
*/
public function getTypeExport() {
return $this->typeExport;
}
}
Then i created an ExportHandler.php that will do some work when i send an ExportMessage (They go together)
<?php
namespace App\MessageHandler;
use App\Message\ExportMessage;
use App\Service\ClientRequestService;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class ExportHandler implements MessageHandlerInterface {
private ClientRequestService $clientRequestService;
// clientRequestService is the service that send the sql request and then call the SpreadsheetService to create the excel file
public function __construct(ClientRequestService $clientRequestService) {
$this->clientRequestService = $clientRequestService;
}
public function __invoke(ExportMessage $message) {
return $this->clientRequestService->export($message->getSlug(), $message->getTypeExport());
}
}
Finally, in my Controller, I don't call clientRequestService->export anymore, i create a messageBus that will keep track of any messages i send, and will process them correctly (Something like that, I didn't understand every aspect of it for now I think)
class DashboardController extends AbstractController {
private MessageBusInterface $messageBus;
public function __construct([...], MessageBusInterface $messageBus) {
[...]
$this->messageBus = $messageBus;
}
[...]
#[Route('{slug}/export-candidats', name: 'app_export_candidats')]
public function exportCandidats(string $slug) {
$this->messageBus->dispatch(new ExportMessage($slug, 'candidats'));
// Not anymore --> $this->requestService->export($slug, 'candidats');
return $this->redirectToRoute('app_dashboard', ['slug' => $slug]);
}
[...]
And just for the sake of it, here's the clientRequestService.php in case
<?php
namespace App\Service;
use App\Service\MappingService;
use App\Service\SpreadsheetService;
use App\Factory\EntityManagerFactory;
use App\Repository\Istrator\DatabaseGroupRepository;
class ClientRequestService {
private $factory;
private $databaseGroupRepository;
private $mappingService;
private $spreadsheetService;
private $rootpath_sql_request;
public function __construct(EntityManagerFactory $factory, DatabaseGroupRepository $databaseGroupRepository, MappingService $mappingService, SpreadsheetService $spreadsheetService, string $rootpath_sql_request) {
$this->factory = $factory;
$this->databaseGroupRepository = $databaseGroupRepository;
$this->mappingService = $mappingService;
$this->spreadsheetService = $spreadsheetService;
$this->rootpath_sql_request = $rootpath_sql_request;
}
public function export(string $slug, $export) {
$databaseGroup = $this->databaseGroupRepository->findBySlug($slug);
$entityManager = $this->factory->createManager($databaseGroup->getIdDb());
switch ($export) {
case 'candidats':
$rsm = $this->mappingService->getMappingExportCandidats($entityManager);
$query = file_get_contents($this->rootpath_sql_request . "export_candidats.sql");
break;
case 'clients':
$rsm = $this->mappingService->getMappingExportClients($entityManager);
$query = file_get_contents($this->rootpath_sql_request . "export_clients.sql");
break;
case 'pieces_jointes':
$rsm = $this->mappingService->getMappingPiecesJointes($entityManager);
$query = file_get_contents($this->rootpath_sql_request . "export_noms_pj.sql");
break;
case 'notes_suivi':
$rsm = $this->mappingService->getMappingNotesSuivi($entityManager);
$query = file_get_contents($this->rootpath_sql_request . "export_notes_suivi.sql");
break;
default:
return;
}
$results = $entityManager->createNativeQuery($query, $rsm)->execute();
$this->spreadsheetService->export($export, $results, $rsm);
}
}
It seems to be okay, but this doesn't trigger the download ...
Can someone help me understand this problem ?
EDIT 1:
After some research, i found out that the Handler isn't even called.
I tried some thing :
In the messenger.yaml i defined my ExportMessage as async :
framework:
messenger:
failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
use_notify: true
check_delayed_interval: 60000
retry_strategy:
max_retries: 3
multiplier: 2
failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
Symfony\Component\Notifier\Message\ChatMessage: async
Symfony\Component\Notifier\Message\SmsMessage: async
// --------------- Here ----------------
App\Message\ExportMessage: async
And then in my services.yaml I defined my handler as a Service
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\MessageHandler\ExportHandler:
tags: [messenger.message_handler]
[...]
Maybe this can narrow down some problems. My handlers isn't called and i don't understand why.
My message is sent (It's created in the database)
| 30 | O:36:\"Symfony\\Component\\Messenger\\Envelope\":2:{s:44:\"\0Symfony\\Component\\Messenger\\Envelope\0stamps\";a:1:{s:46:\"Symfony\\Component\\Messenger\\Stamp\\BusNameStamp\";a:1:{i:0;O:46:\"Symfony\\Component\\Messenger\\Stamp\\BusNameStamp\":1:{s:55:\"\0Symfony\\Component\\Messenger\\Stamp\\BusNameStamp\0busName\";s:21:\"messenger.bus.default\";}}}s:45:\"\0Symfony\\Component\\Messenger\\Envelope\0message\";O:25:\"App\\Message\\ExportMessage\":2:{s:31:\"\0App\\Message\\ExportMessage\0slug\";s:4:\"toma\";s:37:\"\0App\\Message\\ExportMessage\0typeExport\";s:9:\"candidats\";}} | [] | default | 2022-04-26 14:36:53 | 2022-04-26 14:36:53 | NULL |
I continue to work on it.
EDIT 2 :
Ok, so I didn't understand how asynchrones buses worked in php, because I'm from Typescript, and asynchronous process in Typescript are really different compared to this.
I needed a consumers that will listen when there is a message pushed in the bus, and consume it, and send it to the handler...
The documentation explained that, but i didn't understand :
https://symfony.com/doc/current/the-fast-track/en/18-async.html#running-workers-in-the-background
So now, I can generate my excel file asynchronously. I just have to create something to watch for it to be created, and give a link to download it.
Hope this thread can help some people who, like me, didn't quite understand the bus mecanic.
Codeigniter does not find trivial classes:
Unable to load the requested class: Bcrypt
But the same goes for custom made classes defined in files in application/libraries/. I am used that django lists the folders where it searched for a file, but did not find one. Obviously CI must also iterate over some list of folders or files, but is not as polite to display them along with the error.
It seems as if CI has a naming convention to deduce the (set of) filename(s) where it would expect a class to be. How can I programmatically error_log the list of folders or filenames that Codeigniter or PHP tried to track down this class?
EDIT: The lines of code that produce such a loading-error are:
$autoload['libraries'] = array('database','session','mi_file_fetcher');
in application/config/autoload.php and
$this->load->library("bcrypt");
in application/models/User.php
As stated in the comments, I was not asking for a fix, I was asking for a list.
I managed to do so by updating system/core/Loader.php
protected function _ci_load_library_files_tried($class, $subdir, $params, $object_name)
{
$files_tried = array(BASEPATH . 'libraries/' . $subdir . $class . '.php');
foreach ($this->_ci_library_paths as $path) {
if ($path === BASEPATH) {
continue;
}
array_push($files_tried, $path . 'libraries/' . $subdir . $class . '.php');
}
return $files_tried;
}
protected function _ci_load_library($class, $params = NULL, $object_name = NULL)
{
// ...
// If we got this far we were unable to find the requested class.
$files_tried = $this->_ci_load_library_files_tried($class, $subdir, $params, $object_name);
log_message('error', 'Unable to load the requested class: '.$class .
", tried these files:\n" . join("\n", $files_tried));
show_error('Unable to load the requested class: '.$class .
', tried these files:<ul><li>' . join('</li><li>', $files_tried) . '</li></ul>');
}
Would be great if CI actually provided decent debugging information.
The CodeIgniter (CI) documentation does tell you the default locations of libraries, models, helpers, views and many other framework objects. There isn't a section that explicitly lists the folders though. The Loader Class documentation does tell you, but you have to dig for it a bit.
The subtopics on the General Topics section of the docs clearly state the default locations for the various classes the frameworks uses and where to put custom classes.
In most cases following the prescribed file structure and using the loader class, e.g. (Bcrypt.php is in /application/libraries/)
$this->load->library('bcrypt');
works perfectly.
There are cases (none of which seem to be involved in your problem) where CI needs help. Rather than hack or extend CI_Loader an autoloader is useful in these cases.
There are lots of ways to add an autoloader but my preference is to use CI's Hooks feature. Here's how.
In config.php set 'enable_hooks' to TRUE
$config['enable_hooks'] = TRUE;
These lines go in /application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
The following is the contents of /application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
/**
* Allows classes that do not start with CI_ and that are
* stored in these subdirectories of `APPPATH`
* (default APPPATH = "application/" and is defined in "index.php")
* libraries,
* models,
* core
* controllers
* to be instantiated when needed.
* #param string $class Class name to check for
* #return void
*/
function my_autoloader($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.'libraries/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'models/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'core/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'controllers/'.$class.'.php'))
{
require_once $file;
}
}
}
The function log_message($level, $message) could be used in the above if you wanted.
If you are using some other creative folder structure you will have to modify the above to accommodate that layout.
I'm finding myself dealing with the same problem in multiple bundles I've wrote.
The problem is that in my BundleNameBundle class I have to create the path to then load the mappings of Doctrine.
To do this I do something like:
/**
* {#inheritdoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$modelDir = realpath(__DIR__ . '/Resources/config/doctrine/mappings');
$mappings = [
$modelDir => 'SerendipityHQ\Bundle\QueuesBundle\Model',
];
$ormCompilerClass = DoctrineOrmMappingsPass::class;
if (class_exists($ormCompilerClass)) {
$container->addCompilerPass(
$this->getYamlMappingDriver($mappings)
);
}
$container->addCompilerPass(new DaemonDependenciesPass());
}
Complete code here.
As you can see I use __DIR__ to get the path to the folder where the mappings are.
Now, Sensio Insights is alerting me that "Absolute path constants DIR and FILE should not be used".
Ok, but how can I solve this problem? Is there some alternative way to build the path to the mappings?
You can use $this->path. It returns the same result as __DIR__
Background
We have a (fairly typical?) arrangement for a multilingual Symfony CMF website, where resource paths are prefixed by the desired locale—for example:
http://www.example.com/en/path/to/english-resource.html; and
http://www.example.com/fr/voie/à/ressource-française.html.
We are using RoutingAutoBundle to store such routes in the content repository, and DynamicRouter to utilise them: simple and easy.
If a GET request arrives without a locale prefix, we would like to:
determine the most appropriate locale for the user; and then
redirect1 the user to the same path but with locale prefix added.
Current Approach
The first part is an obvious candidate for LuneticsLocaleBundle, with router higher in its guessing order than our desired fallback methods: again, simple and easy.
However, how best to implement the second part is a little less obvious. Currently we have configured Symfony's default/static router to have a lower priority in the routing chain than DynamicRouter, and have therein configured a controller as follows:
/**
* #Route("/{path}", requirements={"path" = "^(?!(en|fr)(/.*)?$)"})
* #Method({"GET"})
*/
public function localeNotInUriAction()
{
$request = this->getRequest();
$this->redirect(
'/'
. $request->getLocale() // set by Lunetics
. $request->getRequestUri()
);
}
But this feels rather hacky and I'm on the search for something "cleaner".
A better way?
Initially I thought to modify LuneticsLocaleBundle so that it would fire an event whenever a guesser determines the locale, thinking that if it was not the RouterLocaleGuesser then we could infer that the requested URI did not contain a locale. However this clearly isn't the case, since the RouterLocaleGuesser will only determine the locale if there was a route in the first place—so I'd not have made any progress.
I'm now a bit stuck for any other ideas. Perhaps I'm already doing the right thing after all? If so, then all I need to do is find some way to inject the permitted locales (from the config) into the requirement regex…
External redirection, i.e. via a response with HTTP 302 status.
we use a custom 404 handler and lunetics:
exception_listener:
class: AppBundle\EventListener\ExceptionListener
arguments:
container: "#service_container"
tags:
- { name:"kernel.event_listener", event:kernel.exception, handler:onKernelException }
and the php class
class ExceptionListener
{
/**
* #var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($this->container->getParameter('kernel.debug')) {
// do not interfere with error handling while debugging
return;
}
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$this->handle404($event);
return;
}
// ...
}
public function handle404(GetResponseForExceptionEvent $event)
{
$request = $event->getRequest();
if (preg_match('#^\/(de|fr|en)\/#', $request->getPathInfo())) {
// a real 404, these are nicely handled by Twig
return;
}
// i *think* that the locale is not set on the request, as lunetics comes after routing, and the routing will raise the 404
$bestLang = $this->container->get('lunetics_locale.guesser_manager')->runLocaleGuessing($request);
if (! $bestLang) {
$bestLang = 'de';
}
$qs = $request->getQueryString();
if (null !== $qs) {
$qs = '?'.$qs;
}
$url = $request->getSchemeAndHttpHost() . $request->getBaseUrl() . '/' . $bestLang . $request->getPathInfo() . $qs;
$this->redirect($event, $url);
}
it would be nicer to also check if the target path actually exists - as is, we will redirect /foobar to /de/foobar and display a 404 for that one, which is not that elegant.
Is it possible (and how) to
determine if a user is using a mobile device
force symfony 2 to load different template in that case
(and fall back the default html template)
What id like to do is, to load different templates without modifying any controller.
UPDATE
It wasn't the detection part the real issue here, it's really nothing to do with symfony. It can be done (load different template) on a controller level:
public function indexAction()
{
$format = $this->isMobile() ? 'mob' : 'html';
return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');
}
But can it be done globally? Like a service, or something that execute before every request, and make changes in the templating rules.
Ok, so I don't have a full solution but a little more than where to look for one :)
You can specify loaders (services) for templating item in app/config/config.yml
framework:
esi: { enabled: true }
#translator: { fallback: %locale% }
secret: %secret%
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: %kernel.debug%
form: true
csrf_protection: true
validation: { enable_annotations: true }
templating:
engines:
- twig
loaders: [moby.loader]
default_locale: %locale%
trust_proxy_headers: false
session: ~
Then define the mentioned loader service:
services:
moby.loader:
class: Acme\AppBundle\Twig\Loader\MobyFilesystemLoader
arguments: ["#templating.locator", "#service_container"]
After that define your loader service class:
namespace Acme\AppBundle\Twig\Loader;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\Storage\FileStorage;
class MobyFilesystemLoader extends FilesystemLoader
{
protected $container;
public function __construct($templatePathPatterns, $container)
{
parent::__construct($templatePathPatterns);
$this->container = $container;
}
public function load(\Symfony\Component\Templating\TemplateReferenceInterface $template)
{
// Here you can filter what you actually want to change from html
// to mob format
// ->get('controller') returns the name of a controller
// ->get('name') returns the name of the template
if($template->get('bundle') == 'AcmeAppBundle')
{
$request = $this->container->get('request');
$format = $this->isMobile($request) ? 'mob' : 'html';
$template->set('format', $format);
}
try {
$file = $this->locator->locate($template);
} catch (\InvalidArgumentException $e) {
return false;
}
return new FileStorage($file);
}
/**
* Implement your check to see if request is made from mobile platform
*/
private function isMobile($request)
{
return true;
}
}
As you can see this isn't the full solution, but I hope that this, at least, points you to the right direction.
EDIT: Just found out that there is a bundle with mobile detection capabilities, with custom twig engine that renders template file depending on a device that sent request
ZenstruckMobileBundle, although I never used it so... :)
Well, you can use LiipThemeBundle.
You can utilize kernel.view event listener. This event comes to action when controller returns no response, only data. You can set reponse according to user agent property. For example
In your controller,
public function indexAction()
{
$data = ... //data prepared for view
$data['template_name'] = "AcmeBlogBundle:Blog:index";
return $data;
}
And the in your kernel.view event listener,
<?php
namespace Your\Namespace;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
Class ViewListener
{
/**
* #var EngineInterface
*/
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$data = $event->getControllerResult(); //result returned by the controller
$templateName = $data['template_name'];
$format = $this->isMobile() ? 'mob' : 'html'; //isMobile() method may come from a injected service
$response = $this->templating->renderResponse($templateName . "." . $format . "twig", $data);
$event->setResponse($response);
}
}
Service definition,
your_view_listener.listener:
class: FQCN\Of\Listener\Class
arguments: [#templating]
tags:
- { name: kernel.event_listener, event: kernel.view, method: onKernelView }
This is what did the trick for me in Symfony 2.0:
Override twig.loader service so we can set our custom class:
twig.loader:
class: Acme\AppBundle\TwigLoader\MobileFilesystemLoader
arguments:
locator: "#templating.locator"
parser: "#templating.name_parser"
And create our custom class, that just sets "mob" format to the templates in case the client is a mobile device:
namespace Acme\AppBundle\TwigLoader;
use Symfony\Bundle\TwigBundle\Loader\FilesystemLoader;
class MobileFilesystemLoader extends FilesystemLoader
{
public function findTemplate($template)
{
if ($this->isMobile()) {
$template->set('format', 'mob');
}
return parent::findTemplate($template);
}
private function isMobile()
{
//do whatever to detect it
}
}
I would suggest that this is not best handled by the controller but by CSS media queries, and serving a separate stylesheet to different classes of devices based on the results of that CSS media query.
A good intro here:
http://www.adobe.com/devnet/dreamweaver/articles/introducing-media-queries.html
and I would try reading http://www.abookapart.com/products/responsive-web-design in great detail. Some thinking has been done since the book was published, but it will get you headed the right direction.
From my experiences, you can but by specifying a format in the first place - check these docs, they may be able to assist you
I think is nothing to do with symfony. Templates are for the VIEW. You may achieve this by using different CSS for the same template to get different layout (template). I am using jQuery and CSS to handle different devices. You may want to look at some source code of the UI from http://themeforest.net/; specifically this template. This is one handles different device.
Alternative: https://github.com/suncat2000/MobileDetectBundle
I found it quite good compared to https://github.com/kbond/ZenstruckMobileBundle and https://github.com/liip/LiipThemeBundle