Adding dash to actions. (e.g. http://yii.com/admin/add-account)

Hi,

How do I name my method for my action if I want the url to use a dash?

e.g. http://yii.com/admin/add-account

public function actionAdd_Account() // Tried this, didn’t work.

Thanks

In order to have this kind of functionality working i have something like :




class Xyx extends CController{


    // this is what does all the hard work

    public function missingAction($action)

    {

        $action=str_replace('-','_',$action);

        $action='action'.ucfirst(strtolower($action));

        if(method_exists($this,$action))

            $this->$action();

        else

            $this->actionIndex();

    }


    public function actionMy_fancy_action_here()

    {

       //the url can be 

       // A) : /xyz/my-fancy-action-here

       // <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/cool.gif' class='bbc_emoticon' alt='B)' /> : /xyz/my_fancy_action_here

    }




}



Pretty neat ha ? :)

Awesome, wish that was built-in, but awesome nonetheless.

Thanks!

Yes - should definitely be built-in and handled automatically!

Just realize I can’t use your method…

I can’t access this variable ($this->action->id) after calling $this->$action

print_r($this->action) // This won’t work inside the actual action method.

Be sure that you have correct routing rules for getting the ID.

Also, the id can be retrieved via $_GET[‘id’];

it is perfectly normal to not access $this->action->id when using the method i shown to you, because the action doesn’t actually exists, but it is the only way to get this functionality.

Ah, i haven’t tried yet, but maybe you can do something like




//http://www.yiiframework.com/doc/api/1.1/CController#setAction-detail

public function missingAction($action)

    {

        $action=str_replace('-','_',$action);

        $action='action'.ucfirst(strtolower($action));

        if(method_exists($this,$action))

          {

            $this->setAction($action);//see api link above

            $this->$action();

          }  

        else

            $this->actionIndex();

    }




Let me know if it works this way, i haven’t tried.

Think you could also use the actions method of the controller.

http://www.yiiframework.com/doc/api/1.1/CController#actions-detail

Thanks, I got this error though:

PHP Fatal error: Call to a member function getId() on a non-object in /framework/yii-1.1.5.r2654/web/CController.php on line 937

Alright, got it.

This works:

$this->run($action);

If you’re still here can u see if this works:




public function missingAction($action)

    {

        $action=str_replace('-','_',$action);

        if(method_exists($this,$action))

            $this->runAction($action);

        else

            $this->runAction('index');

    }




If it does, then this is the right way to do it .

Let me know.

runAction expects a CAction object, a string won’t work that’s why I had to use ->run().

ah okay, makes sense this way, i don’t have access at my yii project right now that’s why i cannot test, but i’m glad we sorted it out in the end.

This is also an important feature to me for SEO purposes. I wish that Yii would have built-in, native support for this option.

Here is my final solution to support action names with dashes.

It checks also controller’s actions and handles action name like my-action => actionMyAction which is compatible with Zend Framework implementation (intuitive name convention).




/**

* Catch all action - used as a workaround to support dashed action names like my-action => actionMyAction (compatible with Zend Framework)

* @param string $action Action that was not found within this controller

*/

public function missingAction($action){

	$action = explode('-', $action);

	$action = array_map('strtolower', $action);

	$action = array_map('ucfirst', $action);

	$action = implode('',$action);

	if(method_exists($this,'action'.$action) || array_key_exists('action'.$action, $this->actions())){

		$this->setAction($action);

		$this->run($action); 

	}else{

		throw new CHttpException(404, Yii::t('main','Action "{action}" does not exist in "{controller}".', array(

			'{action}' => 'action'.$action,

			'{controller}' => get_class($this),

		)));

	}

}



Cheers

Lubos

Just want to share my workaround because overwriting missingAction is not enough for me because I would like to have dashed controller as well.

Basically I stripped the dashes from the whole url where I got the idea somewhere from the forum by overwriting parseUrl() in UrlManager. (Please point me to that post so I can post this workaround there too)

But one thing is missing is that now the GET parameters with dashes using path will stripped as well and this is not desirable, we need to restore it somehow.

So this is my solution. Tested on 1.1.7. It may not work with future Yii version if those two methods handle differently later on.

With this UrlManager, if you has a url with /dash-controller/dash-action, then you will need DashcontrollerController under controllers and actionDashAction() within it.

Any better workarounds?


class UrlManager extends CUrlManager

{    

    private $hasDash = false;

    

    // remove dash to map dashed url with undashed controller/action

    public function parseUrl($request)

    {

        $url = parent::parseUrl($request);

        $this->hasDash = substr_count($url, "-") > 0;

        return $this->hasDash ? str_replace('-', '', $url) : $url;

    }

    

    // restore dash for GET parameters injected from path

    public function parsePathInfo($pathInfo)

    {

        if($this->hasDash) {

            $oldPath = explode("/", $pathInfo);

            $paths = explode("/", Yii::app()->request->pathInfo);

            for($i=0; $i < count($oldPath); $i++) {

                $newPath[] = $oldPath[$i];

                $key = array_search($oldPath[$i], $paths);

                if($key!==false) {

                    $newPath[] = $paths[$key+1];

                    $i++;

                }

            }

            $pathInfo = implode("/", $newPath);

        }

        parent::parsePathInfo($pathInfo);

    }

}

My solution (which currently seems to work) to map from /dash-controller/dash-action to /DashController/DashAction is:


class UrlManager extends CUrlManager {


    // remove dash to map dashed url with undashed controller/action

    public function parseUrl($request)

    {

        $url = parent::parseUrl($request);

        if (substr_count($url, "-") > 0) {

            

            $url = explode('-', $url);

            $url = array_map('strtolower', $url);

            $url = array_map('ucfirst', $url);

            $url = implode('',$url);

        }

        return $url;

    }

}



To avoid having to use parsePathInfo to restore dash for GET parameters injected from path, I changed my rules from:




...

'<controller:\w+>/<id:\d+>' => '<controller>/view',

'<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',

'<controller:\w+>/<action:\w+>' => '<controller>/<action>',

...



to:




...

'<controller:[0-9a-zA-Z_\-]+>/<id:\d+>' => '<controller>/view',

'<controller:[0-9a-zA-Z_\-]+>/<action:[0-9a-zA-Z_\-]+>/<id:\d+>' => '<controller>/<action>',

'<controller:[0-9a-zA-Z_\-]+>/<action:[0-9a-zA-Z_\-]+>' => '<controller>/<action>',

...



I.e. I replaced the \w with a regular expression which includes the dash.

Here’s my solution for converting camel case to underscores and back again:




class UrlManager extends CUrlManager

{

	public function createUrl($route,$params=array(),$ampersand='&')

	{

		if(preg_match('/[A-Z]/',$route)!==0)

		{

			$route=strtolower(preg_replace('/(?<=\\w)([A-Z])/','-\\1',$route));

		}

		return parent::createUrl($route,$params,$ampersand);

	}


	public function parseUrl($request)

	{		

		$route=parent::parseUrl($request);

		if(substr_count($route,'-')>0)

		{

			$route=lcfirst(str_replace(' ','',ucwords(str_replace('-',' ',$route))));

		}

		return $route;

	}

}



Thanks gazbond, that just helped a lot on my end! I’ve finally got easy creation/use of hyphenated links!

I’ve just found a problem with this method - it gets applied to params and values as well :(

I’ll be looking at fixing it sometime in the future but for now beware!