Yii r1.8 Using Custom URL Rule Classes - php

I am trying to take advantage of the Custom URL Rule Classes in Yii r1.8
I am trying to take something that looks like orgs/view/id/24 and instead display the name of the org as identified by Name in the db (i.e. changing www.mysite.com/orgs/view/id/24 to www.mysite.com/jaysshop dynamically ). Unfortunately I am not getting it to work.
Here is my code:
class OrgsUrlRule extends CBaseUrlRule
{
public $connectionID = 'db';
public function createUrl($manager,$route,$params,$ampersand)
{
if ($route==='orgs/view/id') //even tried 'orgs/view' or 'orgs/index'
{
if (isset($params['Name']))
return $params['Name'];
else if (isset($params['Name']))
return $params['Name'];
}
return false;
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches))
{
}
return false;
}
}
urlManager:
array(
'class' => 'application.components.OrgsUrlRule',
'connectionID' => 'db',
),

I'm giving you this example in the assumption that you want to take the name of a shop from the URL trigger the your controller/action on it
For example:
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
$aParts = explode('/', $pathInfo);
if (count($aParts) == 1) // It's only 1 piece, so a possible "shop name"
{
if (isAValidNameForAShop($aParts[0]))
{
$_REQUEST['id'] = $aParts[0]; // Store it to retrieve it in the controller
return 'orgs/view';
}
}
return FALSE; // Seems like something else, we don't apply
}
Hope that helps you along a bit.

Related

Add Routes for Action Method in Controller

