I am testing my PHP REST API. Some methods require the user to be authenticated, some do not. How can I separate my differents contexts in behat.yml to separate my two needs? In addition, for the moment for each method where the user needs to be authenticated I use the following type of feature:
Feature: Get things
#login
Scenario: I want to get a list of things
When I request a list of things from "/myendpoint"
Then The result should include a thing with id "myID"
where #login is a BeforeScenario method. Is this a good way to go?
Finally, I would like to store my base url somewhere so that it is accessible by all my contexts. My behat.yml looks like this for the moment:
default:
suites:
default:
paths:
features: 'features'
bootstrap: 'features/bootstrap'
contexts:
- MyFirstContext:
- 'http://localhost:8080'
- MySecondContext:
- 'http://localhost:8080'
- MyThirdContext:
- 'http://localhost:8080'
I access http://localhost:8080 in each context thanks to the context constructor:
public function __construct($baseUrl)
{
$this->base_url = $baseUrl;
}
I want to store http://localhost:8080 so that I don't have to rewrite it for each new context in my behat.yml. And if it is possible to store it in behat.yml, how can I access it in my context.php?
Related
In my Symfony app (Symfony 5.3) I have to support the following scenario with multiple hosts/domains that belong to multiple app contexts (i.e. own firewall and own controllers, sometimes also shared controllers):
main-domain.tld -> main_context
main-domain2.tld -> main_context
service.main-domain.tld -> service_context
service.main-domain2.tld -> service_context
service.maybe-several-other-brand-domains.tld -> service_context
admin.main-domain.tld -> admin_context
admin.main-domain2.tld -> admin_context
admin.maybe-several-other-brand-domains.tld -> admin_context
How it started
Before we had multiple brands/domains, we had two main app contexts, that are addressed by their own hostnames. So we did something like this to assign the controllers to the context:
#[Route(
path: '/',
requirements: ['domain' => '%app.public_hostname_context1%'],
defaults: ['domain' => '%app.public_hostname_context1%'],
host: '{domain}',
)]
# where app.public_hostname_context1 is a hostname configured in the .env.local
How it is going
This worked well, until we decided to have more than one valid host for one of the contexts, in fact as much as the branding needs. So I did some research and came across the problem, that I cannot access the current hostname inside the defaults config and thus would have to set the domain explicitly on every url I generate.
Question is
How would you solve that requirement?
I post my first approach of a solution as a direct answer, so please discuss it or shine with a better one. Maybe, I have overseen something and I have a slight feeling that that solution may be not the best one. And for others stumbling upon the same requirement, this whole Question will document at least one solution approach. :)
First, remove the defaults from the route definitions and provide a pattern for several valid domains of a context:
#[Route(
path: '/',
requirements: ['domain' => '%app.public_hostnames_context1_pattern%'],
host: '{domain}',
)]
# app.public_hostname_context1_pattern is a pattern configured in the .env.local
# containing all possible hostnames for that context like
# PUBLIC_HOSTNAME_CONTEXT1_PATTERN=(?:service\.main-domain\.tld|service\.main-domain2\.tld)
To set the current hostname as a default for the domain parameter for all routes, I have a RequestListener inspired by this answer from 2012 that sets it, before the RouterListener does its work.
In my services.yaml:
# must be called before the RouterListener (with priority 32) to load the domain
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.request, priority: 33 }
And the RequestListener:
<?php
declare(strict_types=1);
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;
class RequestListener
{
public function __construct(
private RouterInterface $router,
){}
public function onKernelRequest(RequestEvent $event)
{
if (false === $this->router->getContext()->hasParameter('domain')) {
$this->router->getContext()->setParameter('domain', $event->getRequest()->getHost());
}
}
}
The good part of this is, that I can still override the domain parameter when I create URLs. But a drawback I see is: When I generate a URL for another context and don't set the domain explicitly, an error will be raised, because now the host of the current request is used as the domain for the other context and that is not allowed by the pattern within the requirements. I can live with that. Do you?
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 }
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.
I'd like to store a few application specific values for example:
a default Id number for a particular user choice if it's not set yet
keys/tokens/secrets for various services API's like facebook or flickr
Closest I've found so far is http://symfony.com/doc/2.0/cookbook/bundles/best_practices.html#configuration
If I used app/config/parameters.ini it would look like:
[flickr]
callbackUrl = http://example.com/approve
requestTokenUrl = http://www.flickr.com/services/oauth/request_token
consumerKey = 123a1237a29b123a5541232e0279123
[app]
default_layout = 2
these should be available in different bundles and also in templates
these should be available in different bundles and also in templates
They are. As long as you can access the container, you can access the parameters. From the docs you linked to:
$container->getParameter('acme_hello.email.from');
I think there's an error in your parameters.ini example. 'flickr' and 'app' shouldn't be wrapped in brackets. Also, the first element of parameters.ini should be [parameters].
Personally, I like using an app.yml file because I'm used to using it in Symfony 1.x projects (and because I don't see the reason for using an .ini file.). You can create app/config/app.yml and import it into your app/config/config.yml file like this:
imports:
- { resource: app.yml }
Your app.yml would look like this:
parameters:
flickr:
callbackUrl: http://example.com/approve
requestTokenUrl: http://www.flickr.com/services/oauth/request_token
consumerKey: 123a1237a29b123a5541232e0279123
app:
default_layout: 2
And this is how you would access data:
$container->getParameter('flickr.callbackUrl');
A third option is to define your parameters directly in app/config/config.yml. The code would be exactly the same as my example for app/config/app.yml. I don't recommend doing this though because app/config/config.yml can get pretty filled up with bundle configuration parameters, and I think it's cleaner to keep your own app params in a separate file. But of course, it's all up to you.