Lithium link routing - lithium

I am trying to create my first Lithium app and I'm getting a very odd error.
I have a line in my index view,
<td><?php echo $this->html->link($question->title, array('controller'=>'questions','action'=>'view','id'=>$question->id)); ?></td>
Which I would imagine would link to that records view, and using 'questions/view'.$question->id' it does, however, using an array url I get a fatal.
Fatal error: Uncaught exception 'lithium\net\http\RoutingException' with message 'No parameter match found for URL('controller' => 'questions', 'action' => 'view', 'id' => '1').' in /Applications/MAMP/htdocs/learning-lithium/libraries/lithium/net/http/Router.php on line 306
Which to me looks like the router is trying to match the url in the helper, and as it can't, for some reason, it's throwing an exception. Does anyone have any idea's why this is? I'm attacking Lithium from a CakePHP standpoint, so this seems odd to me.

The 'args' param is handled by the default routes and get passed in as arguments to your action method.
Try this:
<?=$this->html->link($question->title, array('Questions::view', 'args' => array($question->id))); ?>
To route it with an id param, you need to specify a route that looks for an id param via {:id}. Look in the default routes.php file for the "Database object routes" section. That has some examples which I'll copy below for completeness:
/**
* ### Database object routes
*
* The routes below are used primarily for accessing database objects, where `{:id}` corresponds to
* the primary key of the database object, and can be accessed in the controller as
* `$this->request->id`.
*
* If you're using a relational database, such as MySQL, SQLite or Postgres, where the primary key
* is an integer, uncomment the routes below to enable URLs like `/posts/edit/1138`,
* `/posts/view/1138.json`, etc.
*/
// Router::connect('/{:controller}/{:action}/{:id:\d+}.{:type}', array('id' => null));
// Router::connect('/{:controller}/{:action}/{:id:\d+}');
/**
* If you're using a document-oriented database, such as CouchDB or MongoDB, or another type of
* database which uses 24-character hexidecimal values as primary keys, uncomment the routes below.
*/
// Router::connect('/{:controller}/{:action}/{:id:[0-9a-f]{24}}.{:type}', array('id' => null));
// Router::connect('/{:controller}/{:action}/{:id:[0-9a-f]{24}}');
So you would need to uncomment one of those two sections depending on what format your ids take. They use regular expressions with the id param to make sure it doesn't match url arguments that aren't ids. Incidentally, the first route is setting the default value for id to null which doesn't exactly make sense to me because I don't think the route will ever match with a null value, but anyway, that is how you set default values for your params.
Note that if you do this, your controller action method needs to look like this:
public function view() {
$id = $this->request->id;
// or an alternative that does the same thing
// $id = $this->request->get("params::id");
// ... etc ...
}
The only way to get url pieces passed in as arguments to your controller action method is to use the 'args' param.

You're not using named parameters in your route, so just output the following in your view:
<?php echo $this->html->link($question->title, array('controller'=>'questions', 'action'=>'view', $question->id));?>
Your function signature in QuestionsController should simply be:
public function view($id) {}

Related

Api Platform pagination custom page_parameter_name

I have very specific question on which I cannot find any answer and/or solution provided for Api Platform.
By default, the documentation states, that if you want to pass a page parameter for paging action, you must do the following:
pagination:
page_parameter_name: _page
However, due to the nature of our frontend we're not able to pass this variable to the request. It is hardcoded to the frontend request and is something like page[number]=1.
Is it possible to configure page_parameter_name to receive this variable or we need to transform it somehow in the Api itself?
Thank you!
ApiPlatform\Core\EventListener\ReadListener::onKernelRequest gets $context['filters'] from the request through ApiPlatform\Core\Util\RequestParser::parseRequestParams which ultimately uses PHP's parse_str function so the value of 'page[number]' will be in $context$context['filters']['page']['number'].
ApiPlatform\Core\DataProvider\Pagination::getPage retrieves the page number from $context['filters'][$parameterName] so whatever the value of [$parameterName] it will at best retrieve the array ['number'=> 1].
Then ::getPage casts that to int, which happens to be 1. But will (at least with PHP7) be 1 for any value under 'number'.
Conclusion: You need to transform it somehow in the Api itself. For example by decoration of the ApiPlatform\Core\DataProvider\Pagination service (api_platform.pagination).
API_URL?page[number]=2
print_r($request->attributes->get('_api_pagination'));
Array(
[number] => 2
)
The value of the "page_parameter_name" parameter should be "number" .
api_platform.yaml
collection:
pagination:
page_parameter_name: number
This may not work in version 3
vendor/api-platform/core/src/JsonApi/EventListener/TransformPaginationParametersListener.php
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$pageParameter = $request->query->all()['page'] ?? null;
...
/* #TODO remove the `_api_pagination` attribute in 3.0 */
$request->attributes->set('_api_pagination', $pageParameter);
}

