.htaccess URL rewrite
Vanneto

Hello everyone!

I have a small issue with URL rewriting in .htaccess. Not that anything is not working, its just the issue with the size of the whole thing.

Here is a sample from my file:

RewriteEngine On

RewriteRule ^/news/([0-9]+)/$ /news.php?news_id=$1
RewriteRule ^/article/([0-9]+)/$ /article.php?art_id=$1
RewriteRule ^/user/([0-9]+)/$ /show_user.php?user_id=$1
RewriteRule ^/login/$ /login.php
...

See? It gets awfully long. What can I do here? Is there anything I can do? I thought of a unified URL structure like NEWS/ACTION/ID so its like news/delete/23 or news/edit/35 but that doesn't work for all of the stuff.

So, is there anything I can do? Or am I cursed to editing the file every time I add or change something?

X-G

RewriteRule ^(.*) index.php?q=$1 [L,QSA]

Vanneto

OK, this opens up a new problem though, how do I parse the input? Something like this:

index.php?q=/news/edit/45/monkey/5/blah/10

So, the first parameter would always be the file ( news.php ), the second would be the name of a parameter and then the value, the next the name and then the value etc.

So the upper URL would be:

news.php?edit=45&monkey=5&blah=10

Correct?

CGamesPlay

I got this from CakePHP:

RewriteEngine on
RewriteRule ^$ engine/index.php [L]
# Translate path into the webroot if not already done
RewriteRule !app/webroot/ - [C]
RewriteRule (.*) app/webroot/$1
# If file doesn't exist in webroot then use Cx
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* engine/index.php

This is cool. It works like this: you have a website at http://www.contoso.com/mysite/. On the server, mysite/app/webroot is the folder where you put things like static HTML pages, CSS files, whatever, and mysite/engine is where CakePHP lives. When a user requests http://www.contoso.com/mysite/style.css, the RewriteRules check mysite/app/webroot/style.css. If it exists, it serves that file. If it doesn't, the rules instead run mysite/engine/index.php, which uses $_SERVER["REQUEST_URI"] to get the name of the requested file (so, if you request http://www.contoso.com/mysite/news/delete/27, it will look like "mysite/app/webroot/news/delete/27", and you can just remove everything up to "app/webroot").

This method is better than X-G's because the query string isn't affected by the rules, and because it allows static documents to co-exist.

[append]

You parse the output by exploding your request (explode("/", $request)), and then parsing the resulting array (array("news", "edit", "45", "monkey", "5", "blah", "10")). If you're using CakePHP or CodeIgniter or something similar, then you have code like this:

class controller_news
{
    function edit($id)
    {
        // news/edit/45 calls this method with $id = 45.
        // You can get all the parameters passed like this:
        $args = func_get_args();
        var_export($args);
        // Prints: array("45", "monkey", "5", "blah", "10")
    }
}

BAF

In one project, I was just passing everything to a file like X-G said, checking a set directory to see if the file existed, if it did I served it up, if not I checked for plugins/pages before 404ing.

Thomas Fjellstrom

You should see the stuff I setup for the wiki:

1RewriteEngine on
2 
3RewriteCond %{REQUEST_URI} ^/bin/view/.+
4RewriteRule ^/bin/view/[^/]+/(.*) /$1 [R]
5 
6RewriteCond %{REQUEST_URI} ^/bin/view/.+
7RewriteRule ^/bin/view/[^/]+ / [R]
8 
9RewriteCond %{REQUEST_URI} ^/bin/view$
10RewriteRule ^/bin/view$ / [R]
11 
12RewriteCond %{HTTP_USER_AGENT} Ask\ Jeeves/Teoma [OR]
13RewriteCond %{HTTP_USER_AGENT} VoilaBot [OR]
14RewriteCond %{HTTP_USER_AGENT} BecomeBot [OR]
15RewriteCond %{HTTP_USER_AGENT} Exabot [OR]
16RewriteCond %{HTTP_USER_AGENT} Googlebot [OR]
17RewriteCond %{HTTP_USER_AGENT} Yahoo!\ Slurp [OR]
18RewriteCond %{HTTP_USER_AGENT} Google\ Desktop [OR]
19RewriteCond %{HTTP_USER_AGENT} MQBOT/Nutch [OR]
20RewriteCond %{HTTP_USER_AGENT} msnbot [OR]
21RewriteCond %{HTTP_USER_AGENT} YahooSeeker [OR]
22RewriteCond %{HTTP_USER_AGENT} OutfoxBot [OR]
23RewriteCond %{HTTP_USER_AGENT} FreshCrawler [OR]
24RewriteCond %{HTTP_USER_AGENT} robot [OR]
25RewriteCond %{HTTP_USER_AGENT} spider [OR]
26RewriteCond %{HTTP_USER_AGENT} Twisted\ PageGetter [OR]
27RewriteCond %{HTTP_USER_AGENT} yacybot [OR]
28RewriteCond %{HTTP_USER_AGENT} yacy [OR]
29RewriteCond %{HTTP_USER_AGENT} Yahoo-MMCrawler
30RewriteRule ^(.*) $1?useskin=simple [C,QSA]
31 
32# Verifying if user forgot to put trailling slash. If so, we'll rewrite to Main_Page
33RewriteCond %{REQUEST_URI} ^/?$
34RewriteRule ^/?$ /index.php/Main_Page [L]
35 
36# rewrite Actions
37# 'edit', 'watch', 'unwatch', 'delete','revert', 'rollback', 'protect',
38# 'unprotect','info','markpatrolled','validate','render','deletetrackback','print',
39# 'dublincore','creativecommons','credits','submit','viewsource','history','raw', 'purge'
40RewriteCond %{REQUEST_URI} ^/(purge|edit|watch|unwatch|delete|revert|rollback|protect|unprotect|info|markpatrolled|validate|render|deletetrackback|print|dublincore|creativecommons|credits|submit|viewsource|history|raw|purge)/
41RewriteRule ^/(purge|edit|watch|unwatch|delete|revert|rollback|protect|unprotect|info|markpatrolled|validate|render|deletetrackback|print|dublincore|creativecommons|credits|submit|viewsource|history|raw|purge)/(.+) /index.php/$2?action=$1 [L,QSA]
42 
43# Don't rewrite requests for files in MediaWiki subdirectories,
44# MediaWiki PHP files, HTTP error documents, favicon.ico, or robots.txt
45#RewriteCond %{REQUEST_URI} !^/(stylesheets|pub|config|stats|skins|bin|languages|locale|math|images)/
46#RewriteCond %{REQUEST_URI} !^/.+.php
47RewriteCond %{REQUEST_URI} !^/error/(40(1|3|4)|500).html
48RewriteCond %{REQUEST_URI} !^/favicon.ico
49RewriteCond %{REQUEST_URI} !^/robots.txt
50 
51 
52# Do main rewrite
53RewriteCond /var/www/%{REQUEST_FILENAME} !-f
54RewriteCond /var/www/%{REQUEST_FILENAME} !-d
55RewriteCond %{REQUEST_URI} !^/(stylesheets|pub|config|stats|skins|bin|languages|locale|math|images|history|icons)/
56RewriteCond %{REQUEST_URI} !^/(stylesheets|pub|config|stats|skins|bin|languages|locale|math|images|history|icons)$
57RewriteCond %{REQUEST_URI} !^/.+\.php
58RewriteCond %{REQUEST_URI} !^/~.*
59RewriteRule ^(.*)$ /index.php$1 [L,QSA]

X-G

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

Was also in the file. I just neglected to mention that. :P

CGamesPlay
Quote:

#RewriteCond %{REQUEST_URI} !^/.+.php

Technically, "!^.+\.php" :)

Matthew Leverton

Perhaps you'd rather have news/edit/35 be news/35/edit, which is hard to do (along with other similar style of URLs) unless you use a global dispatcher.

I use this for my CMS in the /var/www/cms/site directory:

Quote:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /.dispatcher.php [QSA,L]

The document root directory is: /var/www/cms/site/domainname.com/web. I then use an alias in each vhost to point /.dispatcher.php to /var/www/cms/site/dispatcher.php.

That single file just looks at the domain name, loads the configuration file for it, loops through each module that implements IRequestHandler, and lets them determine who gets to serve the request. (First come, first serve.)

It's beautiful because URLs can be anything and every site can share the same exact PHP module code.

Each module can implement any URL how it sees fit. So the news module could see /news/2008/01/01/newyear and decide to handle the request by loading up the news item and calling $tpl->display('News/view') which would first load up the website's specific news template.

The CMS module can look at URLs in a database and display HTML as entered via a WYSIWYG online editor. And anything in the /var/www/cms/site/domainname.com/template directory automatically gets viewed as a last resort (if the name matches the URL) so you can easily add one-off pages per each site.

Those are just examples of a couple modules; there are plenty of ways to do this. The key thing is, though, that "one PHP file per page" based websites are not good.

Vanneto

I'll think about the modules idea. I think its a pretty good idea. But I only have one site, so isn't it kinda pointless?

Quote:

Those are just examples of a couple modules; there are plenty of ways to do this. The key thing is, though, that "one PHP file per page" based websites are not good.

Hmm... I am getting something wrong here. Don't you do "one file per page" anyway? Isn't the difference in the fact that you hide it?

Matthew Leverton