In .NET using AttributeRouting we can add the Route for each Action Method. Something like below
[HttpGet, Route("Create-Project")]
public ActionResult CreateProject()
{
return View();
}
So, in the above code...line 1 indicates that we can mention the route for each Action Method. So url will become something like below..
http://domainname/Create-Project
Question
Is it feasible in PHP MVC CI ? and right now I will have to write the code in route.php in config folder.
In routes.php you can define it
Syntax
$route['short_url_name'] = 'complete/path/for/url';
Example
$route['Contact-Us'] = 'home/contact';
$route['Blog'] = 'home/blog';
$route['Our-Work'] = 'home/work';
so URL look like
http://domain.com/Contact-Us
http://domain.com/Blog
http://domain.com/Our-Work
Codeigniter.com examples
In Application/config/router.php you can add some config like that:
$route['Create-Project'] = 'home/welcome';
So, each time you visit http://domain.com/Create-Project, it will redirect you to http://domain.com/home/welcome
If you are looking for database driven dynamic routes you can rty this, else you can follow what #abdulla has suggested.
Table structure for Dynamic routes(Table name example: routes).
id | slug |route
---------------------------------------------
1 |Pankaj |controller/method/id of user
2 |Abdulla |controller/method/id of user
3 |Niranjan |controller/method/id of user
In controller
$this->load->helper('text');
$slug = $this->input->post("username");// example if you want the username in url as route.
$slug = url_title(convert_accented_characters($slug), 'dash', TRUE);
$slug = $this->Routes_model->validate_slug($slug);
$route['slug'] = $slug;
$route_id = $this->Routes_model->save($route);
$route_id will have the id of route in routes table, insert the route id and slug in users table(For which table you want dynamic routes)
This is the code for route model
<?php
class Routes_Model extends CI_Model {
var $file_name;
function __construct()
{
parent::__construct();
$this->file_name = APPPATH.'config/routes'.EXT;
}
function check_slug($slug, $id=false)
{
if($id)
{
$this->db->where('id !=', $id);
}
$this->db->where('slug', $slug);
return (bool) $this->db->count_all_results('routes');
}
function validate_slug($slug, $id=false, $count=false)
{
if(is_numeric($slug))
{
$slug = '';
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for ($i = 0; $i < 3; $i++)
{
$slug .= $characters[rand(0, strlen($characters) - 1)];
}
}
if($this->check_slug($slug.$count, $id))
{
if(!$count)
{
$count = 1;
}
else
{
$count++;
}
return $this->validate_slug($slug, $id, $count);
}
else
{
return $slug.$count;
}
}
function save($route)
{
// print_r($route);exit;
if(!empty($route['id']))
{
$this->db->where('id', $route['id']);
$this->db->update('routes', $route);
return $route['id'];
}
else
{
$this->db->insert('routes', $route);
return $this->db->insert_id();
}
}
}
?>
One condition i have checked is if the username is a number, i ll generate a length of 3 characters in validate_slug().
Now add the following code in core folder by file name MY_Router.php
<?php
class My_Router extends CI_Router
{
function __construct()
{
parent::__construct();
}
// this is here to add an additional layer to the routing system.
//If a route isn't found in the routes config file. then it will scan the database for a matching route.
function _parse_routes()
{
$segments = $this->uri->segments;
$segments = array_splice($segments, -2, 2);
// Turn the segment array into a URI string
$uri = implode('/', $segments);
// Is there a literal match? If so we're done
if (isset($this->routes[$uri]))
{
return $this->_set_request(explode('/', $this->routes[$uri]));
}
// Loop through the route array looking for wild-cards
foreach ($this->routes as $key => $val)
{
// Convert wild-cards to RegEx
$key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
// Does the RegEx match?
if (preg_match('#^'.$key.'$#', $uri))
{
// Do we have a back-reference?
if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
{
$val = preg_replace('#^'.$key.'$#', $val, $uri);
}
return $this->_set_request(explode('/', $val));
}
}
//look through the database for a route that matches and apply the same logic as above :-)
//load the database connection information
require_once BASEPATH.'database/DB'.EXT;
if(count($segments) == 1)
{
$row = $this->_get_db_route($segments[0]);
if(!empty($row))
{
return $this->_set_request(explode('/', $row['route']));
}
}
else
{
$segments = array_reverse($segments);
//start with the end just to make sure we're not a multi-tiered category or category/product combo before moving to the second segment
//we could stop people from naming products or categories after numbers, but that would be limiting their use.
$row = $this->_get_db_route($segments[0]);
//set a pagination flag. If this is set true in the next if statement we'll know that the first row is segment is possibly a page number
$page_flag = false;
if($row)
{
return $this->_set_request(explode('/', $row['route']));
}
else
{
//this is the second go
$row = $this->_get_db_route($segments[1]);
$page_flag = true;
}
//we have a hit, continue down the path!
if($row)
{
if(!$page_flag)
{
return $this->_set_request(explode('/', $row['route']));
}
else
{
$key = $row['slug'].'/([0-9]+)';
//pages can only be numerical. This could end in a mighty big error!!!!
if (preg_match('#^'.$key.'$#', $uri))
{
$row['route'] = preg_replace('#^'.$key.'$#', $row['route'],$uri);
return $this->_set_request(explode('/', $row['route']));
}
}
}
}
// If we got this far it means we didn't encounter a
// matching route so we'll set the site default route
$this->_set_request($this->uri->segments);
}
function _get_db_route($slug)
{
return DB()->where('slug',$slug)->get('routes')->row_array();
}
}
The above file is very important. As codeigniter first runs files inside core folder, this will create the slug you want. No need to add any routes in routes.php file.
Now while displaying users, you shud echo the slug in your users table.
Lets say controller name is users, method name is edit_user.
your controller code will look like this,
function edit_user($id){
// fetch the data related to $id, and load the view here
}
In url if it sees Niranjan, it will automatically take the route from routes table and will enter your function, edit_user().
Yes Method Looks Lengthy, but works perfect, i use this in all my ecommerce sites.

Yii and multilingual URLs

