Yii2: Controller action parameters with a dash? - php

If I have a URL like https://example.com/controller/action?customer-id=7414 how do I get customer-id in my action parameters? Since a dash is not allowed in variables names I cannot do the following!
public function actionContact($customer-id) { //syntax error! :)
// ...
}
Documentation is usually excellent but on this exact point it's just silent. How do I solve this?

When yii\web\Controller calls action it only binds parameters which names match exactly. Dash cannot be used in variable name in PHP so there is no way it will ever match like that.
If you really want to have param in URL as customer-id=XXX then easiest thing you can do, is skip param in action method definition and get it during action itself:
public function actionContact() {
$customerId = $this->request->get('customer-id');
// or
$customerId = \Yii::$app->request->get('customer-id');
// ...
}

Related

how to pass a ("/") through href

I am passing an id containing / eg: 171/CR/EOW1/14 in the link.
It is showing correctly, but in the controller function it is taking only the first letters before the slash. eg: 171
How do I solve this problem?
Your question is incredibly vague. But for the purposes of this, I'll assume that you want to pass the whole string 171/CR/EOW1/14. Not parts of the string as different params.
you are using an un-escaped slash. So codeigniters' routing thinks the parts of the url after the 171 are more parameters in the route string.
if you want to pass a url, use urlencode() and then urldecode() to handle the slashes in the string you want to pass.
Or use addslashes().
addslahes()
urlencode()
You can use uri_segment, which should help.
http://example.com/index.php/controller/action/1stsegment/2ndsegment
it will return
$this->uri->segment(1); // controller
$this->uri->segment(2); // action
$this->uri->segment(3); // 1stsegment
$this->uri->segment(4); // 2ndsegment
Passing URI Segments to your methods in codeigniter visit codeIgniter docs
If your URI contains more than two segments they will be passed to your method as parameters.
For example, let’s say you have a URI like this:
example.com/index.php/products/shoes/sandals/123
Your method will be passed URI segments 3 and 4 (“sandals” and “123”):
<?php
class Products extends CI_Controller {
public function shoes($sandals, $id)
{
echo $sandals;
echo $id;
}
}
In PHP 5.6 you can retrieve as a variable argument list which can be specified with the ... (spread) operator
function do_something($first, ...$all_the_others)
{
var_dump($first);
var_dump($all_the_others);
}
or if you are using a lesser version you have to specify separate arguments variables
function do_something($first, $second, $third)
{
var_dump($first);
var_dump($second);
var_dump($third);
}
EDIT:
You can route the url to this function like
$route['products/(:any)'] = 'catalog/do_something';
Please check the documentation for more details about url routing
You can you use a question mark like:
?first=171&second=CR
For more information, see e.g. http://html.net/tutorials/php/lesson10.php

Get parameters for resource controller that has parameter in its base path too

I have a route for a resource controller that has a base path. In this base path there is another parameter I need: {countryId}.
Route::resource('country/{countryId}/region', 'CountryRegionController');
I know that in a regular resource controller I can get the resource's id as the function parameter, like this:
public function edit($countryRegionId)
{
}
But that is kinda messed up with the extra parameter in the route. How can I get all parameters in a conventional way?
In this case you can expand your parameter list. Actually you should expand, because if you use only one parameter that will be the value of the parameter in the base route not the id of the resource. So here is the correct way to do this.
public function edit($countryId, $countryRegionId)
{
}
Parameter name can be anything it doesn't have to match with the parameter names in the route, but the order is important.
You could also use Request::segment() but the previous solution is much better.
$countryId = Request::segment(2);
$countryRegionId = Request::segment(4);

Laravel routes with regex portions that don't get passed as parameters

