How can I achieve this URI scheme in CodeIgniter? - php

I'm making a blog site with CodeIgniter, and I'd like to use the first URI segment as a dynamic category.
So all of these URI's:
http://example.com/category1/some-post
http://example.com/category2/some-post
http://example.com/category3/some-post
Would all go to the "categories" controller, where it would find the category from the database.
I can achieve this easily enough with $routes['(:any)'] = 'categories'; , but of course then all of my other static controllers wouldn't work, such as admin, user, etc.
So how can I achieve this URI scheme while allowing other static pages to not be routed?

You can't unless you create specific routes for each category manually.
For instance:
$route['php'] = 'categories';
$route['java'] = 'categories';

If you look at wordpress they start categories with /category/ and all pages are anything else so you should probably do it that way round.

You will, as Francois said, have to create a route for each category. This can be slightly simplified with a simple array as below:
$categories = array('cat1','cat2','etc...');
foreach($categories as $cat)
{
$routes["$cat/(:any)"] = "category/$cat/\$1"
}
A possible alternative is to use a pre-controller hook in which you could query your database to see if the segment is a category and then direct the request appropriately.
If you'll have a lot of categories or they will be added to and updated frequently, I'd recommend the second option, otherwise, maintaining a simple array isn't too bad.

The other option is to declare all your static controllers in your routes...

Related

Nested Controllers and routes

I'm creating a dashboard where the user can create Clients
Each Client will have: Categories, Employees, ...
Now I'm wondering how to structure the routes.
For example if I create the following: (pseudo code)
Route::get('clients/{id}/');
Route::get('clients/{id}/categories');
Route::get('clients/{id}/categories/{category}');
Route::get('clients/{id}/categories/{category}/questions/{question}');
This seems like a lot of unnecessary parameters..
How do you guys go about this? I really want to split the categories on a seperate page, the employees on a seperate page.
Thank you.
in all of my projects, i avoid using a lot of nested entities in the URL, so i access each one apart, this was also recommended by #jeffrey_way at Laracasts, the training website for laravel.
so, i would do the following:
Route::get('clients/{id}/');
Route::get('categories/{client_id}');
Route::get('categorie/{category}'); //not that i have removed the plural s from categorie(s)
Route::get('question/{question}');
Good luck
It honestly depends on how big your application is going to become, I would probably group them, so still keeping the same structure.
Route::group('clients/{id}', function()
{
Route::get('/');
Route::group('categories', function()
{
Route::get('/');
Route::get('{category}');
Route::get('{category}/questions/{question}');
})
})
Same as yours but I feel it a little cleaning for later if you expand on the categories or clients.
Here in such case I would rather prefer to use only one route will all parameters as in GET method. So, I would have add just one param as bellow:
Route::get('client/create', 'ClientController#store');
So, all the parameters will be maintained by the store method of ClientController like below:
public function store(Request $request){
$category = $request->get('category')
//......
//get other get parameters like this when required
}
When I need to trigger this route I would just do something like below:
Create link
As you know, we can pass our parameters here using our old global friend GET variable.

how to create a method on the fly in ci

I'm writing a control panel for my image site. I have a controller called category which looks like this:
class category extends ci_controller
{
function index(){}// the default and when it called it returns all categories
function edit(){}
function delete(){}
function get_posts($id)//to get all the posts associated with submitted category name
{
}
}
What I need is when I call http://mysite/category/category_name I get all the posts without having to call the get_posts() method having to call it from the url.
I want to do it without using the .haccess file or route.
Is there a way to create a method on the fly in CodeIgniter?
function index(){
$category = $this->uri->segment(2);
if($category)
{
get_posts($category); // you need to get id in there or before.
}
// handle view stuff here
}
The way I read your request is that you want index to handle everything based on whether or not there is a category in a uri segment. You COULD do it that way but really, why would you?
It is illogical to insist on NOT using a normal feature of a framework without explaining exactly why you don't want to. If you have access to this controller, you have access to routes. So why don't you want to use them?
EDIT
$route['category/:any'] = "category/get_posts";
That WOULD send edit and delete to get_posts, but you could also just define those above the category route
$route['category/edit/:num'] = "category/edit";
$route['category/delete/:num'] = "category/delete";
$route['category/:any'] = "category/get_posts";
That would resolve for the edit and delete before the category fetch. Since you only have 2 methods that conflict then this shouldn't really be that much of a concern.
To create method on the fly yii is the best among PHP framework.Quite simple and powerful with Gii & CRUD
http://www.yiiframework.com/doc/guide/1.1/en/quickstart.first-app
But I am a big CI fan not Yii. yii is also cool though.
but Codeigniter has an alternative , web solution.
http://formigniter.org/ here.