How to pass a parameter to a scenario in Yii?

How to pass $id to search scenario? Maybe in model look like this, so I can call like in controller like:
$model = new job('search',$id);
I think that you are trying to do a search. Search is one thing, a "scenario" is something else.
Scenarios are used in validation rules in order to be able to validate the same model in multiple ways depending from where you're inserting/adding OR searching data.
There's also a scenario called 'search' that is used by the model's search() method, but I tell you why:
There are a couple of ways to search for something in your database using Yii, I will mention two:
1) By using ClassName::model()->findCommandHere
And there are a couple of them:
ClassName::model()->findByPk($id);
ClassName::model()->findAll("id=$id");
ClassName::model()->findByAttributes(array('id'=>$id));
And so on, more here: http://www.yiiframework.com/doc/guide/1.1/en/database.ar#reading-record
2) By using the model's search() method
This way of finding data is mostly used for easily creating search pages and in combination with data grids.
If you generate CRUD code with the GII code generation tool it will generate all the parts for you, but I will explain each part how it works.
This code is from the blog demo found in Yii files:
In controller it defines a $model using Post class and 'search' as scenario.
$model=new Post('search');
if(isset($_GET['Post'])) // <- checks if there are search params in the URL
$model->attributes=$_GET['Post']; // <- assigns all search params masively to the model (later you'll see why)
$this->render('admin',array(
'model'=>$model,
));
The 'search' scenario here tells Yii what validation rules to use when assigning search parameters directly from $_GET (URL).
You can see that the params are assigned massively to reduce code written but $model->attributes=$_GET['Post'] it is the same as doing:
$model->title=$_GET['Post']['title'];
$model->status=$_GET['Post']['status'];
In the Post model you can find the validation rules for the search scenario. Tells Yii that it is safe to assign title and status fields in order to later use them in the search.
public function rules()
{
return array(
// ... //
array('title, status', 'safe', 'on'=>'search'),
);
}
Then also in the Post model you can see the search() method that will actually be used to get the data:
public function search()
{
$criteria=new CDbCriteria;
$criteria->compare('title',$this->title,true);
$criteria->compare('status',$this->status);
return new CActiveDataProvider('Post', array(
'criteria'=>$criteria,
'sort'=>array(
'defaultOrder'=>'status, update_time DESC',
),
));
}
The search method creates a "criteria" and applies the desired way of filtering using the values you have previously assigned to this model. See the $this->title it comes from the $model->attributes=$_GET['Post'] you used in the controller.
The criteria can be used directly on the model, such as Post::model()->findAll($criteria), but in this case the search() method uses something different, a "data provider".
The data provider is a good thing because it provides you a lot of tools in one place, it returns you the data, but also the pagination, and the sorting, so you don't have to manually define more code for that purposes (CPagination, CSort).
Finally, in the view admin.php in this case it will display the results using a grid view:
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
array(
'name'=>'title',
'type'=>'raw',
'value'=>'CHtml::link(CHtml::encode($data->title), $data->url)'
),
array(
'name'=>'status',
'value'=>'Lookup::item("PostStatus",$data->status)',
'filter'=>Lookup::items('PostStatus'),
),
),
));
Now you can see that in the configuration of the grid it passes $model->search() method as the dataProvider that the grid should use.
Now the grid can access the rest of the dataProvider elements such as sort, pagination and display them on the page.
If you did not want to use the CGridView because it's a very basic table and you want to create your own html markup, you can also retrieve the dataProvider and its components one by one and place them in your HTML code and display data as you want:
$dataProvider=$model->search(); // get the dataprovider from search method
$models=$dataProvider->getData(); // actually get the data (rows) and assign them to a $models variable that you can put in a foreach loop
// show pagination somewhere
$this->widget('CLinkPager', array(
'pages' => $dataProvider->pagination,
));
// create sort links
echo $dataProvider->sort->link('title', 'Title');
So I hope it solves some of your doubts on how to use Yii for displaying/searching data.
I suggest you read the official manual: http://www.yiiframework.com/doc/guide/1.1/en/index
I also suggest to look at the API and so search there all the Yii components to see what methods and params they have: http://www.yiiframework.com/doc/api/
Also exploring the framework codebase manually is quite a good way to learn. If you don't know how CActiveDataProvider works, then find the CActiveDataProvider class file in the code and you'll see all the methods and properties that it uses, so do this for everything you don't understand how it works.
Also for beginners I recommend using a good IDE that auto-completes code and allows you to Ctrl+Click a class name and it will locate the original file where it was defined. I use NetBeans for PHP and after creating a project I add Yii framework files to the project's include paths that way NetBeans knows how to find the framework files for auto-complete and for ctrl+click.
Yes you can define scenario with parameter but that is included within the class constructor of the model
$model = new Job('search'); // creating a model with scenario search
If you wish to include more parameters, then you need to use createComponent -remember, all is a component
$model = Yii::createComponent(array('class'=>'Job','scenario'=>'search'));
I think this will simply do the job without needing any scenario
$model = new job;
$model->search($id);
But If I have failed to understand your problem then you can also try this
$model = new job('search');
$model->search($id);
Think of scenarios as a special variable that you can use in the model.
$userModel = new User("register");
$userModel->setId = 10;
which is the same
$userModel = new User();
$userModel->scenario = 10
$userModel->setId = 10;
And in your model
class Manufacturer extends CActiveRecord
{
// :
if ($this->scenario == 'register') ...
// :
}

