Codeigniter variable-length parameter list - php

I'm making a tutorialsystem with Codeigniter, but I'm a bit stuck with using subcategories for my tutorials.
The URL-structure is like this: /tutorials/test/123/this-is-a-tutorial
tutorials is the controller
test is a shortcode for the category
123 is the tutorial ID (used in the SQL query)
this-is-a-tutorial is just a slug to prettify the URL
What I do is passing the category as a first parameter and the ID as a second parameter to my controller function:
public function tutorial($category = NULL, $tutorial_id = NULL);
Now, if I want subcategories (unlimited depth), like: /tutorials/test/test2/123/another-tutorial. How would I implement this?
Thanks!

For reading infinite arguments, you have at least two useful tools:
func_get_args()
The URI class
So in your controller:
Pop the last segment/argument (this is your slug, not needed)
Assume the last argument is the tutorial ID
Assume the rest are categories
Something like this:
public function tutorial()
{
$args = func_get_args();
// ...or use $this->uri->segment_array()
$slug = array_pop($args);
$tutorial_id = array_pop($args); // Might want to make sure this is a digit
// $args are your categories in order
// Your code here
}
The rest of the code and the validation depends on what specifically you want to do with the arguments.

If you need variable categories you could use the URI class: $this->uri->uri_to_assoc(n)

Another option you may want to consider is CodeIgniter's controller function remapping functionality which you can use to override the default behaviour. You would be able to define a single function inside a controller that would handle all the calls to that controller and have the remaining URI parameters passed in as an array. You could then do whatever you want with them.
See here for the docs reference on the matter.

Related

Seo-friendly url in CodeIgniter

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>';
}

How to define routes with multiple parameters without pretty urls in Laravel

I am using Laravel. I would like users to be able to perform a search on my website using up to 3 criteria. These criteria are: Class, Brand and Model.
They should be free to use any or all of them when searching. As the relationship between these isn't as simple as Many->1, Many->1, Many->1, and also given the criteria will be numbered if blank, I dont want to use pretty urls to post the search criteria as they would look like this:
/SearchResults/0/BMW/0
which is meaningless to users and search engines. I therefore want to use normal dynamic addresses for this route as follows:
/SearchResults/?Class=0&Brand="BMW"&Model=0
How do I define a route that allows me to extract these three criteria and pass it to a custom method in my resource controller?
I have tried this but it isnt working:
Route::get('/SearchResults/?Class={$class}&Brand={$brand}&Model={$type}', 'AdvertController#searchResults');
Many thanks
The Symfony Routing components fetch the REQUEST_URI server variable for matching routes, and thus Laravel's Route Facade would not pick up URL parameters.
Instead, make use of Input::get() to fetch them.
For example, you would start by checking if the class param exists by using Input::has('class'), and then fetching it with Input::get('class'). Once you have all three, or just some of them, you'd start your model/SQL query so that you may return your results to the user.
You will need to route all to the same method and then, within the controller, reroute that given action to the correct method within the controller.
For that, I recommend using the strategy pattern (read more here).
I would do something like this:
route.php
Route::get('/SearchResults', 'AdvertController#searchResults');
AdvertController.php
use Input;
...
private $strategy = [];
public function __construct(){
$strategy = [
/*class => handler*/
'0'=> $this->class0Handler,
'1'=>$this->class1Handler,
...];
}
private function class0Handler(){
//your handler method
}
public function searchResults(){
if( !array_key_exists(Input::get('class'),$this->strategy))
abort(404);
return $this->strategy[Input::get('class')]();
}
In case you are breaking down search by other types, you define the handler in the $strategy variable.
Strategy pattern has a lot of benefits. I would strongly recommend it.

Dynamic routing