Get all "pages" in YII?

I'm trying to create my own xml sitemap. Everything is done except for the part that I thought was going to be the easiest. How do you get a list of all the pages on the site? I have a bunch of views in a /site folder and a few others. Is there a way to explicitly request their URLs or perhaps via the controllers?
I do not want to make use of an extension
You can use reflection to iterate through all methods of all your controllers:
Yii::import('application.controllers.*');
$urls = array();
$directory = Yii::getPathOfAlias('application.controllers');
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileinfo)
{
if ($fileinfo->isFile() and $fileinfo->getExtension() == 'php')
{
$className = substr($fileinfo->getFilename(), 0, -4); //strip extension
$class = new ReflectionClass($className);
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
{
$methodName = $method->getName();
//only take methods that begin with 'action', but skip actions() method
if (strpos($methodName, 'action') === 0 and $methodName != 'actions')
{
$controller = lcfirst(substr($className, 0, strrpos($className, 'Controller')));
$action = lcfirst(substr($methodName, 6));
$urls[] = Yii::app()->createAbsoluteUrl("$controller/$action");
}
}
}
}
You need to know what content you want to include in your sitemap.xml, I don't really think you want to include ALL pages in your sitemap.xml, or do you really want to include something like site.com/article/edit/1 ?
That said, you may only want the result from the view action in your controllers. truth is, you need to know what you want to indexed.
Do not think in terms of controllers/actions/views, but rather think of the resources in your system that you want indexed, be them articles, or pages, they are all in your database or stored somehow, so you can list them, and they have a URI that identifies them, getting the URI is a matter of invoking a couple functions.
There are two possiblities -
Case 1:
You are running a static website then you can find all your HTML inside 1 folder - protected/views/site/pages
http://www.yiiframework.com/wiki/22/how-to-display-static-pages-in-yii/
Case 2:
Website is dynamic. Tasks such as generating and regenerating Sitemaps can be classified into background tasks.
Running background taks can be achieved by emulating the browser which is possible in linux using - WGET, GET or lynx commands
Or, You can create a CronController as a CConsoleCommand. How to use Commands in YII is shown in link below -
http://tariffstreet.com/yii/2012/04/implementing-cron-jobs-with-yii-and-cconsolecommand/
Sitemap is an XML which lists your site's URL. But it does more than that.
It helps you visualize the structure of a website , you may have
category
subcategories.
While making a useful extension, above points can be kept into consideration before design.
Frameworks like Wordpress provide way to generate categorical sitemap.
So the metadata for each page is stored from before and using that metadata it discovers and group pages.
Solution by Reflection suggested by #Pavle is good and should be the way to go.
Consider there may be partial views and you may or may not want to list them as separate links.
So how much effort you want to put into creating the extension is subject to some of these as well.
You may either ask user to list down all variables in config fie and go from there which is not bad or you have to group pages and list using some techniques like reflection and parsing pages and looking for regex.
For ex - Based on module names you can group them first and controllers inside a module can form sub-group.
One first approach could be to iterate over the view files, but then you have to take into account that in some cases, views are not page destinations, but page sections included in another pages by using CController::renderPartial() method. By exploring CController's Class Reference I came upon the CController::actions() method.
So, I have not found any Yii way to iterate over all the actions of a CController, but I used php to iterate over all the methods of a SiteController in one of my projects and filter them to these with the prefix 'action', which is my action prefix, here's the sample
class SiteController extends Controller{
public function actionTest(){
echo '<h1>Test Page!</h1></p>';
$methods = get_class_methods(get_class($this));
// The action prefix is strlen('action') = 6
$actionPrefix = 'action';
$reversedActionPrefix = strrev($actionPrefix);
$actionPrefixLength = strlen($actionPrefix);
foreach ($methods as $index=>$methodName){
//Always unset actions(), since it is not a controller action itself and it has the prefix 'action'
if ($methodName==='actions') {
unset($methods[$index]);
continue;
}
$reversedMethod = strrev($methodName);
/* if the last 6 characters of the reversed substring === 'noitca',
* it means that $method Name corresponds to a Controller Action,
* otherwise it is an inherited method and must be unset.
*/
if (substr($reversedMethod, -$actionPrefixLength)!==$reversedActionPrefix){
unset($methods[$index]);
} else $methods[$index] = strrev(str_replace($reversedActionPrefix, '', $reversedMethod,$replace=1));
}
echo 'Actions '.CHtml::listBox('methods', NULL, $methods);
}
...
}
And the output I got was..
I'm sure it can be furtherly refined, but this method should work for any of the controllers you have...
So what you have to do is:
For each Controller: Filter out all the not-action methods of the class, using the above method. You can build an associative array like
array(
'controllerName1'=>array(
'action1_1',
'action1_2'),
'controllerName2'=>array(
'action2_1',
'action2_2'),
);
I would add a static method getAllActions() in my SiteController for this.
get_class_methods, get_class, strrev and strlen are all PHP functions.
Based on your question:
1. How do you get a list of all the pages on the site?
Based on Yii's way of module/controller/action/action_params and your need to construct a sitemap for SEO.
It will be difficult to parse automatically to get all the urls as your action params varies indefinitely. Though you could simply get controller/action easily as constructed by
Pavle Predic. The complexity comes along when you have customized (SERF) URL rules meant for SEO.
The next best solution is to have a database of contents and you know how to get each content via url rules, then a cron console job to create all the urls to be saved as sitemap.xml.
Hope this helps!

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