Is there any way I can have a route set up with one regex section that doesn't get passed as a parameter?
For example:
Route::get('{string}/method/{id}', function($id)
{
return 'only one parameter passed, ID is ' . $id;
});
Specifically I'm routing to a controller and the methods need to be compatible with routes coming from elsewhere, which don't include this first parameter.
The most important thing is that, routes has to be matched according to it's declaration, for example, if you define a route like your example here
Route::get('{string}/method/{id}', function($id)
{
return 'only one parameter passed, ID is ' . $id;
});
Then the requested url has to be matched with same numbers of parameters including the http method (GET here) and in this case, the route will match only with something like this
httP//example.com/something/method/10
Here the second parameter 10 is not bound to be digits because you didn't make it to be a digit using where(...) so, it could be anything but two parameters must be required.
As an alternative, you may define a missing method in your controller like this (An idea only)
public function missingMethod($args = array())
{
// $args will contain all parameters
}
This is a special method in Laravel that any controller may contain it and whenever a non-existing method will be called in that controller, then this missingMethod would be called and all the parameters will be passed to it's $args parameter as an array, so if you have two parameters in the url and calling method is missing in the controller then you may get those parameters within this method scope, something like [param1, param2] and from this method you may call your desired method depending on the count of params.
So, if you just point the route's action to this controller which has the missing method then from the missingMethod you may call another method using different parameters according to your other method.
Also check PHP Overloading and call_user_func_array to get the real idea of missingMethod.

How to reference a function after an argument in CodeIgniter URLs?

My desired URL structure for a section of a web application is as follows:
/user/FooBar42/edit/privacy, and I would like this to route to controller: user, function: edit, with FooBar42 and privacy as arguments (in that order). How should I accomplish this with CodeIgniter?
Defining this route in application/config/routes.php should work:
$route['user/(:any)/edit/(:any)'] = "user/edit/$1/$2";
However, be aware that (:any) in the above route would match multiple segments. For example, user/one/two/edit/three would call the edit function in the user controller but only pass one as the fist parameter and two as the second.
Replacing the (:any) with the regex ([a-zA-Z0-9]+) will only allow one only alphanumeric values of length at least 1. This mitigates the issue above, where a / would be permitted allowing multiple segments to be allowed. Now, if user/one/two/edit/three was used, a 404 page would be shown.
$route['user/([a-zA-Z0-9]+)/edit/([a-zA-Z0-9]+)'] = "user/edit/$1/$2";
You can also use the remapping option of the CI controller
http://ellislab.com/codeigniter/user-guide/general/controllers.html#remapping
and doing something like this:
public function _remap($method, $params = array())
{
// check if the method exists
if (method_exists($this, $method))
{
// run the method
return call_user_func_array(array($this, $method), $params);
}
else
{
// method does not exists so you can call nay other method you want
$this->edit($params);
}
}

How do I implement controller action in a PHP MVC framework?

