Removing Symfony2 scope widening notice - php

I'm trying to speed up my stack by removing references to the service container where possible. In this case I only need the request:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
arguments: [ #request, %params ]
However, that throws a scope widening issue. I'm not concerned with refactoring the code for now, I just wish to get rid of the warning by adding strict = true. But I can't seem to get the YAML syntax right:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
arguments:
- { type: service, id: request, strict: false }
- %params%
This isn't working though. Any ideas?
EDIT
I realise I could change the scope of this service to request, but that isn't an option in this case.

You want to restrict the scope of the service to the request scope, since you need to make sure you're passed the right Request instance - if you are using the service from within a subrequest for example, or whether through the main request. Adjust your service config to:
services:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
scope: request
arguments: [ #request, %params% ]
See the docs for more details.
Edit as per your question edit, you're not able to change the scope. In which case, your syntax should be as follows:
services:
email_error_message:
class: Core\MyBundle\Services\Email\ErrorMessage
arguments: [ #request=, %params% ]
with the appended = symbol. Note that I've not seen this referenced anywhere, and it's from digging around in the code for the DI container ;-)

Related

How can I support multiple dynamic hosts within Symfony Routing?

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?

Create a Cache Provider class in Symfony

We have memcache on our Symfony 3.4 app:
cache:
app: cache.adapter.memcached
default_memcached_provider: "%app.memcached.dsn%"
However, we've been asked to use several cache servers, so just passing one DSN is no good.
Looking here (https://symfony.com/blog/new-in-symfony-3-3-memcached-cache-adapter), I see you can create it in code like this:
$client = MemcachedAdapter::createConnection(array(
// format => memcached://[user:pass#][ip|host|socket[:port]][?weight=int]
// 'weight' ranges from 0 to 100 and it's used to prioritize servers
'memcached://my.server.com:11211'
'memcached://rmf:abcdef#localhost'
'memcached://127.0.0.1?weight=50'
'memcached://username:the-password#/var/run/memcached.sock'
'memcached:///var/run/memcached.sock?weight=20'
));
However, that isn't autowired.
I believe we need to either make a provider class, or somehow get it to make calls to addServer($dsn), once instantiated. I also saw the following on random posts:
memcache:
class: Memcached
calls:
- [ addServer, [ %app.memcached.dsn.1% ]]
- [ addServer, [ %app.memcached.dsn.2% ]]
However it isn't really helping or I have missed something out.
Can anyone help? How do I create this provider class?
You can copy above code snippet as a service configuration to your services.yaml, which probably roughly looks like this:
# app/config/services.yaml
services:
app.memcached_client:
class: Memcached
factory: 'Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection'
arguments: [['memcached://my.server.com:11211', 'memcached://rmf:abcdef#localhost']]
app.memcached_adapter:
class: Symfony\Component\Cache\Adapter\MemcachedAdapter
arguments:
- '#app.memcached_client'
Then in your configuration you should be able to reference the adapter using the client created by the factory, e.g. something like:
# app/config/config.yaml
framework:
cache:
app: app.memcached_adapter
You might also be able to overwrite the default alias cache.adapter.memcached instead of having your own adapter.
Your approach using Memcached::addServer might work as well, but just like with MemcachedAdapter::createConnection this will return the Client, which needs to be passed to the cache adapter. That's why there is a second service app.memcached_adapter, which is used in the cache configuration.
Please be aware that I have not tested this, so this is rather a rough outline than a fully working solution,
For one of my projects running Symfony 3.4 the configuration was simpler:
Create a service that will be used as a client:
app.memcached_client:
class: Memcached
factory: ['AppBundle\Services\Memcached', 'createConnection']
arguments: ['memcached://%memcache_ip%:%memcache_port%']
The AppBundle\Services\Memcached can have all the custom logic I need like so:
class Memcached
{
public static function createConnection($dns)
{
$options = [
'persistent_id' => 'some id'
];
// Some more custom logic. Maybe adding some custom options
// For example for AWS Elasticache
if (defined('Memcached::OPT_CLIENT_MODE') && defined('Memcached::DYNAMIC_CLIENT_MODE')) {
$options['CLIENT_MODE'] = \Memcached::DYNAMIC_CLIENT_MODE;
}
return \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection($dns, $options);
}
}
And then I used that service in my config.yml:
framework:
cache:
default_memcached_provider: app.memcached_client

Symfony2 Service reference from mustache

i have a mustache template that has the following line
{{{widgets.service_name.js.footer}}}
i found the corresponding service defined in a symfony2 bundle
service_name:
class: A\B\C\D\EventListener\AssetsListener
arguments:
- #templating
- %a.b.timestamp%
- %kernel.environment%
tags:
- { name: kernel.event_listener, event: kernel.response, method: injectAsset, priority: -255}
Is there a mustache specific meaning in .js.footer (prefix to the symfony2 service name) i am unable to find any reference related to this.
Anyone could provide some pointers i would greatly appreciate it.
Thanks
Dots in mustache tags are equivalent to either array access, properties or method calls. So this:
{{{widgets.service_name.js.footer}}}
Means something like this:
$widgets['service_name']['js']['footer'];
$widgets['service_name']->js->footer;
$widgets['service_name']->js()->footer();
… or some combination of the above. Which it actually translates to depends on what the service is, what public methods or properties it exposes, and what they return.
Here's more on Mustache dot notation, and on variable resolution in Mustache.php.

Getting the file_locator service in symfony2 custom router

I want to create a custom route loader as instructed in http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html.
What I have to do is read the read the routes from an xml file (not in "symfony xml" format) and create the according route collection.However I want to do that using the '#' directive.as in:
xmlRoutes:
resource: '#FooBarBundle/Resources/routes.xml'
But in order to resolve the path to routes.xml I need the 'file_locator' service from the container.is it possible to access services in a custom router class.if not, how can I make a symfony\Component\Config\FileLocator to resolve that path?
Yes you could access the file_locator as it's a service. What you need to do is make your custom_route_loader a service itself (I dind't read the cookbook you linked but I'm pretty sure that they would advice to define it as a service) and inject the file_locator service into it.
So basically you'll do something like
#config.yml
[...]
services:
yourbundlename.custom_route_loader:
class: Path\To\Your\Bundle\CustomRouteLoader
arguments: [ #file_locator ]
And into you CustmRouteLoaderClass
#Path\To\Your\Bundle\CustomRouteLoader
class CustomRouteLoader
{
public function __construct(Symfony\Component\HttpKernel\Config\FileLocator $file_locator) {
$this->file_locator = $file_locator;
[...]
}
[...]
}

Symfony 2 #Route annotation case-sensitive

I'm trying to create a url with annotations of the route.
The problem is that I can write any URL large, small or different.
#Route("/{staat}/", name="showStaats",requirements={"location" = "berlin|bayern|brandenburg"})
This URL can be accessed both from www.example.com/berlin and under www.example.com/Berlin.
I would, however, that it is attainable only under www.example.com/berlin.
Answering the question "How to make case-insensitive routing requirement":
You can add case-insensitive modifier to requirement regexp like so:
(?i:berlin|bayern|brandenburg)
You have "/{staat}/", but your requirements set "location" = ..., these should match, so maybe that's the cause of your problem.
If you don't want to hardcode the list of states in your route, you could inject a service containter parameter with a list of states. Just see How to use Service Container Parameters in your Routes in the documentation for how to do that.
If you just want to check, whether that state is all lower-cased you could try the following requirement:
staat: "[a-z-]+"
This should match only lowercase characters and dash (e.g. for "sachsen-anhalt"). But I'm not entirely sure if this will work as the router's regex-detection is a bit quirky.
You could also create a custom Router Loader which will create routes programmatically, e.g. by fetching the list of states from a database or file.
edit:
As I wrote in my comment I would add the list of params as a Service Container parameter, e.g. %my_demo.states% containing a list of states. I'm not sure however if this will work with annotations. So here is a quick workaround how to get it working.
In your app/config/config.yml you append the %my_demo.states% parameter:
my_demo:
states: ["berlin", "brandenburg", "sachsen-anhalt", ... ]
In your app/config/routing.yml there should be something like this:
my_demobundle:
resource: "#MyDemoBundle/Controller/"
prefix: /
type: annotation
The type: annotation and #MyDemoBundle is the relevant part. Add the following route before this one, to make sure it takes precedence:
showStaats:
path: /{state}
defaults: { _controller: MyDemoBundle:State:index }
requirements:
state: %my_demo.states%
This will add a route which will apply before your annotations using the list of states as parameters. This is a bit crude, as you are mixing yml/annotation-based routing, but it's imo still better than cramming a list of 16 states in the annotation, not to mention its easier to maintain.

Categories