I want to be able to choose a controller based on data gathered form the uri.
I have a categories table and a subcategories table. Basically I have a URL in the following format (:any)/(:any). The first wildcard is a city slug (i.e edinburgh) and the second is going to be either a category or a subcategory slug.
So in my route I search for categories with that route, if I find it, I want to use controller: forsale and method: get_category. If it's not a category I'll look up subcategories, if I find it in there I want to use controller: forsale and method: get_subcategory. If it's not a subcategory I want to continue looking for other routes.
Route::get('(:any)/(:any)', array('as'=>'city_category', function($city_slug, $category_slug){
// is it a category?
$category = Category::where_slug($category_slug)->first();
if($category) {
// redirect to controller/method
}
// is it a subcategory?
$subcategory = Subcategory::where_slug($category_slug)->first();
if($subcategory) {
// redirect to controller/method
}
// continue looking for other routes
}));
First off I'm not sure how to call a controller/method here without actually redirecting (thus changing the url again).
And secondly, is this even the best way to do this? I started using /city_slug/category_slug/subcategory_slug. But I want to only show city_slug/category|subcategory_slug but I need a way to tell which the second slug is.
Lastly, there may be other URL's in use that follow (:any)/(:any) so I need it to be able to continue looking for other routes as well.
Answer to your questions in order:
1. Instead of using different controller#action's you could use a single action and based on the second slug (category or subcategory), render a different view (although I don't like this approach, see #2 and #3):
public class Forsale_Controller extends Base_Controller {
public function get_products($city, $category_slug) {
$category = Category::where_slug($category_slug)->first();
if($category) {
// Do whatever you want to do!
return View::make('forsale.category')->with(/* pass in your data */);
}
$subcategory = Subcategory::where_slug($category_slug)->first();
if($subcategory) {
// Do whatever you want to do!
return View::make('forsale.sub_category')->with(/* pass in your data */);
}
}
}
2. I think /city_slug/category_slug/subcategory_slug is way better than your method! You should go with this one!!
3. Again, you should revise your routes. I always try to make my routes in a way that they don't confuse me, neither Laravel!! Something like /products/city/category/subcategory is much more clear!
Hope it helps (my code is more like a psudocode, it's not been tested )!

CodeIgniter routing with app ID's and entry ID's

I'm new to CodeIgniter and going to be using it for building a sort of reusable application with multiple instances of an application. For example, each instance of the application will have an id "12345", and inside that instance, there will be entry IDs of 1,2,3,4,5,6,7,8, etc.
to do this, I think I will want to be able to using Routing to set up something like:
http://example.com/12345/Entry/Details/1
Where this URI will go to the Details page of the Entry of ID=1, inside application ID 12345. This would be a different group of entries from a url of, say, /12346/Entry/Details/1. Is this a routing rule that needs to be set up, and if so, can someone please provide an example of how this could be configured, and then how I would be able to use 12345, and 1, inside of the function. Thanks so much for your help, in advance.
My suggestion would be that you route your urls like this:
$route['(:any)/{controller_name}/(:any)/(:any)'] = '{controller_name}/$2/$3/$1';
so that the last parameter for the function is always the id of the app (12345/12346). Doing this means that your Entry controller functions will look like this:
class Entry extends CI_Controller
{
function Details(var1, var2, ..., varn, app_id){}
function Someother_Function (var 1, app_id){}
}
you will also need to add a route for functions that don't have anything but the app_id:
$route['(:any)/{controller_name}/(:any)'] = '{controller_name}/$2/$1'; //This may work for everything.
I hope this is what you we're asking...
Edit:
If you are only going to be using numbers you could use (:num) instead of (:any)
You can achieve a routing like that by adding this rule to the application/config/routes.php file:
$route['default_controller'] = "yourdefaultcontroller";
$route['404_ovverride'] = "";
// custom route down here:
$route['(:num)/entry/details/(:num)'] = "entry/details/$1/$2",
of course assuming your URI to be like the example.
In your controller "Entry" you'll have a method "details" which takes 2 parameters, $contestID and $photoID, where $contestID is the unique instance you're assigning, while $photoID is the other (assumed) variable of your url (last segment).
class Entry extends CI_Controller(
{
function details {$contestID, $photoID)
{ //do your codeZ here }
}
See URI routing for more info on that. You might also want to consider the __remap() overriding function, in case.

CodeIgniter - unlimited parameters?

I am currently using CodeIgniter.
I am trying to write a function that can take an unlimited number of paramaters.
So in the controller it will be something like
function test($name, $others){
foreach($others){
//do something
}
}
and I could call it like
example.com/control/test/some name/param1/param2/param3/param4/param5...
How can I set this up?
you could also do it like this:
function foo($params=array())
{
$params=func_get_args();
//print_r($params);
}
so any url like:
site.com/controller/foo/param1/param2/param3/param4
would create an array of parameters.
You can get an associated array of URI segments with the uri_to_assoc function in the URI class. So in your controller, you might do something like this:
function test($name){
$uri_seg = $this->uri->uri_to_assoc(4);
foreach($uri_seg as $para){
// Do something with each of the URI segments passed in here after $name
}
}
It's not clear why you need an unlimited (variable) number of arguments, but you may want to consider why you need them.
You can use alternative URI semantics for certain types of arguments. Often the URI design is the path to a thing in a system. Unlimited parameters may not refer to the resource, but rather describe its attributes. Tags are a good example of this.
/chicken/red+tall+fat
Tags implement easily in CodeIgniter with a single parameter.
If the parameters refer to a node, you can consider a similar approach:
/hen-house.bunk4.topshelf/chicken
Or, if the hierarchy is important as a path, you can use the func_get_args() solution above.

Categories