I have a symfony project with multiple skins/templates that have their own routes, does anyone have an idea for a correct setup?
Every skin/template is its own bundle, since its not just skins and assets, but maybe also services that might exist in some skins.
Hostname decides the skin.
Using a custom RouteLoader to load the route.yml of the target bundle.
The custom RouteLoader does the job--but the generated routes are getting cached, and as far as i understand, there is no way to prevent route caching.
Some suggestions are:
Creating a /{dynamic} route, so manually form routes.. But i dont want to throw away that piece of functionality of the router, or refactor the entire project..
Prefix the routes with the template identifier. This would require me to load all route.yml files, which isnt possible since their share paths.
Anyone? I cant go with multiple projects really, the amount of skins will be around 20-30~.
The reason for this setup is because its a target of Content-as-a-Service .. service, multiple clients use the project as a platform, and their setting decides which templates gets used.
It sounds like you want to dynamically load bundles based on the host name? Not going to happen with Symfony 2 because of the caching. Especially the services.
Your best bet is to setup an app for each skin and then do some url majic to execute the desired app.php file. Clearly since you have defined a bundle for each skin then there is a finite number so having multiple apps should not be much or a burden.
It's possible that you might be able to work around the template issue. You would still need to load all your skin bundles but you could futz around with the template names or paths and probably get something to work.
But services? Unless you start appending host names to service id's then I don't see any work around.
I think it's possible to load dynamically twig templates depending of your user by adding a listener on kernel requests.
I can give you a piece of code which, I hope, could help you :
/**
* On Kernel Request triggers the request to get the user config
* then adds TWIG paths depending on user TemplateName
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
//$userConfig = Retrieve your user config
if (null === $userConfig->getTemplateConfig()->getTemplate()->getName())
{
throw new TemplateConfigNotFoundException(sprintf("Could not find TemplateConfig for %s", $userConfig->getName()));
}
$template = $userConfig->getTemplateConfig()->getTemplate()->getName();
$path = sprintf('%s/../../%s/Resources/views', __DIR__, ucfirst($template));
if (!is_dir($path)) {
throw new TemplateNotFoundException(sprintf("Could not find template %s", $template));
}
$this->loader->prependPath($path);
$this->loader->addPath(sprintf('%s/../Resources/views/Default', __DIR__));
}
With $this->loader defined as \Twig_Loader_Filesystem in your Listener constructor
Hope it can give you a clue
Symfony2 already supports host aware routing out-of-the-box, like this:
website_customer_1:
path: /
host: customer1.example.com
defaults: { _controller: Customer1Bundle:Main:startPage, theme: template1 }
website_customer_2:
path: /
host: customer2.example.com
defaults: { _controller: Customer1Bundle:Main:startPage, theme: template2 }
Related
I have a Symfony application that handles request from multiple domains. On these domains, customers have the ability to create custom Twig templates that can be used in their CMS module. These templates are saved to a /path/to/ftp/example.com/cms_templates/ directory.
In previous versions of Symfony, rendering these templates was not an issue. On an incoming request on mydomain.com/page_one, the absolute path of the Twig template could be resolved to e.g. /path/to/ftp/mydomain.com/cms_templates/page_one.html.twig. The existence of the template was verified and the template was rendered. However, in Symfony 4 support for absolute template paths has been removed.
I thought of registering an extra Twig namespace in twig.paths called 'custom' that points to /path/to/ftp and then reference the templates with #custom/mydomain.com/cms_templates/page_one.html.twig. The problem is, /path/to/ftp contains not only templates but loads and loads of other files too. And the TemplateCacheWarmer is looping through all of these files.
What I'am actually looking for is a way to register a Twig namespace, say #cms that is resolved on the fly such that I can inject the hostname of the current request in it. I would then be able to reference a template using #cms\page_one.html.twig. Cache warmup isn't necessary for these templates.
How to achieve such functionality? I looked ad the Twig documentation but it looks like I just can't figure it out.
TLDR; How to get Symfony/Twig to resolve the template #foo\template.html.twig to /path/to/domain.com/templates/template.html.twig when domain.com differs between requests.
One solution would be to prepend configuration
public function prepend(ContainerBuilder $container)
{
$bundles = $container->getParameter('kernel.bundles');
if (isset($bundles['TwigBundle'])) {
$config = $container->getExtensionConfig('twig')[0];
$paths = ['/path/to/cms' => 'cms'];
if (array_key_exists('path', $config)) {
$paths = array_merge($config['paths'], $paths);
}
$config['paths'] = $paths;
$container->prependExtensionConfig('twig', $config);
}
}
The problem would be this:
If you are building some kind of CMS - then this would mean - no other bundle/module/plugin could ever register additional twig namespaces again - if I am right.
So... I am interested in other solutions if there are some.
I am working on a newly created Phalcon project, and I don't really know how to actually use multiples views.
What is the entry point? I don't really know when each method in the controller is called, under which conditions, etc.
Where is the control flow defined? is it based in the name of the view? or is there a place where you can register them?
Phalcon is a bit different than other well-known PHP frameworks, in that not much is pre-configured or pre-built by default. It's quite loosely-coupled. So you have to decide where and how your control flow will work. This means that you will need to dig deeper in the documentation and also that there could be different way to achieve the same thing.
I'm going to walk you through a simple example and provide references, so you can understand it more.
1) You would start by defining a bootstrap file (or files) that will define the routes, or entry points, and will setup and create the application. This bootstrap file could be called by an index.php file that is the default file served by the web server. Here is an example of how such bootstrap file will define the routes or entry points (note: these are just fragments and do not represent all the things that a bootstrap file should do):
use Phalcon\Di\FactoryDefault;
// initializes the dependency injector of Phalcon framework
$injector = new FactoryDefault();
// defines the routes
$injector->setShared('router', function () {
return require_once('some/path/routes.php');
});
Then it the routes.php file:
use Phalcon\Mvc\Router;
use Phalcon\Mvc\Router\Group as RouterGroup;
// instantiates the router
$router = new Router(false);
// defines routes for the 'users' controller
$user_routes = new RouterGroup(['controller' => 'users']);
$user_routes->setPrefix('/users');
$user_routes->addGet('/show/{id:[0-9]{1,9}}', ['action' => 'show']);
$router->mount($user_routes);
return $router;
Im defining routes in an alternate way, by defining routes groups. I find it to be more easy to organize routes by resource or controller.
2) When you enter the url example.com/users/show/123, the routes above will match this to the controller users and action show. This is specified by the chunks of code ['controller' => 'users'], setPrefix('/users') and '/show/{id:[0-9]{1,9}}', ['action' => 'show']
3) So now you create the controller. You create a file in, let's say, controllers/UsersController.php. And then you create its action; note the name that you used in the route (show) and the suffix of Action:
public function showAction(int $id) {
// ... do all you need to do...
// fetch data
$user = UserModel::findFirst(blah blah);
// pass data to view
$this->view->setVar('user', $user);
// Phalcon automatically calls the view; from the manual:
/*
Phalcon automatically passes the execution to the view component as soon as a particular
controller has completed its cycle. The view component will look in the views folder for
a folder named as the same name of the last controller executed and then for a file named
as the last action executed.
*/
// but in case you would need to specify a different one
$this->view->render('users', 'another_view');
}
There is much more stuff related to views; consult the manual.
Note that you will need to register such controller in the bootstrap file like (Im also including examples on how to register other things):
use Phalcon\Loader;
// registers namespaces and other classes
$loader = new Loader();
$loader->registerNamespaces([
'MyNameSpace\Controllers' => 'path/controllers/',
'MyNameSpace\Models' => 'path/models/',
'MyNameSpace\Views' => 'path/views/'
]);
$loader->register();
4) You will also need to register a few things for the views. In the bootstrap file
use Phalcon\Mvc\View;
$injector->setShared('view', function () {
$view = new View();
$view->setViewsDir('path/views/');
return $view;
});
And this, together with other things you will need to do, particularly in the bootstrap process, will get you started in sending requests to the controller and action/view defined in the routes.
Those were basic examples. There is much more that you will need to learn, because I only gave you a few pieces to get you started. So here are some links that can explain more. Remember, there are several different ways to achieve the same thing in Phalcon.
Bootstrapping:
https://docs.phalconphp.com/en/3.2/di
https://docs.phalconphp.com/en/3.2/loader
https://docs.phalconphp.com/en/3.2/dispatcher
Routing: https://docs.phalconphp.com/en/3.2/routing
Controllers: https://docs.phalconphp.com/en/3.2/controllers
More on Views (from registering to passing data to them, to templating and more): https://docs.phalconphp.com/en/3.2/views
And a simple tutorial to teach you some basic things: https://docs.phalconphp.com/en/3.2/tutorial-rest
The application begins with the routing stage. From there you grab the controller and action from the router, and feed it to the dispatcher. You set the view then call the execute the dispatcher so it access your controller's action. From there you create a new response object and set its contents equal to the view requests, and finally send the response to the client's browser -- both the content and the headers. It's a good idea to do this through Phalcon rather than echoing directly or using PHP's header(), so it's only done at the moment you call $response->send(); This is best practice because it allows you to create tests, such as in phpunit, so you can test for the existence of headers, or content, while moving off to the next response and header without actually sending anything so you can test stuff. Same idea with exit; in code, is best to avoid so you can write tests and move on to the next test without your tests aborting on the first test due to the existence of exit.
As far as how the Phalcon application works, and in what steps, it's much easier to follow the flow by looking at manual bootstrapping:
https://docs.phalconphp.com/en/3.2/application#manual-bootstrapping
At the heart of Phalcon is the DI, the Dependency Injection container. This allows you to create services, and store them on the DI so services can access each other. You can create your own services and store them under your own name on the DI, there's nothing special about the names used. However depending on the areas of Phalcon you used, certain services on the DI are assumed like "db" for interacting with your database. Note services can be set as either shared or not shared on the DI. Shared means it implements singleton and keeps the object alive for all calls afterwards. If you use getShared, it does a similar thing even if it wasn't initially a shared service. The getShared method is considered bad practice and the Phalcon team is talking about removing the method in future Phalcon versions. Please rely on setShared instead.
Regarding multiple views, you can start with $this->view->disable(); from within the controller. This allows you to disable a view so you don't get any content generated to begin with from within a controller so you can follow how views work from within controllers.
Phalcon assumes every controller has a matching view under /someController/someView followed by whatever extension you registered on the view, which defaults to .volt but can also be set to use .phtml or .php.
These two correspond to:
Phalcon\Mvc\View\Engine\Php and Phalcon\Mvc\View\Engine\Volt
Note that you DON'T specify the extension when looking for a template to render, Phalcon adds this for you
Phalcon also uses a root view template index.volt, if it exists, for all interactions with the view so you can use things like the same doctype for all responses, making your life easier.
Phalcon also offers you partials, so from within a view you can render a partial like breadcrumbs, or a header or footer which you'd otherwise be copy-pasting into each template. This allows you to manage all pages from the same template so you're not repeating yourself.
As far as which view class you use within Phalcon, there's two main choices:
Phalcon\Mvc\View and Phalcon\Mvc\View\Simple
While similar, Phalcon\Mvc\View gives you a multiple level hierarchy as described before with a main template, and a controller-action based template as well as some other fancy features. As far as Phalcon\Mvc\View\Simple, it's much more lightweight and is a single level.
You should be familiar with hierarchical rendering:
https://docs.phalconphp.com/en/3.2/views#hierarchical-rendering
The idea is with Phalcon\Mvc\View that you have a Main Layout (if this template exists) usually stored in /views/index.volt, which is used on every page so you can toss in your doctypes, the title (which you would set with a variable the view passed in), etc. You'd have a Controller Layout, which would be stored under /views/layouts.myController.volt and used for every action within a controller (if this template exists), finally you'd have the Action Layout which is used for the specific action of the controller in /views/myController/myAction.volt.
There are all types of ways you can break from Phalcon's default behavior. You can do the earlier stated $this->view->disable(); so you can do everything manually yourself so Phalcon doesn't assume anything about the view template. You can also use ->pick to pick which template to use if it's going to be different than the controller and action it's ran in.
You can also return a response object from within a controller and Phalcon will not try to render the templates and use the response object instead.
For example you might want to do:
return $this->response->redirect('index/index');
This would redirect the user's browser to said page. You could also do a forward instead which would be used internally within Phalcon to access a different controller and/or action.
You can config the directory the views are stored with setViewsDir. You can also do this from within the controller itself, or even within the view as late as you want, if you have some exceptions due to a goofy directory structure.
You can do things like use $this->view->setTemplateBefore('common') or $this->view->setTemplateAfter('common'); so you can have intermediate templates.
At the heart of the view hierarchy is <?php echo $this->getContent(); ?> or {{ content() }} if you're using Volt. Even if you're using Volt, it gets parsed by Phalcon and generates the PHP version with $this->getContent(), storing it in your /cache/ directory, before it is executed.
The idea with "template before" is that it's optional if you need another layer of hierarchy between your main template and your controller template. Same idea with "template after" etc. I would advise against using template before and after as they are confusing and partials are better suited for the task.
It all depends on how you want to organize your application structure.
Note you can also swap between your main template to another main template if you need to swap anything major. You could also just toss in an "if" statement into your main template to decide what to do based on some condition, etc.
With all that said, you should be able to read the documentation and make better sense of how to utilize it:
https://docs.phalconphp.com/en/3.2/api/Phalcon_Mvc_View
I'm new at Symfony 2, now I'm trying to get a dynamic routing, I mean really dynamic.
For example:
example.com/en/categoryLevel1/categoryLevel2/categoryLevel3/productId-ProductName
OR
example.com/en/categoryLevel1/categoryLevel2/productId-ProductName
OR
example.com/en/categoryLevel1/categoryLevel2/categoryLevel3/
The number of category levels (the category depth) have to be flexible to 100%. It must be possible and able to use one level to twenty levels.
Where is the entry point to setup this (which classes are doing those routing stuff)?
Another example is:
routes on the old page:
example.com/{categoryLvl1}/{categoryLvl2}/.../p-{productId}
at the new page are some changes in the routes:
example.com/{lang}/{catLevel1}/{catLevel2}/.../{productId}-{productName}
how i do the regex, etc.. i know. But i can't find the routing process in symfony (better the pre-routing process). I would like to build an pre-routing class and fallback the "normal" symfony2 routing. i have to match old and new, both are completely dynamic.. the old one is written in ZF1 (pretty easy for me) but symfony2 is a new area for me...
Let's assume you have a bundle that handles this type of URL, you might add the following in the bundle's routing.yml (I prefer yml, YMMV).
YourSomethingBundle_main_any:
pattern: /{request}
defaults: { _controller: YourSomethingBundle:Main:dispatcher }
requirements:
request: ".*"
Important: This is a “catch-all”, letting you process the actual request path in your controller. You should either prefix the pattern path, or load this bundle after all other bundles, or other routes will no longer work.
As per SF2 conventions, you would now have a MainController class with a dispatcherAction method:
<?php
namespace Your\SomethingBundle\Controller;
use \Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MainController extends Controller
{
public function dispatcherAction($request='')
{
$request = preg_split('|/+|', trim($request, '/'));
// ... and so on.
}
}
I am working on a new website being built in SilverStripe. Currently I am having a ton of trouble trying to get the system to let me change the URL alias (or create a second one) for the Security controller's login (and eventually logout) function.
I have tried playing around with the routes.yml file and I tried creating the paths in my own UserController and loading directly from the Security controller with "return Security::login()". But that gives me errors about the use of the static functions.
Unfortunately I don't come from a ton of object oriented experience and this is the first CMS I have used that actually uses a bunch of true object orientation. The current version of SilverStripe we are using is 3.0 (but we will be upgrading to 3.1.1 in a few days).
Does anyone know much about the routing in SilverStripe?
as you stated correctly, SilverStripe has routes, and they can be defined in a yaml config file.
if you take a look at the existing routes.yml in the framework you can see how the default security route is defined:
https://github.com/silverstripe/silverstripe-framework/blob/fd6a1619cb7696d0f7e3ab344bc5ac7d9f6cfe77/_config/routes.yml#L17
if you just want to replace the Secuirty in Secuirty/login, its as easy as just creating your own routes.ymlin mysite/_config/ with the following content:
---
Name: myroutesorsomeotherrandomname
Before: '*'
After:
- '#rootroutes'
- '#coreroutes'
- '#modelascontrollerroutes'
- '#adminroutes'
---
Director:
rules:
'foobar//$Action/$ID/$OtherID': 'Security'
NOTE: make sure you ran a ?flush=1 to ensure the yml file is loaded (they get cached)
also make sure you use spaces in the yml file, if you use tabs you are going to have a bad time
if you however wish to also replace /login and /logout this is no longer a thing for routing.
login and logout are actions (php functions that are listed in Security::$allowed_actions and thus available as URL) on the on Security.
but its still rather easy, just subclass Security and create the actions you want:
<?php
class MySuperSecurity extends Security {
private static $allowed_actions = array(
'showMeTheLoginForm',
'alternative_login_action_with_dashes',
'doTheLogout',
);
function showMeTheLoginForm() {
// can be visited via http://website.com/foobar/showMeTheLoginForm
return $this->login();
}
function alternative_login_action_with_dashes() {
// can be visited via http://website.com/foobar/alternative-login-action-with-dashes
return $this->login();
}
function doTheLogout($redirect = true) {
// can be visited via http://website.com/foobar/doTheLogout
return $this->logout($redirect);
}
}
and make the route point to your new class instead of Security inside the routes.yml:
'foobar//$Action/$ID/$OtherID': 'MySuperSecurity'
NOTE: again, make sure you did a ?flush=1, both the private static $allowed_actions as well as the yml config file are cached by silverstripe.
NOTE: both solutions suggested in this post will create an additional route to login and does not replace the existing one, so the old Security/login will still be available
I don't know nothing about SilverStripe excepting that is a CMS, but i think SilverStripe must provide a way to aliases Url. Also an alternative is create Alias in virtual host definition if you're using apache or in .htaccess file. Refer to apache doc to further details. If you post a skeleton of your .htaccess file or VirtualHost definition i could help you.
If I wanted to make it so that every url call apart from ones I have defined after act upon /ExplicitControllerName/ExplicitActionToRun... how might the routing look like.
for example some pseudo code:
default_pathing:
pattern: /{controller}/{action}
defaults: { _controller: Bundle:Default:index }
So if I went to
www.example.com/Page/About
it would call my controller
class Page extends Controller
{
public AboutAction()
{
// Called by above URL
}
}
This question does not answer: Symfony2 / routing / use parameters as Controller or Action name
Imagine I have 100 pages with lots of sub routing pages doing pretty much the same routing every time. I want to do 1 routing for all those 100 controllers. How would we do this?
P.S I'm really going for something like the C#.NET MVC 4.0 routing in which it allows you to set a routing for a typical setup you might have even if at the very least its for development
Your question is not totally clear but here are some hints.
I can imagine two use cases you're trying to solve:
1) You've a lot of some sort of CMS page, like your about example, these pages don't have much logic and just render some view, in such case you would something like:
class CMSPageController
{
public function renderPage($pageSlug)
{
$page = $this->cmsPageRepository->findBySlug($pageSlug);
// your logic to render the view
}
}
And the according routing configuration:
<route id="cms_page_view" pattern="/cms/{pageSlug}">
<default key="_controller">cms_page.controller.page:renderPage</default>
<requirement key="_method">GET</requirement>
<requirement key="slug">[0-9a-zA-Z\-\.\/]+</requirement>
</route>
2) You have much more complex requirements, and/or follow a specific pattern to name your controller/action, therefore you need to write a custom UrlMatcherInterface implementation. Take a look at the native implementation to know where to start. It would allow you define a fallback.
This can be achieved using either SensioFrameworkExtraBundle's #Route annotation on class- and method-level excessively...
... or more elegant with less annotations using FOSRestBundle's automatic route generation with implicit resource names. Maybe you'll need to correct some of the generated routes using some of FOSRestBundle's manual route definition annotations.
Both methods originally still leave the need to explicitly add the route resources to your app/config/routing.yml.
Example import for #Route
# import routes from a controller directory
blog:
resource: "#SensioBlogBundle/Controller"
type: annotation
Example import for FOSRestBundle
users:
type: rest
resource: Acme\HelloBundle\Controller\UsersController
You could work around having to import all the resources by:
introducing a custom annotation (class-level)
creating a compiler pass or a custom route loader in which you ...
use the Finder to find all controller classes in all bundles with that annotation
finally add all those as resources with type annotation/rest to the route collection in there
If you don't plan to use hundreds of controllers and don't have too much experience with compiler-passes, custom annotations, ... etc. you'll definitely be faster just registering the resources in the routing config.