I'm trying to create my own MVC framework in PHP to learn about them but I'm currently having trouble implementing the action of the controller.
The problem is that some controllers have one or multiple arguments such as actionView($id), while others have no arguments like actionCreate(). How do I handle these differently?
For example:
$action; //contains my action
$params; //array containing my arguments
In my controller I would call...
$this->$action(); //for no parameters
$this->$action($params[0]); //for one parameter
$this->$action($params[0], $params[1]); //for two parameters
... and so on
It wouldn't be feasible to handle every possible situation like this. Perhaps I am implementing the actions wrong. Can someone guide me in the right direction?
EDIT: How do the MVC frameworks out there make handling multiple arguments possible?
I would simply pass the array of arguments as the only argument to every action. Let it be known that your framework's convention includes passing arguments to actions in this way. This allows the implementing code to choose how they want to deal with it; use func_get_args() maybe or provide a paramater in the method signature:
public function action(array $args = array()) { ...
Something to be wary of though is that although your action may require a parameter the user may not actually provide one. If your method signature is setup to require a value be passed and you don't have enough values to plug in then you may get all kinds of errors. This is part of the reason I would choose to simply pass in the array of parameters and always pass an array, even if it is empty.
Alternatively, partly influence off of BrNathanH answer, you could somehow tie an object to a given action and then inject that array into the object's constructor. That object can be responsible for providing the data needed for the action and giving suitable defaults/checking for the values to exist. You could then just add an object signature to your actions, if you wanted to be doubly sure that they are getting the appropriate data. I'm not sure exactly how you would implement this in your own code, the primary problem being the mapping of controller::action to the appropriate "ParamObject". But wrapping the array into objects might be a good idea and would solve the problem of not knowing how many parameters to pass. It's always one, the "ParamObject" associated with that action.
The best way I know how to explain this is with examples, so here is an example of my own implementation for a PHP-MVC application. Make sure to pay close attention to the Hook::run function, which handles the arguments for loading the control.
index.php:
<?php
class Hook {
const default_controller = 'home';
const default_method = 'main';
const system_dir = 'system/';
static function control ($name, $method, $parameter) {
self::req(Hook::system_dir.$name.'.php'); // Include the control file
if (method_exists('Control', $method)) { // For my implementation, I can all control classes "Control" since we should only have one at a time
if (method_exists('Control', 'autoload')) call_user_func(array('Control', 'autoload')); // This is extremely useful for having a function to execute everytime a particular control is loaded
return call_user_func(array('Control', $method), $parameter); // Our page is actually a method
}
throw new NotFound($_GET['arg']); // Appear page.com/control/method does not exist, so give a 404
}
static function param ($str, $limit = NULL) { // Just a wrapper for a common explode function
return (
$limit
? explode('/', rtrim($str, '/'), $limit)
: explode('/', rtrim($str, '/'))
);
}
static function req ($path) { // Helper class to require/include a file
if (is_readable($path)) return require_once($path); // Make sure it exists
throw new NotFound($path); // Throw our 404 exeception if it doesn't
}
static function run() {
list($name, $method, $parameter) = ( // This implementaion expects up to three arguements
isset($_GET['arg'])
? self::param($_GET['arg'], 3) + array(self::default_controller, self::default_method, NULL) // + array allows to set for default params
: array(self::default_controller, self::default_method, NULL) // simply use default params
);
return self::control($name, $method, $parameter); // Out control loader
}
}
class AuthFail extends Exception {}
class UnexpectedError extends Exception {}
class NotFound extends Exception {}
try {
Hook::run();
}
catch (AuthFail $exception) { // Makes it posssible to throw an exception when the user needs to login
// Put login page here
die('Auth failed');
}
catch (UnexpectedError $exception) { // Easy way out for error handling
// Put error page here
die('Error page');
}
catch (NotFound $exception) { // Throw when you can't load a control or give an appearance of 404
die('404 not found');
}
system/home.php:
<?php
class Control {
private function __construct () {}
static function autoload () { // Executed every time home is loaded
echo "<pre>Home autoload\n";
}
static function main ($param='') { // This is our page
// Extra parameters may be delimited with slashes
echo "Home main, params: ".$param;
}
static function other ($param='') { // Another page
echo "Home other, params:\n";
$param = Hook::param($param);
print_r($param);
}
}
.htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.+)$ index.php?arg=$1 [QSA,L]
With this htaccess file, we are able to load the home.php control using localhost/home/main. Without it, our urls would look like localhost/index?args=home/main.
Demonstration screenshot 1 (localhost/simple_mvc/home/main/args1/args2/args3):
You don't always know how many arguments an particular control method is going to expect, which is why I believe it is best to pass a single argument delimited by slashes. If the control method expects more than one argument, you can then use the provided Hook::param function to further evaluate.
Demonstration screenshot 2 (localhost/simple_mvc/home/other/args1/args2/args3):
To answer your question:
The key file here that really helps to answer your question is the .htaccess file, which transparently turns localhost/these/types/of/urls into something like localhost/index.php?args=these/types/of/urls. That then allows you to split the arguments using explode($_GET['args'], '/');.
A GREAT video tutorial on this subject is here. It really helps to explain a lot.
I've made some changes to home.php and index.php with the latest edit
You could just pass the whole array. The problem is that each controller has to do error checking to see if the correct values are there. Another solution would be to create a data object that holds the parameters. You have the controller access that (which does all the error checking for you).
For arguments, you can always set some arguments to be optional. Like this:
function($argument='', $argument2='')
{
// ...
}
This means that $argument and $argument2 are optional. If those arguments are set when the function is called... those arguments will have values.
Although, passing an array() (and then using isset() to check for set array keys or items,) would be easier for some functions.

Categories