I'm trying to create multilingual application. I've implemented ability of translationable content and next step should be showing it to user. I want to have ability of changing language depending on URL. I've found a couple of components for those purposes but they all create urls which I don't like. For example, my application's default language is English and I have content which is translated into French. I have page "contacts", for instance. And URLs which will be generated by application will be: mysite.com/en/contacts, mysite.com/fr/contacts, but I want to have mysite.com/contacts for default language and mysite.com/fr/contacts for French language. It's simillar for site's root too. mysite.com/ - for default language and mysite.com/fr for French.
Is there any methods for implementing these functionality?
I'm using XUrlManager extension XUrlManager on GitHub
Yii generates URL's based on UrlManager rules. If you want URL's without /lang/ code - you need just create correct rules. For example, if you dublicate records in rules array:
'rules'=>array(
'<_c:\w+>/<_a:\w+>'=>'<_c>/<_a>',
'<language:\w{2}>/<_c:\w+>/<_a:\w+>'=>'<_c>/<_a>',
);
your URL's will be generated withou /en/ and /fr/, but URL's with code works too. By default, XUrlManager use previously selected language and store this in session or cookie.
If you want only hide /en/ and use /fr/ and others always, you can change your XUrlManager extension with:
public function createUrl($route,$params=array(),$ampersand='&')
{
if(!isset($params['language']) && Yii::app()->language!=='en')
$params['language']=Yii::app()->language;
return parent::createUrl($route,$params,$ampersand);
}
I've found very elegant method for solving my problem on http://www.elisdn.ru
Reimplement CHttpRequest
class DLanguageHttpRequest extends CHttpRequest
{
private $_requestUri;
public function getRequestUri()
{
if ($this->_requestUri === null)
$this->_requestUri = DMultilangHelper::processLangInUrl(parent::getRequestUri());
return $this->_requestUri;
}
public function getOriginalUrl()
{
return $this->getOriginalRequestUri();
}
public function getOriginalRequestUri()
{
return DMultilangHelper::addLangToUrl($this->getRequestUri());
}
}
Reimplement CUrlManager
class DLanguageUrlManager extends CUrlManager
{
public function createUrl($route, $params=array(), $ampersand='&')
{
$url = parent::createUrl($route, $params, $ampersand);
return DMultilangHelper::addLangToUrl($url);
}
}
Change config
return array(
'sourceLanguage'=>'en',
'language'=>'ru',
'components'=>array(
'request'=>array(
'class'=>'DLanguageHttpRequest',
...
),
'urlManager'=>array(
'class'=>'DLanguageUrlManager',
...
),
),
...
'params'=>array(
'translatedLanguages'=>array(
'ru'=>'Russian',
'en'=>'English',
'de'=>'Deutsch',
),
'defaultLanguage'=>'ru',
),
);
Create DMultilangHelper
class DMultilangHelper
{
public static function enabled()
{
return count(Yii::app()->params['translatedLanguages']) > 1;
}
public static function suffixList()
{
$list = array();
$enabled = self::enabled();
foreach (Yii::app()->params['translatedLanguages'] as $lang => $name)
{
if ($lang === Yii::app()->params['defaultLanguage']) {
$suffix = '';
$list[$suffix] = $enabled ? $name : '';
} else {
$suffix = '_' . $lang;
$list[$suffix] = $name;
}
}
return $list;
}
public static function processLangInUrl($url)
{
if (self::enabled())
{
$domains = explode('/', ltrim($url, '/'));
$isLangExists = in_array($domains[0], array_keys(Yii::app()->params['translatedLanguages']));
$isDefaultLang = $domains[0] == Yii::app()->params['defaultLanguage'];
if ($isLangExists && !$isDefaultLang)
{
$lang = array_shift($domains);
Yii::app()->setLanguage($lang);
}
$url = '/' . implode('/', $domains);
}
return $url;
}
public static function addLangToUrl($url)
if (self::enabled())
{
$domains = explode('/', ltrim($url, '/'));
$isHasLang = in_array($domains[0], array_keys(Yii::app()->params['translatedLanguages']));
$isDefaultLang = Yii::app()->getLanguage() == Yii::app()->params['defaultLanguage'];
if ($isHasLang && $isDefaultLang)
array_shift($domains);
if (!$isHasLang && !$isDefaultLang)
array_unshift($domains, Yii::app()->getLanguage());
$url = '/' . implode('/', $domains);
}
return $url;
}
}
After all of these steps your application will have URLs which you want
More information here