You're right that it is a lot of overhead if never reused. But one site becomes two sites ... and so on until you are sick of writing the same stuff over and over again.

And no, my approach is far from page based. The entire module sits inside of a class. For instance, a "pretend" module:

1<?php
2 class Module_News extends Module implements IRequestHandler
3 {
4 public function handleGetRequest(HTTPRequest $req)
5 {
6 // /news/view/$id
7 if ($req->path[0] != 'news') return FALSE; // doesn't handle the url
8 
9 switch ($req->path[1])
10 {
11 case 'view':
12 $tpl->news = DBO_News::find($req->path[2]);
13 $tpl->display('news/view');
14 return TRUE;
15 
16 case 'foo': // other logic, etc
17 }
18 
19 return FALSE;
20 }
21 }
22?>

All of the logic goes inside the class. That's the stuff you don't ever want to rewrite. If I have a second website that needs something a little bit different, I first try to add the functionality via configuration. If that fails to be practical, I just extend the class module and use it for that particular website.

The template stuff is per page, but that's fine. The basic news "view" template might look like:

<mef:template container="default">
  <html>
    <h1>{news.title}</h1>
    <div>{news.body}</div>
  </html>
</mef:template>

Then for a particular website, I'll probably end up copy/pasting it into the website's local template directory and editing it to look like however I want.

The key is that I can easily build a new website without having to write a line of duplicated PHP. I expect to write custom template files ... that's what makes web sites unique. But the "business logic"? It's mostly reusable.

This is my approach that works great for me, and you may find something you like better. But never resort to spaghetti PHP code. It's never worth it.

Vanneto

Now I get it. So, this is just theoretical, you have modules in a directory, like modules and when someone requests the page the request is send to index.php or whatever the main file is.

Then the modules directory ( or database, don't really know ) is scanned for modules. And since every module is derived from Module you can just do:

$request = new IRequest();
$request->page = ...;

while(!EndOfModules())
{
    $module = new Module($data["module_name"]);
    $module->handleGet($request);
    
}

Wow, even if this is not exactly what you use, its pretty darn good. This way you can easily add way more modules without any hassle. No hard-coding... Real flexible. Hehe I'm all excited now! ;D

Just one more thing, how do you handle where the stuff gets displayed. The website has a certain structure. Header, footer, navbar etc. How do you know where each module will go? It could have a $type member var in it... Or is there a better way to handle this?

Matthew Leverton

I have an ini file per website (domain) that says which modules to load:

[modules]
News=true
AutoTemplate=true

Those are looped through and added to a modules array within the base Application class.

I have a function called something like Application::callInterface('IRequestHandler', 'handleRequest', $args) and that is responsible for looping through each module, calling 'handleRequest' until one of them returns TRUE. It's been a while since I've looked at my base classes, so I don't remember exactly what I'm doing.

Regarding your second question, I'm not sure exactly what you are talking about. But I use "containers." One might look like:

<html>
<head><title>Default Container</title></head>
<body>
 
{html} 

</body>
</html>

The {html} correlates to the <html> field in the template file. Same goes with any other field you want to pass as a tag or as a parameter (in the <mef:container /> tag).

Also note that templates can also pull in data as long as a recognized object is used.

// loop through recent threads
<mef:loop collection="news.recent(count: 5)" item="n">
  {n.title}
</mef:loop>

For instance, that would call DAO_News::recent() inline. (The DAO class of objects are just read-only interfaces from a template to the more general purpose DBOs.)

That allows templates files to do more than what they might originally have been intended to do by the module writer. (e.g., maybe on your News/View template page you also want to show something from a different module.)

Vanneto

Seems pretty logical. But, there is one line I do not understand as I do not have so much experience in the OOP world:

class Foo extends Bar implements Baz

So, what does this mean? That Bar implements Baz and that Foo extends Bar? That would mean that Baz is an abstract class and that Bar implements it and Foo extends the implemented class Bar?

Regarding the HTML thing, I think there was a misunderstanding. I maybe just talking garbage but anyway. Allegro.cc has two sidebars and the center where the content is. Now, I am speculating, but I reckon that the sidebars are constant ie. are not tied to modules? You know what I mean? Sure, the content is tied to a module, but what about the sidebars etc.? Guess theres not much need for that anyway... Just asking.

BAF

Baz is an interface, so class Foo inherits from Bar and implements everything required by the Baz interface.

Vanneto

OK, I thought it was something like that. Thanks for destroying my doubts. :)

Fladimir da Gorf

Looking at the PHP code Matthew posted, does PHP really look almost like Java these days?

Thomas Fjellstrom
Quote:

Looking at the PHP code Matthew posted, does PHP really look almost like Java these days?

Yes. A horrible mix of Perl and Java. I still wonder why people like it.

Vanneto

