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);
}
}
Related
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');
// ...
}
is there any possible way to send parameters within the redirection function from Codeigniter 4?
Important, it is a named route:
$routes->get('edit', 'Test_Controller::editTest/$1', ["as" => "editTest", "filter" => 'testFilter']);
And i want to do a redirect like this:
this works fine:
return $this->redirectTo('test/edit/' . $id1);
this would be nice, if it works:
return redirect('editTest', array($passingID));
or
return redirect('editTest')->with($passingID);
Why? I have HMVC and I would like to name every module with the same routes but different functions ofc. And this would help me having same site urls. (edit, add, a.s.o.)
The short answer is no its not possible. Take a look at system/Common.php and you'll see there is only 1 parameter to the redirect() function.
The longer answer however is take a look at what the redirect function does.
function redirect(?string $route = null): RedirectResponse
{
$response = Services::redirectresponse(null, true);
if (! empty($route)) {
return $response->route($route);
}
return $response;
}
Based on the current (4.1.4) redirect function you'll see it calls route(). It turns out that route() allows a second parameter which are the parameters.
So the solution would probably be to do your own redirect() function (see https://codeigniter.com/user_guide/extending/common.html?highlight=common) and allow a second parameter.
I've created a filter method for filtering the products list. This is my URL:
localhost/myshop/products/filter?category=shirts&color=blue&page=1
But I want to show this way:
localhost/myshop/products/shirts/blue/1
How can I achieve it?
Assuming that Products::filter() is responsible for handling the request, you can rewrite the method to accept parameters in its signature. So, if the current logic is something like this:
class Products extends CI_Controller
{
public function filter()
{
// Retrieve data from GET params
$page = $this->input->get('page');
$color = $this->input->get('color');
$category = $this->input->get('category');
// Do the filtering with $category, $color and $page...
}
}
You can simply refactor it to accept parameters through URL segments:
public function filter($category, $color, $page)
{
// Do the filtering with $category, $color and $page...
}
With this in place, your current URL is:
localhost/myshop/products/filter/shirts/blue/1
We need to get rid of that extra filter/ and we're done, right? Quoting from the docs:
Typically there is a one-to-one relationship between a URL string and its corresponding controller class/method. The segments in a URI normally follow this pattern:
example.com/class/method/param1/param2
In some instances, however, you may want to remap this relationship so that a different class/method can be called instead of the one corresponding to the URL.
OK, so we need to remap the current route. You have a few options:
First, is to update your application/config/routes.php file with a new entry:
$route['products/(:any)'] = 'products/filter/$1';
It says that if a URL starts with products/, remap it to the filter method of products class.
Here you can use wildcards and regex patterns to be even more precise about the type of parameters your method accepts.
Another option is that you might want to implement a _remap() method in your controller in order to do the route remapping for you.
in routes.php file, you can write following line
$route['products/(:any)/(:any)/(:num)'] = 'products/filter/$1/$2/$3';
and function will be like following
public function filter($category, $color, $page)
{
echo $category.'<br>';
echo $color.'<br>';
echo $page.'<br>';
}
I'd like my API to handle calls of the such:
/teams/colors
/teams/1/colors
The first would return all colors of all teams, the second would return colors of team 1 only.
How would I write a route rule for this in Laravel?
This should be simple using a laravel route.
Route::pattern('teamid', '[0-9]+');
Route::get('/teams/{teamid}/colors', 'controller#method');
Route::get('/teams/colors', 'controller#method');
Using the pattern, it lets you specify that a route variable must match a specific pattern. This would be possible without the pattern also.
I noticed you mentioned REST in the title. Note that my response is not using Laravel's restful routes system, but its normal routes system, but I'm sure this could be adapted to be restul, or work with the restful system.
Hope this helps.
Edit:
After a bit of looking around, you may be able to use this if you are using Route::resource or Route::controller.
Route::resource('teams', 'TeamsController');
Route::any('teams/{teamid}/colors', 'TeamsController#Method');
// Or to use a different route for post, get and so on.
Route::get('teams/{teamid}/colors', 'TeamsController#getMethod');
Route::post('teams/{teamid}/colors', 'TeamsController#postMethod');
Note: the resource word above can be replaced with ::controller.
*Note 2: I have not tested this and am unable to guarantee it would work, but it does seem possible.*
You may try something like this:
class TeamsController extends BaseController {
// GET : http://example.com/teams
public function getIndex()
{
dd('Colors of all teams');
}
// GET : http://example.com/teams/1/colors
public function getColorsById($id)
{
dd("Colors of team $id");
}
// This method will call the "getColorsById" method
public function missingMethod($parameter = array())
{
if(count($parameter) == 2) {
return call_user_func_array(array($this, 'getColorsById'), $parameter);
}
// You may throw not found exception
}
}
Declare a single route for both methods:
Route::controller('/teams', 'TeamsController');
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.