How to programatically change the routing to add my own "filter" by keyword - symfony

My case is the following:
I have a Shopware Bundle and i need to change/extend the routing in such a way that if a request URL contains a keyword "xyz", the request is forwarded to a controller in my bundle without checking further if the route is available in "static routes" for example.
For instance: "/xyz/1/lorem/3" or "/xyz/5/3/ipsum" etc. all need to be rerouted to the controller in my bundle, since they contain the keyword /xyz.
Is there a service i can overwrite/decorate or something similar where i can implement this behaviour?

You can have a placeholder in your route with a default and a requirement allowing for all characters:
/**
* #Route("/xyz{anything}", name="frontend.my.action", methods={"GET"}, defaults={"anything"=""}, requirements={"anything"=".+"})
*/
public function myAction(Request $request): Response
{
$anything = $request->get('anything');
// ...
}
This will match any url starting with /xyz and every set of characters that follows afterwards is considered to be part of anything.

Related

Symfony Route with Slash (/) in parameter not working

I have a Symfony Route configured with annotations where I want the last parameter to allow for slashes in it.
#[Route('/getFtp/{customerName}/{taskId}/{domainName}', name: 'get_ftp', requirements: ['domainName' => '.+'])]
public function index(string $customerName, string $taskId, string $domainName): Response
According to documentation https://symfony.com/doc/6.0/routing.html#slash-characters-in-route-parameters this should work.
It works for
http://mgr2.example.com/getFtp/quadramedia/abcdef/http:
but not for
http://mgr2.example.com/getFtp/quadramedia/abcdef/http:%2F
Using your exemple in my own project worked.
I tryied this url http://127.0.0.1:8080/getFtp/thomas/23/http:%2F
And then i dumped my param to be sure and got :
So, it is on your project that something is wrong.
I expect something like an other route is matching.
Try to define the route in yaml (or just on top of your controller) and put it on top of all the other to be completely sure that this is not working.
By the way, i tried your case in php 8.1 and Symfony 6.2
It works in Symfony 5.x versions. I would suggest take domainName as a request parameter in an argument. For example:
#[Route('/getFtp/{customerName}/{taskId}', name: 'get_ftp')]
public function index(string $customerName, string $taskId, Request $request): Response
{
$domainName = $request->query->get('domainName');
...
}

Shopware 6 backend controller path

In Shopware 6, I want to call a backend (/admin) API controller from a backend / admin page using JavaScript. What is the correct way to use a relative path, probably with a built-in getter function?
Fetching /api/v1 only works if the shop is on /, but not when it is in a sub-folder.
fetch('/api/v1/my-plugin/my-custom-action', ...)
The best practice would be to write your own JS service that handles communication with your api endpoint.
We have an abstract ApiService class, you can inherit from. You can take a look at the CalculatePriceApiService for an example in the platform.
For you an implementation might look like this:
class MyPluginApiService extends ApiService {
constructor(httpClient, loginService, apiEndpoint = 'my-plugin') {
super(httpClient, loginService, apiEndpoint);
this.name = 'myPluginService';
}
myCustomAction() {
return this.httpClient
.get('my-custom-action', {
headers: this.getBasicHeaders()
})
.then((response) => {
return ApiService.handleResponse(response);
});
}
}
Notice that your api service is preconfigured to talk to your my-plugin endpoint, in the first line of the constructor, which means in all the following request you make you can use the relative route path.
Keep also in mind that the abstract ApiService will take care of resolving the configuratuion used for the Requests. Especially this means the ApiService will use the right BaseDomain including subfolders and it will automatically use an apiVersion that is supported by your shopware version. This means the apiVersion the ApiService uses in the route will increase every time a new api version is available, that means you need to work with wildcards in your backend route annotations for the api version.
Lastly keep in mind you need to register that service. That is documented here.
For you this might look like this:
Shopware.Application.addServiceProvider('myPluginService', container => {
const initContainer = Shopware.Application.getContainer('init');
return new MyPluginApiService(initContainer.httpClient, Shopware.Service('loginService'));
});
If you are talking about custom action that you implemented, you need to define route (use annotation) and register controller in routes.xml in your Resources\config\routes.xml.
Please follow that documentation
https://docs.shopware.com/en/shopware-platform-dev-en/how-to/api-controller

Symfony : Route PUT method

Anyone knows why the PUT method doesn't work using PHP Symfony?
If I replace PUT to POST everything works fine
/**
* #Route("/api/product/update", name="product_udpate", methods = {"PUT"})
*/
i am reading variables like that
$request = Request::createFromGlobals();
echo $request->request->get('name');
error:
No route found for "PUT /api/product/update/23" (404 Not Found)
The problem is you are not creating the route correctly. Basically, you need to add the "id" to the route.
/**
* #Route("/api/product/update/{id}", name="product_udpate", methods = {"PUT"})
*/
public function updateAction(Request $request, $id)
{
// Your logic here
$name = $request->get('name');
}
You got the following error because you have not configured route correctly.
error: No route found for "PUT /api/product/update/23" (404 Not Found)
If you want to add id along with your desire url, you have to define in your route.
Thus, you can update your route:
/**
* #Route("/api/product/update/{id}", name="product_udpate", methods = {"PUT"}, defaults={"id"=null})
*/
As stated in the symfony documentation How to Use HTTP Methods beyond GET and POST in Routes
Unfortunately, life isn't quite this simple, since most browsers do not support sending PUT and DELETE requests via the method attribute in an HTML form. Fortunately, Symfony provides you with a simple way of working around this limitation. By including a _method parameter in the query string or parameters of an HTTP request, Symfony will use this as the method when matching routes
So you have to fake the method like this one :
<form action='your route'>
<input type='hidden' name='_method' value='PUT'>
//do something.......
</form>