Don't make me laugh! Even PHP at its worst isn't nearly as bad as perl! Yuck! :-X

BAF

Good PHP isn't bad. Bad PHP is horrible. cough*zencart*cough.

Thomas Fjellstrom
Quote:

Don't make me laugh! Even PHP at its worst isn't nearly as bad as perl! Yuck! :-X

You obviously haven't seen actual live PHP applications then. Right now I'm doing a small job working with ZenCart, if you feel like going out of your mind, take a look at all the template files. Almost every PHP program I've ever seen is made the same way.

However, my Perl programs are much much cleaner.

Vanneto

Look at it this way, PHP is the most popular scripting language currently. So naturally there will be more people that write ugly code. Its also very simple, contributing to people writing said bad code.

Perl is scary, thats why beginners don't use it*. :P But PHP code can be very clean, readable and efficient if written in the right way. Works for any language.

* At least not as much as PHP IMHO.

ImLeftFooted

PHP is pretty fast and has an enormous amount of junk that makes web development easy. That junk is really invaluable.

(more on other bad coders coding in php): You shouldn't judge your language choices by the stupidity of other coders.

Thomas Fjellstrom
Quote:

Look at it this way, PHP is the most popular scripting language currently. So naturally there will be more people that write ugly code

Clueless much? There area actually still more usefull sites that run perl than PHP. Well known fact, php was once implemented with Perl.

Quote:

Perl is scary, thats why beginners don't use it. :P

Its the same syntax as php! Except theres three separate special datatypes and BUILT IN regexes. I don't see much of a difference.

Quote:

But PHP code can be very clean, readable and efficient if written in the right way.

Oh sure, apply that to your precious php, and not perl? :P

Perl was my first programming language. Taught it to myself by reading a book and playing with scripts. If I can do it, anyone can.

Quote:

PHP is pretty fast and has an enormous amount of junk that makes web development easy. That junk is really invaluable.

Junk is indeed the right term. And it dumps it all in the global namespace. hundreds of functions you're likely to not use in most apps!

Quote:

(more on other bad coders coding in php): You shouldn't judge your language choices by the stupidity of other coders.

Except when most of the apps are coded in that way, then its more of a language issue. PHP was created to write spagetty <?php code ?> crap.

BAF

If you want to learn how not to write something, look at Zencart.

ImLeftFooted
Thomas said:

Well known fact, php was once implemented with Perl.

Not that it's related, but hasn't php been implemented in a lot of language?

Thomas said:

Except when most of the apps are coded in that way, then its more of a language issue.

:-X You can't actually think that... Whats the point of a language but to aide you in some purpose?

Thomas Fjellstrom
Quote:

Not that it's related, but hasn't php been implemented in a lot of language?

It started out as a Perl program, and is now written in C afaik. From the state of PHP code itself, I can only imagine what php's C code looks like :'(

Quote:

:-X You can't actually think that... Whats the point of a language but to aide you in some purpose?

Ok, then it must be the other option, MOST php coders are completely worthless. Then again if it is that, then PHP attracts MOSTLY worthless coders, which doesn't put php in a good light.

Vanneto
Quote:

Clueless much? There area actually still more usefull sites that run perl than PHP. Well known fact, php was once implemented with Perl.

If George Bush and Vladimir Putin prefer some Zango Cola over Coca-Cola then that does not mean that Coca-Cola is any less popular...

Quote:

Oh sure, apply that to your precious php, and not perl?

Sure it applies to Perl, but you started the debate.

Quote:

Perl was my first programming language. Taught it to myself by reading a book and playing with scripts. If I can do it, anyone can.

Same here man, just replace Perl with PHP. ;)

Quote:

Junk is indeed the right term. And it dumps it all in the global namespace. hundreds of functions you're likely to not use in most apps!

Yeah it has its quirks, but PHP 6 will fix that AFAIK.

Quote:

Except when most of the apps are coded in that way, then its more of a language issue. PHP was created to write spagetty <?php code ?> crap.

Popularity applies here... 90% of the code is bad because 90% of the users are beginners that don't have a clue. Its not a language issue... Anymore. Granted, it was some time ago... cough register globals cough

EDIT:

Quote:

It started out as a Perl program, ...

No:

Quote:

PHP was written as a set of Common Gateway Interface (CGI) binaries in the C programming language by the Danish/Greenlandic programmer Rasmus Lerdorf in 1994, to replace a small set of Perl scripts he had been using to maintain his personal homepage.

Thomas Fjellstrom
Quote:

Popularity applies here... 90% of the code is bad because 90% of the users are beginners that don't have a clue. Its not a language issue... Anymore. Granted, it was some time ago... cough register globals cough

Take any popular php app and prove to me that its not crap code wise. Dare you.