Web API 2 Routing based on Parameter's Value

Is it possible to setup Web Api 2 route based on a parameter's value in the query string.
I have the following requirement:
/api/controller?data=param.data1
should go to controller's action 1
/api/controller?data=param.data2
should go to controller's action 2
any other value of data must go to action 3.
I know there's an option to set a constraint with a regex, but the examples I've found are for generic scenarios and not as specific as mine.
This is what I've tried
config.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/Hub/{data2}",
defaults: new { action = "Test" },
constraints: new { data2 = #"^(param\.data2)$" }
);
Is there a way to do it? Maybe there's a better way?
Important to note, I cannot change the URI of the service. It must have ?data=[value]
This is a fallback for a legacy system :(
You can use Attribute Routing, new in Web API 2.
Let's say you have the following actions, where the data param is, let's say, a string:
public Stuff GetStuffForData1(string data) { ... }
public Stuff GetStuffForData2(string data) { ... }
public Stuff GetStuffForData(string data) { ... }
Since you mentioned regex, you can specify route constraints for each of the above actions using a regex like the one you mentioned in your question1, for example:
[Route("controller/{data:regex(#"^(param\.data1)$")]
public Stuff GetStuffForData1(string data) { ... }
[Route("controller/{data:regex(#"^(param\.data2)$")]
public Stuff GetStuffForData2(string data) { ... }
// No need for a route constraint for other data params.
public Stuff GetStuffForData(string data) { ... }
The general syntax is {parameterName:constraint(params)} (params is optional and is not used for all constraints). In the above example, the first route will only be selected if the data segment of the URI matches the data1 regex. Similarly, the second route will be selected if the data segment of the URI matches the data2 regex. Otherwise, the last route will be chosen.
In general, the total ordering is determined as follows:
Compare the RouteOrder property of the route attribute. Lower values are evaluated first. The default order value is zero.
Look at each URI segment in the route template. For each segment, order as follows:
Literal segments.
Route parameters with constraints.
Route parameters without constraints.
Wildcard parameter segments with constraints.
Wildcard parameter segments without constraints.
In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.
You can even create your own custom route constraints by implementing the IHttpRouteConstraint interface and registering it in the Register method of your WebApiConfig class, assuming you're hosting on IIS, or in the Configuration method of your Startup class if self-hosting using OWIN.
Note I haven't personally tried any of the above, but it should all work; at the very least it should give you some ideas. For more details, including very nice examples, you should start by taking a look at the following article (which I shamelessly used extensively in my answer):
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#constraints
1 I'm really not an expert on writing regexes, so unfortunately I can't advise you on the specific ones you'll need.

Laravel dynamic route from MySQL

I am trying to generate routes from one template "tuning.blade.php"
I have a DB with 250 rows I would like to dynamically create 250 routes with one route or one controller.
I want to be able to use these URLs
laravel.dev/tuning/(field from DB row 1)
laravel.dev/tuning/(field from DB row 2 and so on)
I want to put the DB requests in tuning.blade.php so that this template can display all 250 rows using 250 different URLS
I have tried to use the first example from Laravel Docs
class UserController extends BaseController {
/**
* Show the profile for the given user.
*/
public function showProfile($id)
{
$user = User::find($id);
return View::make('user.profile', array('user' => $user));
}
}
Route::get('user/{id}', 'UserController#showProfile');
I also got some interesting search results from http://forumsarchive.laravel.io/viewtopic.php?id=9010
But alsas I always end up with a notfound exception
But I am unsure what to put in my tuning template to display anything at all. my Tuning template reside in app/views/home/tuning.blade.php
Currently I have got the error "Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpException"
Can anyone put me in the right direction of where I can find a resource to help me understand?
You said you want to be able to use these URLs:
laravel.dev/tuning/(field from DB row 1)
laravel.dev/tuning/(field from DB row 2 and so on)
You can do this by declaring a route like this:
Route::any('/tuning/{field}', 'TuningController#someMethod'); you may get/post
You shouldn't run sql queries from your view and if you want to really declare some dynamic routes for each fields from your database then you may do id right from your routes.php file, for example, assume that you have a table named tunings and that table contains some fields including id, name and some others. Now, to declare routes individually for each routes dynamically using the tuning field of table tunings you may create a method in your TuningController, something like this:
class TuningController extends baseController {
// other methods...
public function registerTuningRoutes()
{
$tunings = Tuning::all(); // Assume that you have a model Tuning
// Or you may use this instead
$tunings = DB::table('tuning')->get();
// Now loop all tunings and declare routes
foreach($tunings as $tuning) {
$url = '/tuning/' . $tuning->name;
$route_name = 'tuning.' . $tuning->name;
Route::any($url, $route_name); // You may use get/post
}
}
public function TuningMethod($tuning = null)
{
// $tuning will contain the current tuning name, check
dd($tuning);
}
}
Now in your routes.php file use something like this:
Registers route for each tuning name in database
App::make('TuningController')->registerTuningRoutes();
From your terminal/command prompt check the routes by running following command:
php artisan routes
But, I think, you don't need to do this, only one route is enough as I mentioned earlier in my answer.

Yii nested URL parameters

Can anyone point me in the right direction in order to write a URL rule in order to achieve the following:
Using the base controller I want to get to each of the parameters in this url:
DomainName/clientID/fileID
Haven't tested it, but this should work:
'rules' => array(
'<clientID:\d+>/<fileID:\d+>' => 'myBaseController/myAction',
)
Just make sure the one rule gets added before your Yii default URL rules. FYI, the rule assumes that both client ID and file ID are integers. If fileID contains letters, change it to fileID:\w+.
$url = $this->createUrl('timeSheetDetaile/create',array('timesheetID'=>$timeSheedID,'dayID'=>1));
first param is the name of your controller and second array is $_GET params ..
have fun.