I am using the code found here https://laracasts.com/discuss/channels/laravel/how-to-prefixing-in-routes-for-localization to prefix my routes with a locale.
Route::prefix('{lang?}')->middleware('locale')->group(function() {
Route::get('/', function () {
return view('index');
})->name('index');
});
This locale is optional, but then my question is how am I supposed to reinject the locale when calling the route helper.
route('index');
to generate: /it/ or /, or another, depending on the current locale.
If have tried this with no success :
Route::resourceParameters(['lang' => 'en']);
Actually, I'm a bit unsure about what the question is. I took it as a way to generate URL without setting its dynamic parameters using route helper based on #rahulsm's answer.
So, I just figured out that you can set default parameters to UrlGenerator class. By listening on container url and request rebind event, it's possible to set default parameters there.
Inside AppServiceProvider boot
$this->app->rebinding('url', function ($url, $app) {
$url->defaults(['lang' => $app->getLocale()]);
});
$this->app->rebinding('request', function ($app) {
$app['url']->defaults(['lang' => $app->getLocale()]);
});
And then tried to call it,
route('index') // http://test.dev/en (based on config)
route('index', ['lang' => 'what']) //http://test.dev/what
This was tested only on Laravel 5.5, but I'm sure would also working on Laravel 5.4.
To explain a bit more about rebinding method available Laravel container[1], firstly it's good to know how is Laravel request lifecycle works. In a one line simplified words, it should be the same as how an application in general works, which is: receive a request, do the logic, return a response.
At the second part, appear stage commonly said as bootstrapping, which one of its logic is try to store (register) classes (services) that mostly are important for the application[2] to work. Into a container. Thus, intended to be shared or even just for easily to be called, whether for the high-end developers or the framework itself.
Once registered, it will then be booted. This booting process has several actions to call[3]. One of it that suit this case is firing (local) events. Once a service has been resolved, it will try to call all registered rebound functions (listeners) at this stage. I can only assume this purpose is to make the application can be "easily mutate" the currently-resolving instance (service). Thus, defining rebinding method to listen to recalled event is the way to go.
Since Laravel allows to re-resolve (re-instantiate) a service, which means our previous stored value in the class is lost[4], waiting it to be resolved (which then the listener called) is much make sense, right?
Back to rebinding snippet above, I used to listen to both url and request rebound services because url is request's dependent. It waits for request service to be fully resolved and then call setRequest[5] method which flushes the needed instance which is Illuminate\Routing\RouteUrlGenerator that holds default parameter.
And as the name implies, defaults, used to set the default named parameters used by the URL generator[6].
cit
[1] Container here refer to both Illuminate\Foundation\Application also Illuminate\Container\Container
[2] configure error handling, configure logging, detect the application environment
[3] resolving type-hinted dependencies, storing/caching stuffs to buffer, etc
[4] Unless retained like stored in class' static property
[5] Illuminate\Routing\UrlGenerator#setRequest
[6] Illuminate\Routing\UrlGenerator, either calling URL::route('route.name'), url('your-url'), app('url')->route('route.name'), or route('route.name'), they refer to the same class
This should should be,
route('index', ['lang' => 'fr']);
And change route helper with
/**
* Generate a URL to a named route.
*
* #param string $name
* #param array $parameters
* #param bool $absolute
* #param \Illuminate\Routing\Route $route
* #return string
*/
function route($name, $parameters = [], $absolute = true, $route = null)
{
if (!isset($parameters['lang'])) $parameters['lang'] = App::getLocale();
return app('url')->route($name, $parameters, $absolute, $route);
}
Refer link for more details.
Related
Route::get('/atomic/{id}',[ApiController::class,'index'])->defaults('task', 'atomic');
why use defaults here and what is a task & atomic, and Api controller does not have an index function. Please explain this route properly.
I am new to laravel I tried to google for a solution but no result
defaults method helps to pass extra params to controller without passing as route params
As a backend engineer you’ll often be asked to produce URL patterns
that just don’t work with the rest of the site without breaking your
current routing structure. Often you’ll create what’s known as a slug
for your content, a simple hyphen separated string which is unique in
the system. A typical slug would be just generated from the title like
“My Simple Article” becomes as a slug my-simple-article. This way
there’s a unique string in the system for each post.
If you’ve already been implementing routes like this in your system
you’ll likely have urls that look like /post/{slug} but you know now
that’s not going to be good enough. Your company’s marketing team or
SEO wizards want it to be /{slug} and that’s pretty tricky. You can’t
create the pattern /{post-slug} because it’s going to confuse the
system. What is you have an About Us page or a Contact Us page which
equally important urls like /about-us and /contact-us respectively.
The problem here being that the routing system might pick up the
/about-us link and believe it’s meant to be a slug for a Post model.
At this point Laravel will simply not find the model and throw a HTTP
404 error instead. Not good.
This is where the ‘defaults’ method on routes comes into use to save
the day.
if I consider your example then
Route::get('/atomic/{id}',[ApiController::class,'index'])->defaults('task', 'atomic');
while hitting URL http://127.0.0.1:8002/atomic/1 then in the controller,you will get both params $id and $task
public function index($id,$task){
dump($task);
dump($id);
}
the output of the above will be atomic and 1
defaults() method nothing but key-value pair params
/**
* Set a default value for the route.
*
* #param string $key
* #param mixed $value
* #return $this
*/
public function defaults($key, $value)
{
$this->defaults[$key] = $value;
return $this;
}
suppose if you want to pass multiple array params then use setDefaults method like below
Route::get('/atomic/{id}',[ApiController::class,'index'])->setDefaults([
'tasks'=> 'atomics',
'postTitle'=>'post title goes here'
]);
then in controller
public function index($id,$tasks,$postTitle){
dump($tasks);
dump($postTitle);
dump($id);
}
now if you hit URL http://127.0.0.1:8002/atomic/1 then it will print
atomics
post title goes here
1
Ref : The Power of Laravel’s Route ‘defaults’ for making root level SEO pages
Before i state my problem, please be aware that this is my first time working with Typo3 and/or creating an Extbase extension.
SO basically i want to create an extbase extension for Typo3, but i seem not to be able to wrap my head around the concept of transfering an object (assigned to the view of a specific template) via arguments to an action, with the purpose of attaching the object to another (with a 1:n relation).
My example:
I have an Objekt of the type "Appliance" assigned to the view of a template ("Show.html"). I can list all the properties of it in the Template, so it definitely exists in the view.
Now i want to create an Object of the type "Host" using a form and then attaching it to this specific "Appliance" object.
The problem is: I can't transfer the object of the type "Appliance" to the specific Action of the Controller of the type "Host" which itself should then assign it to the view of another template.
Look at the following code example:
<f:link.action action="new" controller="Host" arguments="{appliance:appliance}" >Add Host X</f:link.action>
This is the specific code line in the "Show.html" template that transfers the "Appliance" object to the Action "new" of the "Host" controller using arguments... The "Host" controller:
public function newAction(\Cjk\Icingaconfgen\Domain\Model\Appliance $appliance, \Cjk\Icingaconfgen\Domain\Model\Host $host = NULL)
{
$this->view->assign('appliance', $appliance);
$this->view->assign('host', $host);
}
At this point i get the following error message:
"Argument 1 passed to
Cjk\Icingaconfgen\Controller\HostController::newAction() must be an
instance of Cjk\Icingaconfgen\Domain\Model\Appliance, none given"
What am i doing wrong?
You need a Docblock that describes these parameters.
What may look like just comments, actually follows the PHPDoc standard. These declarations are interpreted by the TYPO3 ReflectionClass to map your Domain Model and validate parameters and object properties.
Make sure you completely flush the cache whenever you add or update one of these.
/*
* #param \Cjk\Icingaconfgen\Domain\Model\Appliance $appliance
* #param \Cjk\Icingaconfgen\Domain\Model\Host $host
* #return void
*
*/
public function newAction(\Cjk\Icingaconfgen\Domain\Model\Appliance $appliance, \Cjk\Icingaconfgen\Domain\Model\Host $host = NULL)
{
$this->view->assign('appliance', $appliance);
$this->view->assign('host', $host);
}
You need to be sure that there is an Appliance model given in your Fluid template, easily by debugging it before the link with e.g. <f:debug>{appliance}</f:debug>
If this is okay, you should add some doc comments above your newAction because Extbase is referring to that.
An example would be: (just as I am writing this, a good example was posted). :)
I used to use Framework Laravel 5.x routes as follows:
Route::controller("demo", "\App\Http\Controllers\DemoController");
But when I read the routing codes in Laravel 5.2, I see it has been deprecated since version 5.2, in file \Illuminate\Routing\Router
/**
* Route a controller to a URI with wildcard routing.
*
* #param string $uri
* #param string $controller
* #param array $names
* #return void
*
* #deprecated since version 5.2.
*/
public function controller($uri, $controller, $names = [])
{
$prepended = $controller;
// First, we will check to see if a controller prefix has been registered in
// the route group. If it has, we will need to prefix it before trying to
// reflect into the class instance and pull out the method for routing.
if (! empty($this->groupStack)) {
$prepended = $this->prependGroupUses($controller);
}
$routable = (new ControllerInspector)
->getRoutable($prepended, $uri);
// When a controller is routed using this method, we use Reflection to parse
// out all of the routable methods for the controller, then register each
// route explicitly for the developers, so reverse routing is possible.
foreach ($routable as $method => $routes) {
foreach ($routes as $route) {
$this->registerInspected($route, $controller, $method, $names);
}
}
$this->addFallthroughRoute($controller, $uri);
}
I think this is a good feature, but why they deprecate it ?
Are there any other better solutions ?
= Update 1 =
As the issue ( https://github.com/laravel/framework/pull/10777 ) said, he thought that feature makes the routes confusing, or easy leading access to unexpected route definitions.
According to Github Issue (https://github.com/laravel/framework/pull/10777)
rkgrep says:
This method has quite a lot of issues and also makes routes definitions confusing as they are not directly referenced in routes file. This sometimes may lead to unexpected route definitions (e. g. if method like getFilesystem is added to parent or trait - developer mistake, but may be unnoticed).
It also may cause security mistakes when middleware is applied on specific methods (e. g. someone adds new method to controller and forgets to add middleware), which arise rarely when routes are defined manually.
And at last - parameters issue, which is hard to fix because of grouping or other unexpected route combinations (See #10774).
To decrease amount of breaks it should be marked as deprecated in next 5.1.x release.
Possible alternative: mark this method as deprecated in master and remove in next major release.
Using Symfony, I am displaying a table with some entries the user is able to select from. There is a little more complexity as this might include calling some further actions e. g. for filtering the table entries, sorting by different criteria, etc.
I have implemented the whole thing in an own bundle, let's say ChoiceTableBundle (with ChoiceTableController). Now I would like to be able to use this bundle from other bundles, sometimes with some more parametrization.
My desired workflow would then look like this:
User is currently working with Bundle OtherBundle and triggers chooseAction.
chooseAction forwards to ChoiceTableController (resp. its default entry action).
Within ChoiceTableBundle, the user is able to navigate, filter, sort, ... using the actions and routing supplied by this bundle.
When the user has made his choice, he triggers another action (like choiceFinishedAction) and the control flow returns to OtherBundle, handing over the results of the users choice.
Based on these results, OtherBundle can then continue working.
Additionally, OtherOtherBundle (and some more...) should also be able to use this workflow, possibly passing some configuration values to ChoiceTableBundle to make it behave a little different.
I have read about the "Controller as Service" pattern of Symfony 2 and IMHO it's the right approach here (if not, please tell me ;)). So I would make a service out of ChoiceTableController and use it from the other bundles. Anyway, with the workflow above in mind, I don't see a "good" way to achieve this:
How can I pass over configuration parameters to ChoiceTableBundle (resp. ChoiceTableController), if neccessary?
How can ChoiceTableBundle know from where it was called?
How can I return the results to this calling bundle?
Basic approaches could be to store the values in the session or to create an intermediate object being passed. Both do not seem particularly elegant to me. Can you please give me a shove in the right direction? Many thanks in advance!
The main question is if you really need to call your filtering / searching logic as a controller action. Do you really need to make a request?
I would say it could be also doable just by passing all the required data to a service you define.
This service you should create from the guts of your ChoiceTableBundleand let both you ChoiceTableBundle and your OtherBundle to use the extracted service.
service / library way
// register it in your service container
class FilteredDataProvider
{
/**
* #return customObjectInterface or scallar or whatever you like
*/
public function doFiltering($searchString, $order)
{
return $this->filterAndReturnData($searchString, $order)
}
}
...
class OtherBundleController extends Controller {
public function showStuffAction() {
$result = $this->container->get('filter_data_provider')
->doFiltering('text', 'ascending')
}
}
controller way
The whole thing can be accomplished with the same approach as lipp/imagine bundle uses.
Have a controller as service and call/send all the required information to that controller when you need some results, you can also send whole request.
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$responeFromYourSearchFilterAction = $this->container
->get('my_search_filter_controller')
->filterSearchAction(
$this->request, // http request
'parameter1' // like search string
'parameterX' // like sorting direction
);
// do something with the response
// ..
}
}
A separate service class would be much more flexible. Also if you need other parameters or Request object you can always provide it.
Info how to declare controller as service is here:
http://symfony.com/doc/current/cookbook/controller/service.html
How liip uses it:
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service
How to forward with GET parameters?
I have two actions in same controller. I do some magic in the first action, and if all goes well, I want it to forward to the other one, but with GET parameters created in the first one.
What I have now is this:
return $this->forward('AppBundle:default:tracked', array(), array(
'tracking_code' => $tracking_code,
'company' => $tp_company_name
)));
Empty array in there because of a desperate effort to get it to work. Documentation tells that second array is for the query parameters, but nothing really happens, so I must be doing something wrong.
The second action tries to get the parameters like this:
/**
* #Route("/tracked", name="tracked")
*/
public function trackedAction()
{
$request = Request::createFromGlobals();
// If there is the required variables in the GET:
if($request->query->has('tracking_code') && $request->query->has('company'))
But no, variables never get there it seems.
Reason I have this kind of setup, is that user can get into the trackedAction from another place as well.
I think the proper way to get your parameters in your second action would be to do like this :
return $this->forward('AppBundle:default:tracked', array(
'tracking_code' => $tracking_code,
'company' => $tp_company_name
)));
And your second action would be :
/**
* #Route("/tracked/{tracking_code}/{company}", name="tracked")
*/
public function trackedAction($tracking_code=null, $company=null)
{
...
}
I used the $tracking_code = null because you specified this could be accessed from another place, that maybe does not provide these parameters. This way it works for both of them.
Hope this helps.
The way that Symfony controller forwarding works is by duplicating the current the Request with the options that you pass and then re-dispatching it through the HttpKernel component. You can see this in the code. Because it's a sub-request, your second action is creating a Request from globals (i.e. $_GET etc.) which haven't changed.
The solution is to change your second action method signature to:
public function trackedAction(Request $request)
This means that the $request variable will be "local" to your action and will contain the variables you want.
In practice you should always pass the Request in to your actions this way as it makes your controllers a lot more testable and prevents strange issues like this.
Alternatively to all of the above, you could use a redirect instead which would do an HTTP redirect rather than just forwarding within the Symfony request system.