Quote:

PHP was written as a set of Common Gateway Interface (CGI) binaries in the C programming language by the Danish/Greenlandic programmer Rasmus Lerdorf in 1994, to replace a small set of Perl scripts he had been using to maintain his personal homepage.

I just said that. It started out as a perl program/script, and was then rewritten in C.

Vanneto

Yeah but technically, the Perl scripts weren't PHP! They were PHP after they were rewritten!

Anyway, phpBB 3?

Thomas Fjellstrom
Quote:

Yeah but technically, the Perl scripts weren't PHP! They were PHP after they were rewritten!

This is only an asumption, but I'm betting his original site loaded and executed Php like template files with embeded Perl code, which explains PHP's similarity to Perl.

Quote:

Anyway, phpBB 3?

phpBB? At least v3 seems to be using a templating system of sorts (custom format using html comments? huh?). Being brand new, I know little about it. But if its anything like the prior phpBB's, its pure crap.

Vanneto
Quote:

phpBB? At least v3 seems to be using a templating system of sorts (custom format using html comments? huh?). Being brand new, I know little about it. But if its anything like the prior phpBB's, its pure crap.

OK Thomas, your turn! You show me some popular application written in Perl thats not crap! 8-)

BAF

Any popular application is likely pure crap. Thats why I use custom code for the most part. 8-)

Thomas Fjellstrom

Ever heard of slashcode? How 'bout apt?

wp said:

Large projects written in Perl include Slash, Bugzilla, TWiki and Movable Type. Many high-traffic websites, such as bbc.co.uk, Amazon.com, LiveJournal.com, Ticketmaster.com and IMDb.com[16] use Perl extensively.

BAF

Those high traffic websites aren't popular applications. :P A.cc uses PHP, and works just as well as those sites, just not on as large a scale.

Vanneto
Quote:

Many high-traffic websites, such as bbc.co.uk, Amazon.com, LiveJournal.com, Ticketmaster.com and IMDb.com[16] use Perl extensively.

Cheater! Those are not available to the public and the applications aren't popular but the websites are! Different!

Hence, I could say Allegro.cc, I dare you to say otherwise, Matthew will smite you! ;D:P

Thomas Fjellstrom

Wow, you guys totally missed the first sentence. Try paying attention.

Quote:

A.cc uses PHP, and works just as well as those sites, just not on as large a scale.

Scalability is part of usability.

Vanneto
Perl said:

Large projects written in Perl include Slash, Bugzilla, TWiki and Movable Type.

PHP said:

MediaWiki, Typo3, phpBB3, Drupal

apt is not a web application. :P I thought we were talking about those. Anyways, time to go look at all the projects source code and vote which one has the dirtiest...

BAF
Quote:

Scalability is part of usability.

I didn't say it wasn't scalable. I just said it wasn't on as large a scale. The sites from WP likely run on quite a few servers, whereas a.cc runs on one.

Thomas Fjellstrom

Intead of those apps, look at PHPBB2, ZenCart, and Moodle.

BAF

http://avatraxiom.livejournal.com/58084.html

Also, nobody ever claimed those apps were written well. What about Drupal or e107? There are many PHP apps that aren't coded badly. (Although I've never looked at the source of those applications, I've never heard anybody say anything bad about them).

ImLeftFooted

Dude Thomas, why are you so angry about PHP?

BAF

He has to deal with Zencart. I only looked at it for under an hour and that was almost enough to make me angry about PHP. Zencart's devs do have a place on my hated developers list, though.

Thomas Fjellstrom
Quote:

Dude Thomas, why are you so angry about PHP?

