Help With Urls

I think this should be a simpler problem than I’m making it, but I’ve been playing with it for a few hours and can’t figure it out. Any help would be appreciated.

I developed a site that has a super-simple content management system that allows an administrator to create/edit pages that are accessed at "url.com/index.php/event/event-id" where event-id is a text string. The page IDs are stored in a database. The Event controller handles the requests using the View action.

I’m trying to use this site as a framework for a new site, but the new site requires that the URLs not have “event” on the front of them. So I want what used to be “/index.php/event/event-id” to just be “/index.php/event-id”. I’ve tried playing with URL rules in the URL manager in config/main.php and I’ve tried adding a View action to the Site Controller that forwards requests to the Event controller, both with no luck. In particular, when I try to use the View action in the Site controller, the “event-id” gets turned into a string containing the word “error”, which then causes a CHttpException error saying the request is invalid - because “error” isn’t an ID it can find in the database.

Like I said, I think there’s probably a simple URL manager solution or something, but I can’t get my head around it. Any ideas? Thanks.

Here’s what I tried as far as URL rules in main.php:


'<id:\w+>'=>'site/<id>'

and

'<id:\w+>'=>'event/<id>'

and

'<\w+>'=>'event/view'

None of these rules seem to have any effect at all. I read through the CUrlManager doc page and don’t really understand why, and I don’t understand why removing all the existing rules in main.php seems to have no effect either - I guess there must be default rules.

One of the urls is "about-us" - so "url.com/index.php/about-us"

If some of my examples are confusing due to changing which controller is handling the request, it’s because I have my Site controller setup to handle the requests also. Event/view and Site/view can both handle it.

I tried the following rule, which works:


'about-us' => array('site/about-us', 'caseSensitive'=>false)

However, I can’t get it to work dynamically…I’m sure I’m just putting it in wrong:


'<\w+>' => array('site/about-us', 'caseSensitive'=>false)

or

'<pg:\w+>' => array('site/<pg>', 'caseSensitive'=>false)

or

'<\w+>' => array('site/view', 'caseSensitive'=>false)

Well, I ghetto-rigged it so it works, but I’d still rather write it correctly if anyone can help. Instead of writing a correct URL rule, I did this in my controller and looked up the model with this:


$event = Yii::app()->request->pathInfo;

That value contains the ID I need to look up in my "Event" table.

I think it would help if you could tell us more precisely how your event id’s are constructed. In turn, we might be able to tell you why \w+ doesn’t match them.

First of all, \w means letter or number or underscore, but not a dash, so you’d rather need to use


[-\w]+

If to take it all together, that’s what you need:




public function actionEvent($slug){

	findPageBySlugIsHere($slug);

}

'<slug:[-\w]+>'=>'event/view'

The second approach I prefer to use in such a case - is to build url rule dynamically, based on actual slugs (event-ids) stored in db.

This way only URLs with existen slugs (event-ids) will be handled by the url rule.

I’ll just provide piece of my code with my names:





class UrlManager extends CUrlManager

{


    /**

 	* Initializes the application component.

 	*/

    public function init()

    {

        $this->setRules();

        parent::init();

    }


    /**

 	*  Sets application url rules

 	*/

    protected function setRules()

    {

        // add text pages support

        $pages = CHtml::listData(Text::model()->findAll(array(

            'select'=>'slug', 

            'condition'=>'`type`=:type', 

            'params'=>array(':type'=>Text::T_PAGE)

            )), 'slug', 'slug');

        if($pages){

            $this->rules = array_merge(

                    array('<slug:('.implode('|', $pages).')>' => 'site/page'),

                    $this->rules

            );

        }

    }

}



In your config file don’t forget to change urlmanager component class:

[size=2]