I am developing an ecommerce website with CI that has product categories and products. I want to route the URL so that it will go to the products controller, then run the getCategoryByName function for the first segment, then run the getProductByName for the second segment. Here is what I have:
URL:
products/docupen/rc805
routes.php:
$route['products/([a-z]+)'] = "products/getCategoryByName/$1";
$route['products/([a-z]+)/([a-z0-9]+)'] = "products/$1/getProductByName/$2";
But its not working. "docupen" is the category, and "rc805" is the product.
Thanks in advance.
Thank you all for your help. This is what I ended up with for what I needed.
$route['products/:any/:num'] = "products/getProductByID";
$route['products/:any/:any'] = "products/getProductByName";
$route['products/:any'] = "products/getCategoryByName";
My answer builds a bit on Colin's answer.
When I played around with the routes in CodeIgniter I came to the conclusion that the order of the routes was important. When it finds the first valid route it won't do the other routes in the list. If it doesn't find any valid routes then it will handle the default route.
My routes that I played around with for my particular project worked as follows:
$route['default_controller'] = "main";
$route['main/:any'] = "main";
$route['events/:any'] = "main/events";
$route['search/:any'] = "main/search";
$route['events'] = "main/events";
$route['search'] = "main/search";
$route[':any'] = "main";
If I entered "http://localhost/index.php/search/1234/4321" It would be routed to main/search and I can then use $this->uri->segment(2); to retrieve the 1234.
In your scenario I would try (order is very important):
$route['products/:any/:any'] = "products/getProductByName";
$route['products/:any'] = "products/getCategoryByName";
I don't know enough to route the way you wanted (products/$1/getProductByName/$2), but I'm not sure how you would create a controller to handle this particular form of URI. Using the $this->uri->segment(n); statements as mentioned by Colin in your controller, you should be able to do what you want.
You should use the URI class to retrieve the "docupen" and "rc805" segments from your url. You can then use those values in whatever functions you need.
For example, if your url is www.yoursite.com/products/docupen/rc805, you would use the following in your products controller to retrieve the segments and assign them to variables:
$category = $this->uri->segment(2); //docupen
$product = $this->uri->segment(3); //rc805
Then you can use $category and $product however you need to.
CodeIgniter routes don't work well with regex. They are supported, not I can never get them to work. It would be much easier to catch them like this
$route['products/(:any)'] = "products/getCategoryByName/$1";
$route['products/(:any)/(:any)'] = "products/$1/getProductByName/$2";

Categories