I'm more iritated about Vaneeto's (and many other people's random) Perl comments. But then I've just spent 3 days trying to hack an old template into a new version of ZenCart. God my head hurts.

Vanneto

Oh sure, sure, now I write random comments... I wonder what this is:

Quote:

Yes. A horrible mix of Perl and Java. I still wonder why people like it.

Thomas Fjellstrom
Quote:

Yes. A horrible mix of Perl and Java. I still wonder why people like it.

You can mix one or more non horrible things and get a horrible result you know.

BAF

Personally, my Perl comments are in return to PHP comments. I don't hate Perl. I don't like how the code looks, but I don't have enough experience with it to form a solid opinion.

Vanneto
Quote:

You can mix one or more non horrible things and get a horrible result you know.

That was not the point. The point was that you were the first to make a random comment. I did not write random comments about Perl, one maybe, but all others were not random.

Well, at least not any less or more random then yours.

Thomas Fjellstrom

I didn't necessarily mean yours was random, most people's are.

Vanneto

Well, seeing as how we single handedly derailed this thread, I might as well give out cookies for the ones that helped! (with the problem at hand, not the derailing part :P)

Matthew Leverton
Quote:

Regarding the HTML thing, I think there was a misunderstanding. I maybe just talking garbage but anyway. Allegro.cc has two sidebars and the center where the content is. Now, I am speculating, but I reckon that the sidebars are constant ie. are not tied to modules? You know what I mean? Sure, the content is tied to a module, but what about the sidebars etc.? Guess theres not much need for that anyway... Just asking.

A.cc isn't written on my complete CMS engine, but if it might look something like this if it weren't static:

1// index.tpl
2<mef:template container="three-columns">
3 
4 <html>
5 <ul>
6 <li mef:loop="n; news">
7 <h2>{n.caption</h2>
8 <div>{n.body}</div>
9 </li>
10 </ul>
11 </html>
12 
13 <leftcolumn>
14 <h2>Allegro.cc</h2>
15 <ul>
16 <li>Depot</li>
17 <li>Forums</li>
18 </ul>
19 </leftcolumn>
20 
21</mef:template>

The first layer of tags is just a way to name a block of HTML. The container file could then reference them as {html} and {leftcolumn}.

Or another approach could be:

1// index.tpl
2<mef:template container="three-columns" sidebar="Depot,Resources">
3 // blah
4</mef:template>
5 
6// three-columns.tpl
7<html>
8 
9<body>
10 
11<div id="left-column">
12 <acc:column resources="{sidebar}" /> // a custom tag written in PHP
13</div>
14 
15<div id="main">
16 {html}
17</div>
18 
19</body>
20 
21</html>

There's plenty of ways to do it. Obviously you want to keep the template files as close to pure HTML as possible. Sometimes this means creating custom tags that generate HTML that's otherwise hard to represent.

Vanneto

OK I got something figured out. Just need to put that down into code now. I have a question though, everything beyond the base directory of the domain is the Query string?

And I still don't get this line:

class News_Module extends Module implements IRequestHandler

I mean, I thought I got it but I don't. I tried to code something similar and now I know my ignorance is bigger then I thought. :P

Matthew Leverton

An interface is an empty class:

interface Foo
{
  public function bar();
}

class Bar implements Foo
{
}
// error - it has no function called bar

In a language like PHP, it's more of just a formality. It's just a way to make sure a class implements a set of functions.

Vanneto

OK I just got some of the basics working, pretty nifty I must say. So, do you actually have two separate methods in a module - that handle get and post requests? For example:

public function handle_get_request(httpreq $request);
public function handle_post_request(httpreq $request);

Although I do not see the benefit in this as you can just as easily handle everything as GET and then check for $_POST vars in the get handler. Maybe I can make some sort of automatic form handler for these things.

Matthew Leverton

I handle them separately just because there's an ideological difference: GET should be used when requesting a read-only page, and POST should be used when pushing data to a read/write page.

But technically there's little difference between them, and they could easily be handled together.

CGamesPlay

I don't like the idea of splitting GET and POST. It would seem like the biggest advantage of this method comes from being able to GET a form and then POST to the same URL, and by splitting it, you make the code more modular and readable. But I've found that much of the code is shared between the two requests (typically, the code for a POST request is just a preamble to the code for the GET request), and so you harm your readability by splitting it.

Matthew Leverton

Yes, but I handle it much differently within my CMS. I don't care what URL you submit the POST data to. It's all encapsulated within a POST wrapper request. So my POST request handler looks more like:

function handlePost($post)
{
  if ($post->namespace != 'News') return false;

  switch ($post->action)
  {
    case 'delete':
      // check permissions
      // validate fields
      // perform operation
      // redirect
    break;
  }
}

This is because I don't want the CMS to define the URLs for all the applications it hosts. In short, you make a request with the namespace/action (e.g., News.Delete) along with a redirect URL on success (if wanted).

If you don't redirect and there is no error, then the "GET" request handler would kick in.

Vanneto

OK, I got it working with a prefixed set of Modules. Now, does PHP have any INI handling functions? Yeah, I know I can write my own and quite easily. But, just to avoid the hassle... Is there?

Matthew Leverton
Vanneto

Great, got everything working. Except one little thing. For example, www.mysite.com/ should display the news. So, it works if the url is www.mysite.com/news/ or something but how do I do it if no params are defined?

EDIT:

Another thing, lets say you have a Statistics module. Now, this has to be called in every page. But if News is first and the param is news then News module will return true and loading of modules stops there. Confusion! :P

Matthew Leverton
Quote:

Great, got everything working. Except one little thing. For example, www.mysite.com/ should display the news. So, it works if the url is www.mysite.com/news/ or something but how do I do it if no params are defined?

I don't understand your problem. There's nothing stopping any module from handling any request.

Quote:

Another thing, lets say you have a Statistics module. Now, this has to be called in every page. But if News is first and the param is news then News module will return true and loading of modules stops there. Confusion!

A module could do something transparent within the handleRequest (e.g., log to a database), but still return FALSE as to say that it didn't handle the request. Just make sure the Statistics module is loaded first. I treat the INI file as first come, first serve. That's been good enough for me.

Vanneto
Quote:

I don't understand your problem. There's nothing stopping any module from handling any request.

Well I have something like this:

$request = new HTTPRequest();
$request->path = /* ... */;

$return = false;
foreach($modules as $key => $value)
{
    if($value == "active")
    {
        $module = $system->load_module($key);
        $return = $module->handle_http_request($request);
        if($return === true) break;   /* This line is the culprit I guess */
    }
}

But you already answered that question:

Quote:

A module could do something transparent within the handleRequest (e.g., log to a database), but still return FALSE as to say that it didn't handle the request. Just make sure the Statistics module is loaded first. I treat the INI file as first come, first serve. That's been good enough for me.

But that seems so, hackish, interfering with the natural flow of the PHP code is a dangerous business indeed. :P

I could implement something like an always_load option or something similar, but your solution sounds so much simpler! ;D

Matthew Leverton

There are various ways to solve it. In this particular case, I actually just rolled logging into the main application class. I build my CMS (actually it's a framework with a CMS module) on the fly. So while it may be useful to loop through the modules twice, by first calling a pre-processing function, I've just not had the need to yet.

Vanneto

That preprocessor function idea is great! There could be a handle_preprocessing() function! No double looping! ;D This is just great!

Well, this is basically everything I need for now. Just one tiny question. Its about style, its not particularly relevant, but I would like to know anyway.

What is better, that every class is extended from System and that System has all the functions that the module could use:

class Foo // Is it possible to extend from 2 different classes?
{
    public function Bar()
    {
        // If that is not possible, this could be a solution:
        global $system;

        $result = $system->db->query("SELECT something FROM everything");
        /* ... Moar code ... */
    }
}

This seems like its quaint. But yeah, I am infected from the "perfect design" virus. If its not perfect, its not good! Damn, I need to get over this! :P

Matthew Leverton

You can only extend one class.

I do this:

class Module_Foo extends Module
{
  public function bar()
  {
    $app = Application::getInstance();

    // in reality, I rarely make direct calls like this. 
    // usually an object (e.g., DBO_New) is used.
    $st = $app->db->prepare('...');
    $results = $st->getAll();
  }
}

I wanted to avoid using globals, although there's really nothing wrong with it considering PHP's syntax can otherwise get cumbersome (thanks to its retarded scope rules). Or you could create a $this->app variable in the Module constructor.

Vanneto

Sorry for being stupid, but how is DBO_New used?

I'm suspecting:

/* ... class ... */
    $db = DBO_New();
/* ... */

?

Matthew Leverton

It's supposed to be "DBO_News." DBO stands for "database object," but it's sort of a misnomer in that it doesn't necessarily have to be stored in the database. There's nothing exposed in the interface that requires it.

Basically a DBO is any conceptual object. It may or may not correspond one to one with a database table.

$news = DBO_News::find($foo); // the constructor is private
if ($news)
{
  $news->title = "A new title";
  // or $news['title'] = "A new title";
  $news->update();
}

It gives me runtime flexibility to only query the data I need. For instance:

$recent_news = DBO_News::findRecent();
foreach ($recent_news as $news)
{
  echo $news->reporter->name . "<br />";
}

The "reporter" isn't actually loaded by the initial query. It's used on demand.

Yes, this generates more SQL than is needed, but I don't care. The reason is that I cache HTML and data objects in memory if needed for performance.

In the above example, the findRecent() will generate a query that returns an array of news items without any linked information. Then for every unique reporter_id, it will load on demand the data for that person. That reporter data is then stored in memory so that the next time the page loads, only one query is run (findRecent()).

This is accomplished by:

1class DBO_News extends DBO
2{
3 public function __get($key)
4 {
5 switch ($key)
6 {
7 case 'reporter':
8 if (!$this->_reporter)
9 $this->_reporter = DBO_User::find($this->data['reporter_id']);
10 
11 return $this->_reporter;
12 break;
13
14 default: return parent::__get($key);
15 }
16 }
17}

The generic find() automatically will pull from memory if it can, thus eliminating simple one-off queries.

Again, this may seem like overkill if you know exactly how you will be using your data. But I chose this design with my templates in mind. I don't know when they will want to use extended information, nor do I want to force the template designers to use some SQL like syntax to inform PHP what to grab.

In this template, no extended data is used or queried:

<ul>
  <li mef:loop="n; news">
    {n.title}
  </li>
</ul>

But it's possible it might look like:

<ul>
  <li mef:loop="n; news">
    {n.title}, reported by {n.reporter.name}
  </li>
</ul>

Or even...

<ul>
  <li mef:loop="n; news">
    {n.title}, reported by {n.reporter.name} who has a game called {n.reporter.projects[0].title}
  </li>
</ul>

Vanneto

OK, but does the Module implement DBO News or is it a system object? I am thinking of letting each module have its own folder and then can have all classes it wants in there. That way the module-ness is not lost.

Oh, by the way, is it even useful to have these:

// include.php
if(!defined("IN_SITE")) die;

// index.php
define("IN_SITE", true);
include "include.php";

I'm getting bad headaches because of these plus __autoload! :S

Matthew Leverton

The DBO objects live outside of the modules. They can be used with or without the rest of the framework. Module_News "knows" that DBO_News exists, but the reverse isn't true. The other modules probably never need to use DBO_News, but they could if need be.

I don't know what you mean by your include question. (I don't even use includes.)

Vanneto

Its used so someone doesn't access your classes by doing:

http://www.mysite.com/include/myclass.class.php

And the defines are there to kill the script if its accessed directly. But I know what your gonna say, you have all the include files out of the web directory! ;D

CGamesPlay
Quote:

Oh, by the way, is it even useful to have these:

No. Your include file doesn't execute any code, it just defines functions and classes. If a user accessed it, it would be a blank page.

Matthew Leverton
Quote:

And the defines are there to kill the script if its accessed directly. But I know what your gonna say, you have all the include files out of the web directory! ;D

Yes, I do.

class/                      All of my classes, including modules, DBOs, etc
template/                   System-wide (default / generic) templates 
site/dispatcher.php         The driver for everything
site/domain.com/template    Website templates
site/domain.com/web         Static Web Files (this is the doc root)

That's a subset of the directories I use. Each website has a mod_rewrite that directs everything to /.dispatcher.php which is just an Alias to the global dispatcher.php file.

Anything in the "web" directory can be accessed directly by the web browser, but in general it's just static images and downloads. Occasionally I may slip a PHP file in there, but only has a hack because I'm not interested (at the time) in creating a proper module for the functionality.

Thomas Fjellstrom

I'm interested in how thats actually setup, do you place it all in /var/www and have just one virtual host setup in apache? or?

Matthew Leverton

It's in /var/www/cms (I have other regular sites in /var/www/domain.com) with each cms-driven domain having its own vhost pointing to /var/www/cms/site/domain.com/web.

Thomas Fjellstrom

Ah, I figured as much, and the "links" are actual symlinks to the main controller?

Matthew Leverton

Each sites' vhost has an Alias from /.dispatcher.php (which doesn't exist) to /var/www/cms/site/dispatcher.php. That's the only kind of link between the two. That main dispatcher handles everything via the classes that sit in the cms/class directory.

Vanneto

Haha I'm so lucky this thread isn't dead yet! ;D Time is of essence.

So, I still don't get it. What if there is no parameter in Request->path? What does a module show then? Index.tpl? This is now bugging me for some time and I just can't seem to get it yet... My brain is hardwired with page-based thinking! :P

Matthew Leverton

Use whatever method that makes you happy inside. For me a path like /foo/bar/blah?z=y would look like (in JSON):

{
  path: ['foo', 'bar', 'blah'],
  params: {z: 'y'}
}

A path like / just becomes:

{
  path: []
  params: {}
}

I don't treat it as a special case. If nothing implements the empty request, then a 404 would be triggered just as if someone went to '/asdfsadfasdfasdf'.

So you could create a Module_EmptyRequest if you wanted. It could just load index.tpl.

I always load Module_AutoTemplate as the last, catch-all module. It just looks for any matching template. So /foo/bar would match in order: /foo/bar/index.tpl, /foo/bar.tpl, /foo/index.tpl, /foo.tpl, /index.tpl.

The null case just naturally searches for /index.tpl.

CGamesPlay

Matthew: what is your opinion on the CakePHP-style request handling like at the bottom of this post?

Matthew Leverton

Are you referring to requiring that function names match the URL? I don't like it. But there's nothing preventing taking my generic request handler function and farming out requests to individual methods.

Vanneto

Still not sure what to do... I could let the News module handle the empty request and just display the page as the index. I could implement an Empty request module and when an empty request happens I fetch the news and everything thats needed on the front page and display it.

Anyway, while this thread is still open, could anyone help me with a regex? I need a regex for something like this:

<lang string="SOMETHING" args=($foo, $bar)/>

This would be translated into:

sprintf($language[SOMETHING], $foo, $bar);

I'm currently trying with a regex but it wont work no matter what I do. My regex skills are broken.

Thread #595252. Printed from Allegro.cc