[/size]


        'urlManager' => array(

            'class'=>'UrlManager',

....



Thanks, yugene! I could have sworn I read somewhere in the docs that said \w covered everything except vertical bar (|) or something like that.

The following rule works for me now:


'<id:[-\w]+>'=>'site/<id>'

I tried to implement some code like yours for a custom UrlManager:




<?php


class QUrlManager extends CUrlManager

{

	/**

	* Initializes the application component.

	*/

	public function init()

	{

		$this->setRules();

		parent::init();

	}


	/**

	*  Sets application url rules

	*/

	protected function setRules()

	{

		$criteria = new CDbCriteria();

		$criteria->select = 'id';


		$slugs = Event::findAll($criteria);


		foreach($slugs as $slug)

		{

			$this->rules[] = array($slug,'site/'.$slug);

		}

	}

}



When I attempt to access a page after implementing that code and setting main.php to use that class for the UrlManager, I get the following error:

I can’t find anything helpful on Google for that. Having the URL rule correct is “good enough” but if you have any insight on that error, I’d appreciate it.

Shouldn’t you have


Event::model()->findAll($criteria)

?

And what looks a bit strange to me is how your final url rules look (even working one): do you have actions that named according to event-ids from db?

Third, I think you’re setting dynamic url rules a bit wrong way. Should be something like:

[size="2"]


$this->rules['<id:('.implode('|', $slugs).')>'] = 'controller/action';

[/size]

Replace controller/action with your actual names and please notice with such approach $slugs must be an array. Just study the example I sent you more carefully.

Thanks again, yugene. Yes, the id in the database is actually the slug. I didn’t think to use the name “slug” when I wrote the site in the first place - it would have been a better name.

Edit: I understand your question better now. No, I don’t have a separate action for each id/slug in the database. I’m honestly not sure why it even works with that rule, now that you mention it.

I see how your rule works now. I thought the CHtml piece was a little bit of a long way around to solve the problem, so I skipped your rule line. What I’m doing is writing a separate rule for each URL, but you’re probably handling it more efficiently by adding every ID to one rule.

I’ll try these suggestions and post again.

Ok, here’s my custom UrlManager code now. It’s working. Thanks a lot.




<?php


class QUrlManager extends CUrlManager

{

	/**

	* Initializes the application component.

	*/

	public function init()

	{

		$this->setRules();

		parent::init();

	}


	/**

	*  Sets application url rules

	*/

	protected function setRules()

	{

		$criteria = new CDbCriteria();

		$criteria->select = 'id';


		$events = Event::model()->findAll($criteria);

		foreach($events as $event)

			$slugs[] = $event->id;


		$this->rules['<id:(' . implode('|', $slugs) . ')>'] = 'event/view';

	}

}



[quote=“BStep, post:10, topic:58478”]

It’s working.

[code]

Great :)

Wow. That looks expensive and unsafe. Don’t you want to pass the event ids through preg_quote() at least?

Da:Sourcerer,

Do you have a suggestion relating to the “expensive” comment? It seems to load quickly and it’ll be a low-traffic site, so it shouldn’t be a problem, but I’d like to learn if you have a thought on it.

I’m the only one who has the ability to insert the event ids into the database, so I don’t think I need to do any filtering on them unless that changes.

Yeah, I must be had to mention in the first post I use dynamic approach to create url-rules for site static pages (about-us and similar) and slugs are created programmatically, which is similar to BStep’s situation. I’m also interested what did you mean by saying ‘expensive’ - do you mean rule will be too slow when parsing with many items in it? Could you be more specific?

Well, I’m deeply concerned since this code will be invoked on every page request. This rule will accumulate more costs as additional event ids will be added. Given that complex regular expressions have the potential to be very cpu intensive, I’d feel uneasy with this on my system. And there’s still that preg_quote() thing.

So, while this solution might work for you, I’m under the impression a lot of technical debts were gathered here. So, some advice from my side:

  • Extend CBaseUrlRule instead of CUrlManager to set your own custom URL rule in place

  • Use DAO instead of ActiveRecords to fetch the event ids

  • Set some caching mechanism in place so the regex doesn’t need to be regenerated on each and every request