Symfony: How encode / in URL parameter?

I want to build RESTful API with URLs something like:
First route: http://example.com/api/{element_name}/aaa/{related_name} and
Second route: http://example.com/api/{element_name}/bbb/{related_name}.
Everything is simple and easy when element_name is integer or simple text.
Things get complicated when parameter {element_name} has "/" char in the name, because even if I encode / by %2f (url encode) routing will decode %2f before process routes.
For example when I want to generate URL to first route and I have {element_name} = xyz and {related_name} = ooo then the URL will be http://example.com/api/xyz/aaa/ooo and it's OK.
But when I have {element_name} = xyz/bbb and {related_name} = ooo then the URL should be: http://example.com/api/xyz%2fbbb/aaa/ooo but routing first will decode url and make: http://example.com/api/xyz/bbb/aaa/ooo and it isn't OK because doesn't match to first route.
How I should do that?
All you need to do is to add requirement while configuring the routes in your controller. Like so :
class DefaultController
{
/**
* #Route("/share/{token}", name="share", requirements={"token"=".+"})
*/
public function share($token)
{
// ...
}
}
it's explained in the SF doc: http://symfony.com/doc/current/routing/slash_in_parameter.html

Symfony2 Templating without request

I'm trying to send an email from a ContainerAwareCommand in Symfony2. But I get this exception when the email template is render by:
$body = $this->templating->render($template, $data);
Exception:
("You cannot create a service ("templating.helper.assets") of an inactive scope ("request").")
I found in github that this helper need the request object. Anybody knows how can I to instance the Request object?
You need to set the container into the right scope and give it a (fake) request. In most cases this will be enough:
//before you render template add bellow code
$this->getContainer()->enterScope('request');
$this->getContainer()->set('request', new Request(), 'request');
The full story is here. If you want to know the details read this issue on github.
The problem arises because you use asset() function in your template.
By default, asset() relies on Request service to generate urls to your assets (it needs to know what is the base path to you web site or what is the domain name if you use absolute asset urls, for example).
But when you run your application from command line there is no Request.
One way to fix this it to explicitely define base urls to your assets in config.yml like this:
framework:
templating:
assets_base_urls: { http: ["http://yoursite.com"], ssl: ["http://yoursite.com"] }
It is important to define both http and ssl, because if you omit one of them asset() will still depend on Request service.
The (possible) downside is that all urls to assets will now be absolute.
Since you don't have a request, you need to call the templating service directly like this:
$this->container->get('templating')->render($template, $data);
Following BetaRide's answer put me on the right track but that wasn't sufficient. Then it was complaining: "Unable to generate a URL for the named route "" as such route does not exist."
To create a valid request I've modified it to request the root of the project like so:
$request = new Request();
$request->create('/');
$this->container->enterScope('request');
$this->container->set('request', $request, 'request');
You might need to call a different route (secured root?), root worked for me just fine.
Symfony2 Docs
Bonus addition:
I had to do so much templating/routing in cli through Symfony2 commands that I've updated the initializeContainer() method in AppKernel. It creates a route to the root of the site, sets the router context and fakes a user login:
protected function initializeContainer()
{
parent::initializeContainer();
if (PHP_SAPI == 'cli') {
$container = $this->getContainer();
/**
* Fake request to home page for cli router.
* Need to set router base url to request uri because when request object
* is created it perceives the "/portal" part as path info only, not base
* url and thus router will not include it in the generated url's.
*/
$request = Request::create($container->getParameter('domain'));
$container->enterScope('request');
$container->set('request', $request, 'request');
$context = new RequestContext();
$context->fromRequest($request);
$container->get('router')->setContext($context);
$container->get('router')->getContext()->setBaseUrl($request->getRequestUri());
/**
* Fake admin user login for cli. Try database read,
* gracefully print error message if failed and continue.
* Continue mainly for doctrine:fixture:load when db still empty.
*/
try {
$user = $container->get('fos_user.user_manager')->findUserByUsername('admin');
if ($user !== null) {
$token = $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->getContainer()->get('security.token_storage')->setToken($token);
}
} catch (\Exception $e) {
echo "Fake Admin user login failed.\n";
}
}
}
You might not need the last $container->get('router')->getContext()->setBaseUrl($request->getRequestUri()); part, but I had to do it because my site root was at domain.com/siteroot/ and the router was stripping /siteroot/ away for url generation.

Resources