CakePHP testing user model with parentNode() method

I'm trying to learn how to do unit testing in general but specifically the project I'm working on is built with CakePHP. I have this parentNode() method in my user model taken directly from the Simple Acl Controlled Application tutorial.
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
if (isset($this->data['User']['group_id'])) {
$groupId = $this->data['User']['group_id'];
} else {
$groupId = $this->field('group_id');
}
if (!$groupId) {
return null; // not tested
} else {
return array('Group' => array('id' => $groupId));
}
}
I wrote the following tests
public function testParentNodeHasNoUserDataOrId() {
unset($this->User->id);
unset($this->User->data);
$this->assertNull($this->User->parentNode());
}
public function testParentNodeWithGroupIDInUserData() {
$this->User->data['User']['group_id'] = 1;
$this->assertEquals(array('Group'=>array('id'=>1)),$this->User->parentNode());
}
public function testParentNodeWithoutGroupIDInUserData() {
$this->User->id = 1;
unset($this->User->data['User']['group_id']);
$this->assertEquals(array('Group'=>array('id'=>1)), $this->User->parentNode());
}
and they all seem to work. My code coverage report however shows that I'm not testing the return null; in the if(!$groupId) block. I can't figure out how to test that line.
As far as I can tell it will never execute. If my User model has no id and no data it returns null in the first if block. if I cheat a bit and give the user some fake data $this->field('group_id') is still returning group 1 (I think it should return false instead but it doesn't)
So when unit testing do you have to test everything? How could I test for the return null if(!$groupId)? If there's code that will never execute should I just remove it?
Thanks!
I figured out how I could test for the "impossible" scenario with the following test
public function testParentNodeWhenUserHasNoGroupId() {
$this->User->id = 1;
$user = $this->User->read();
$user['User']['group_id'] = 0;
$this->User->save($user);
$this->assertNull($this->User->parentNode());
}

CodeIgniter returning different request format / content type html|json|xml etc

What is a simple way in CodeIgniter that I can return a specific content type for request URL extension? For example I want to return json if the url is http://example.com/phone/digits/1.json, html if the URL ends in /1 or /1.html, and XML if the URL ends in /1.xml. This will load a view in the format specified. So in the above example (phone/digits/1.json) would return the json version of the digits method. Here is what I've got so far that is NOT correctly working but gives an idea of what I'm going for. It's currently generating a 404 if no arguments are passed (/phone/digits.json)... Any suggestions would be appreciated.
class Phone extends CI_Controller {
public $layout = FALSE;
public function __construct()
{
if (preg_match('/\.(html|json)$/', $ci->uri->uri_string(), $matches))
{
$this->format = ('html' == $matches[1] || !isset($matches[1])) ? '' : '.json.php';
}
}
public function digits()
{
$this->load->view('phone/digits' . $this->format);
}
Updated for clarity,
I didint understand your question well, asuming you want to simplify your url
its solution for this url : http://domain.com/phone/digits.json (xml or html also)
But with few modification , it can be useful also with http://domain.com/phone/digits/n.json (n - id number)
in config/routes.php
$route['phone/digits.(json|html|xml|php)'] = 'Phone/digits/$1';
$route['phone/digits'] = 'Phone/digits';
Controller
class Phone extends CI_Controller {
public $layout = FALSE;
public function __construct()
{
//hmm
}
public function digits($format = '')
{
if($format == '') {
//default view or something else
}
else {
$this->load->view('phone/digits' . $format);
}
}
Parse the url
grab the extension
use a switch condtional and set relevant output
-
switch( $extension ){
case 'json':
$this->output
->set_content_type('application/json')
->set_output(json_encode(array('foo' => 'bar')));
break;
case 'xml':
$this->output
->set_content_type('application/xml')
->set_output(file_get_contents(some_xml_file.xml));
break;
// etc etc
}

An example of an MVC controller

I have been reading a lot about how and why to use an MVC approach in an application. I have seen and understand examples of a Model, I have seen and understand examples of the View.... but I am STILL kind of fuzzy on the controller. I would really love to see a thorough enough example of a controller(s). (in PHP if possible, but any language will help)
Thank you.
PS: It would also be great if I could see an example of an index.php page, which decides which controller to use and how.
EDIT: I know what the job of the controller is, I just don't really understand how to accomplish this in OOP.
Request example
Put something like this in your index.php:
<?php
// Holds data like $baseUrl etc.
include 'config.php';
$requestUrl = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$requestString = substr($requestUrl, strlen($baseUrl));
$urlParams = explode('/', $requestString);
// TODO: Consider security (see comments)
$controllerName = ucfirst(array_shift($urlParams)).'Controller';
$actionName = strtolower(array_shift($urlParams)).'Action';
// Here you should probably gather the rest as params
// Call the action
$controller = new $controllerName;
$controller->$actionName();
Really basic, but you get the idea... (I also didn't take care of loading the controller class, but I guess that can be done either via autoloading or you know how to do it.)
Simple controller example (controllers/login.php):
<?php
class LoginController
{
function loginAction()
{
$username = $this->request->get('username');
$password = $this->request->get('password');
$this->loadModel('users');
if ($this->users->validate($username, $password))
{
$userData = $this->users->fetch($username);
AuthStorage::save($username, $userData);
$this->redirect('secret_area');
}
else
{
$this->view->message = 'Invalid login';
$this->view->render('error');
}
}
function logoutAction()
{
if (AuthStorage::logged())
{
AuthStorage::remove();
$this->redirect('index');
}
else
{
$this->view->message = 'You are not logged in.';
$this->view->render('error');
}
}
}
As you see, the controller takes care of the "flow" of the application - the so-called application logic. It does not take care about data storage and presentation. It rather gathers all the necessary data (depending on the current request) and assigns it to the view...
Note that this would not work with any framework I know, but I'm sure you know what the functions are supposed to do.
Imagine three screens in a UI, a screen where a user enters some search criteria, a screen where a list of summaries of matching records is displayed and a screen where, once a record is selected it is displayed for editing. There will be some logic relating to the initial search on the lines of
if search criteria are matched by no records
redisplay criteria screen, with message saying "none found"
else if search criteria are matched by exactly one record
display edit screen with chosen record
else (we have lots of records)
display list screen with matching records
Where should that logic go? Not in the view or model surely? Hence this is the job of the controller. The controller would also be responsible for taking the criteria and invoking the Model method for the search.
<?php
class App {
protected static $router;
public static function getRouter() {
return self::$router;
}
public static function run($uri) {
self::$router = new Router($uri);
//get controller class
$controller_class = ucfirst(self::$router->getController()) . 'Controller';
//get method
$controller_method = strtolower((self::$router->getMethodPrefix() != "" ? self::$router->getMethodPrefix() . '_' : '') . self::$router->getAction());
if(method_exists($controller_class, $controller_method)){
$controller_obj = new $controller_class();
$view_path = $controller_obj->$controller_method();
$view_obj = new View($controller_obj->getData(), $view_path);
$content = $view_obj->render();
}else{
throw new Exception("Called method does not exists!");
}
//layout
$route_path = self::getRouter()->getRoute();
$layout = ROOT . '/views/layout/' . $route_path . '.phtml';
$layout_view_obj = new View(compact('content'), $layout);
echo $layout_view_obj->render();
}
public static function redirect($uri){
print("<script>window.location.href='{$uri}'</script>");
exit();
}
}
<?php
class Router {
protected $uri;
protected $controller;
protected $action;
protected $params;
protected $route;
protected $method_prefix;
/**
*
* #return mixed
*/
function getUri() {
return $this->uri;
}
/**
*
* #return mixed
*/
function getController() {
return $this->controller;
}
/**
*
* #return mixed
*/
function getAction() {
return $this->action;
}
/**
*
* #return mixed
*/
function getParams() {
return $this->params;
}
function getRoute() {
return $this->route;
}
function getMethodPrefix() {
return $this->method_prefix;
}
public function __construct($uri) {
$this->uri = urldecode(trim($uri, "/"));
//defaults
$routes = Config::get("routes");
$this->route = Config::get("default_route");
$this->controller = Config::get("default_controller");
$this->action = Config::get("default_action");
$this->method_prefix= isset($routes[$this->route]) ? $routes[$this->route] : '';
//get uri params
$uri_parts = explode("?", $this->uri);
$path = $uri_parts[0];
$path_parts = explode("/", $path);
if(count($path_parts)){
//get route
if(in_array(strtolower(current($path_parts)), array_keys($routes))){
$this->route = strtolower(current($path_parts));
$this->method_prefix = isset($routes[$this->route]) ? $routes[$this->route] : '';
array_shift($path_parts);
}
//get controller
if(current($path_parts)){
$this->controller = strtolower(current($path_parts));
array_shift($path_parts);
}
//get action
if(current($path_parts)){
$this->action = strtolower(current($path_parts));
array_shift($path_parts);
}
//reset is for parameters
//$this->params = $path_parts;
//processing params from url to array
$aParams = array();
if(current($path_parts)){
for($i=0; $i<count($path_parts); $i++){
$aParams[$path_parts[$i]] = isset($path_parts[$i+1]) ? $path_parts[$i+1] : null;
$i++;
}
}
$this->params = (object)$aParams;
}
}
}
Create folder structure
Setup .htaccess & virtual hosts
Create config class to build config array
Controller
Create router class with protected non static, with getters
Create init.php with config include & autoload and include paths (lib, controlelrs,models)
Create config file with routes, default values (route, controllers, action)
Set values in router - defaults
Set uri paths, explode the uri and set route, controller, action, params ,process params.
Create app class to run the application by passing uri - (protected router obj, run func)
Create controller parent class to inherit all other controllers (protected data, model, params - non static)
set data, params in constructor.
Create controller and extend with above parent class and add default method.
Call the controller class and method in run function. method has to be with prefix.
Call the method if exisist
Views
Create a parent view class to generate views. (data, path) with default path, set controller, , render funcs to
return the full tempalte path (non static)
Create render function with ob_start(), ob_get_clean to return and send the content to browser.
Change app class to parse the data to view class. if path is returned, pass to view class too.
Layouts..layout is depend on router. re parse the layout html to view and render
Please check this:
<?php
global $conn;
require_once("../config/database.php");
require_once("../config/model.php");
$conn= new Db;
$event = isset($_GET['event']) ? $_GET['event'] : '';
if ($event == 'save') {
if($conn->insert("employee", $_POST)){
$data = array(
'success' => true,
'message' => 'Saving Successful!',
);
}
echo json_encode($data);
}
if ($event == 'update') {
if($conn->update("employee", $_POST, "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Update Successful!',
);
}
echo json_encode($data);
}
if ($event == 'delete') {
if($conn->delete("employee", "id=" . $_POST['id'])){
$data = array(
'success' => true,
'message' => 'Delete Successful!',
);
}
echo json_encode($data);
}
if ($event == 'edit') {
$data = $conn->get("select * from employee where id={$_POST['id']};")[0];
echo json_encode($data);
}
?>

Categories