Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: Ham, a fast PHP router/microframework I hacked up last night (github.com/radiosilence)
87 points by antihero on March 20, 2012 | hide | past | favorite | 63 comments


Refreshing with these micro frameworks as a counter movement to the monoliths. In my opinion PHP is so fragmented library wise, that trying to streamline everything is a failure - Much better to have a collection of independent modules that are stitched loosely together. It's of course very much a matter of taste.

On the technical side, I have come to prefer using straight regular expressions for routes, rather than a dsl or made-up routing syntax a la rails. It's a relatively unknown feature of preg, but you can have named captures, that pretty much gives you all that you need. For example, here's a couple of routes from a recent application of mine:

    $GLOBALS['ROUTES']['~^GET/devices/(?P<device_id>\d+)~'] = "devices/show";
    $GLOBALS['ROUTES']['~^GET/devices~'] = 'devices/index';
My front controller is then just a loop over the routes, that will resolve first match.

For example, a GET request to `/devices/1234` will resolve to the file `handlers/devices/show.php`, where `device_id` is available as a parameter.

Yes, the syntax is a bit awkward, but it's computationally efficient without a cache/compilation step and it's a standard language, rather than a proprietary one.


I wouldn't call that computationally efficient. It is possible to make routing an O(log n) operation; your routing solution, like all routers that use regexes directly, is O(n).


Technically, yes. What I was referring to was more mundane though. The difference between writing a parser/interpreter partially in PHP or minimizing the php code to a loop and then push all the hard stuff down to preg is significant. Optimizing it further is probably futile, compared to what else goes on in a typical http req-res cycle.

It's a red herring anyway, so I sort of regret bringing it up. Much more important is the other point - that this is a standard tool, rather than a tailored one.


I'm curious; what data structure and algorithm are you using to achieve O(log n) routing?


If n is the number of routes, and they are sorted (e.g. implicitly through a prefix tree) and evenly distributed accordingly then then the claim might be plausible by doing log n prefix comparisons of increasingly smaller suffixes of the route to be resolved. I find this line of reasoning unusual and misleading though.

The lower bound is linear in the length of the route if all routes participate in your regular expression and if you have it pre-compiled to, for example, a DFA or an LL parser ahead of time.


Great work. Hope to see this evolve into a great little toolset.

I have gone through your code a bit and think that you should consider error handling first. Some methods return strings on error but arrays on success but this is not validated before using the value as an array (this results in the error reported before). I guess when no caching system is available the Dummy will break your code as well as it only returns false.

Also, I think using "False" instead of "false" results in two internal checks when PHP tries to determine if something is false or true, instead of one. Not sure if that is still the case with the latest PHP version, though. I reckon using "false" would be better.


Thanks, and I agree. I usually use exceptions but for some things (like cache misses) it seems unwieldy. For the example of the cache calls, I check when I've done a cache call like:

$data = $cache->get("key");

if(!$data) { // do the stuff if cache misses }

Though yes, I do need to go through and make sure it's extremely robust, as I did hack it up in an evening :)


Y U NO USE your cache like that:

  $data = $cache->get('key',function() {
    // do the stuff if cache misses
    return $some_value_to_be_cached;
  });
? It's quite handy and it forces you to separate actual logic to get the value, which is good by itself.


That's a cool idea, but I wouldn't want to lose the scope as you would with a closure. I will look into seeing if it's possible, though.


Yep, but i you can call "loosing the scope" as "specifying data dependencies explicitly" :) It all depends on a use case, though.


I was thinking about the part where you check the cache with if (!$found)

On that note, I adapted a rule of an open source project (can't quite remember which) that said to never use !$var to validate your data. One should always use isset($found['args']) or empty($found['args']) or for example is_array($found) && count($found) > 0 to check.


I've been burnt by this in the past. Nowadays I use ($found === false) to check for a cache miss (though this means you can't store boolean values verbatim).

This is why:

!array() -> true

!null -> true

!false -> true

!0 -> true

!"0" -> true (!!?)

!"" -> true


I suppose I could use $var === False, to prevent it pick up empty results.


Your microframework also reminds me of Silex a lot. http://silex.sensiolabs.org/

It also uses the same $app content, and but has - at the moment - a bit more evaluating of values.


I'd prefer Slim Framework ( http://www.slimframework.com/ ) combined with RedBean ORM and H2O Template.


In that case, you might like Concordia (https://github.com/schuttelaar/Concordia). From the same author as RedBeanPHP.


Good for you :)


I'll stick with Slim for now because, simply, it's established and Josh there has made it into a quite solid platform.

But keep at it, well done.


Yep, that's cool, I mainly made it for personal use so I'm not too bothered if others don't use it!

I need to start writing tests and docs really, but as I said, it was something I hacked up in an evening :)


I remember seeing an article or talk or something by one of the main Symfony guys on the fact that PHP was wanting, as a community, to move away from monolithic frameworks in favour of smaller, front controller style implementations. I was actually tring to find it the other day - does anyone have a link?

It's interesting to see the growing trend in this direction for PHP.

I wrote one a little while back[1] that does even less than this :) The only goal being to provide a simple way to execute classes from the web or command line with a default autoloading implementation and a way to inject new ones[2]

Personally a lot of my current direction in PHP comes from watching what's happening the node.js community - I like the way that there are a lot of small-ish packages/modules that are pretty independent.

I've written a couple of packages for RocketSled already which you can see on my github page if you're interested.

[1] https://github.com/iaindooley/RocketSled

[2] There are some obvious improvements that could be made to the current implementation which always parses the package tree ie. caching


I don't know about the article. But the truth is that Symfony2 is built from a collection of independent components. And each component could be used independently in your app without a need to use symfony framework. Here is for example a link for a routing component: https://github.com/symfony/Routing


The Zend Framework is the same. You can use each component completely separately. I know a few people who have used the Zend Controller classes and Zend Router in their own frameworks.


Hey that's pretty interesting. I can use that instead of writing my own routing package ;)


It's an encouraging trend. One of the things I like from the Java ecosystem is that there are a couple of really nice general-purpose frameworks that can be mixed and matched with other, more specific frameworks. Like Spring + Hibernate + JSF. I never liked the idea of a big, monolithic framework pretty much forcing its way of working on me. I'm very happy to see the PHP community moving in this direction!


I like the idea of that for command line, its a wicked project. Seems a bit painful for web use though - I moved away from mapping routes to controllers automatically in favour of specifying them manually, I feel that it gives great control and leads to less magic. Not knocking your project, though! Good stuff


Actually it doesn't even do that! It does so little - that r= argument just has to be a class that is autoloadable and implements the Runnable interface.

In fact, if you want routes you'd have to build it as a package and in FACT you could just create a class like this:

    class RocketHam implements rocketsled\Runnable
 {
    public function run()
    {
        $app = new Ham();
        $app->config_from_file('settings.php');

        $app->route('/pork', function($app) {
            return "Delicious pork.";
        });

        $hello = function($app, $name='world') {
            return $app->render('hello.html', array(
                'name' => $name
            ));
        };
        $app->route('/hello/<string>', $hello);
        $app->route('/', $hello);

        $app->run();
    }
 }
then put your ham/ directory in packages with a file rs.config.php:

    spl_autoload_register(function($class)
 {
     if($class == 'Ham')
         require_once(dirname(__FILE__).'/ham.php');
 }
then use .htaccess or something similar to route all requests to:

index.php?r=RocketHam

EDITS: oops sorry not used to formatting code on here


Gotcha, that's kinda cool. I just learned about this http://www.php.net/manual/en/language.oop5.magic.php#object....

The __invoke method means I can use my apps AS closures which is totally sweet.


Looks nice, keep it simple though. There are already a lot of feature full frameworks about that to be honest you can't compete with. I've never used a really lightweight framework like this but I would definitely consider it if I had a project that was the right size.


I vote for a rename to phlask =)


Heheh, the project was born out of enjoying flask development so much, then coming back to PHP and being like...whaaaat.


Can you tell us about the License for use? I do not see the license info.


Ah, I didn't really think of that initially. I'll pop one in there!

Edit: Done - BSD 2-clause.


Seems much easier to use than Fat free http://fatfree.sourceforge.net/

You should evolve this into a solid/flexible micro framework.


I wonder what would be hard about FatFree? To me it does not get much simpler than that. And it has a router, templating engine, auto loader (even with name space support, if you wish) and several other plugins.

It helps when you need it and lets you do things your way at the same time.

The only caveat is there's basically only one guy developing it.


Ham looks good. However, Fatfree has lot more than Ham - templating, framework variables.

It will be exciting to see how Ham comes up. Will try to use it in some hobby project.


I hate to be That Guy, but how does this compare to klein.php [1]?

[1] https://github.com/chriso/klein.php


Two things I do differently:

The most important one is that everything is done within an $app context. This means that theoretically you could build two apps, and I later will be creating functionality to "mount" apps on paths, like in Flask Blueprints.

Second one is that I am going to be extending it (but keeping it lean) with other microframework-y goodness, patterns, ways to use PHP's native stuff in elegant ways, etc.

Essentially, klein is a router, but Ham will be a microframework, providing patterns for application development with it, extensions to work with other libraries, etc, ala Flask.

I'm definitely looking at Klein and seeing what I can learn from it, what can be done better, etc.


Will definitely check this out, although I just started a project using Slim.

Slightly off-topic but besides Doctrine, RedBean and PHP ActiveRecord, is there any good and well-tested 'ORM' or some DataMapper/ActiveRecord implementation for PHP?

(I actually like PHP ActiveRecord but had some odd problems with it a while ago and well, there's 60+ issues on GitHub and no proper release since 2010.)


Critique totally welcome!


Am I reading this correctly that currently the route results will stay cached for the caches TTL? So if you change a route to have a different callback in your main program it will not update?

Really cool framework and congratulations on hacking this up in a night!


I tried the example but ran with some issues. Maybe you could specify the requirements ?

1/ It needs PHP >= 5.3 I guess (anonymous functions ?).

2/ It needs APC (use of apc_exists ?)

3/ I stopped at "Cannot use string offset as an array in ham\ham.php on line 42" (seems cache related).


Ah there were a couple of bugs. It now doesn't require a caching module to be installed, but is slower with out it. XCache is preferred as it allows closure caching.

On my laptop:

Xcache: ~700 microseconds APC: ~1,300 microseconds Dummy (no caching): ~3,712 microseconds


Yeah, I tried to drop in a Redis caching class and immediately ran into that problem. I noticed someone had published a method for serializing PHP closures using magic methods and reflection, but that's way too complex a workaround.


Looking very good! Definitely a nice start. Made me think of this MVC project: http://www.phpro.org/tutorials/Model-View-Controller-MVC.htm...


I'm sure I built my 2nd or 3rd learning framework based on that tutorial some years back :)


Looks good. I'd use named regex matches for the routes (like in Django) to make routes more flexible, ie. !/path/(?P<id>[0-9]{1,5}_(?P<kw>[a-z0-9_]{5,20})! etc.


Aye, I'm thinking of adding that functionality so you could do <re:blah> in the route, however for now I'm keeping it as simple as possible.


Looks similar to NiceDog (which is nice, but hasn't been updated for a while.).

https://github.com/bastos/nicedog


  Routes are converted to regex and cached so this process 
  does not need to happen every request.
But, regex will again degrade performance.


For Photon, a micro framework for Mongrel2, we use regex. Even with 100's or 1000's of routes, you cannot even see the cost of preg_match when benchmarking because the regex are organized in a tree following the URI structure like Django. It means that most of the time, a view is found within 2 to 3 preg_match calls.

This - regex is slow to match anchored strings on a 120 character long URI - is a myth.


For matching? Because PCRE/PHP_PCRE already caches the compiled versions of regexes, so there's little to no perf hit there.


Aren't the regexes cached only for the current request anyway?


For most requests, there is no regex matching done, because it also caches the resolved route for a given URI :)


Following the feature set of http://www.slimframework.com/ at all?


Heh, wow - the syntax is really similar. I've never seen that before, I'll be sure to look at their code.


What, we're talking about PHP micro-frameworks and nobody's mentioned Lithium?

www.lithify.me

Your search for a modular and lightweight framework has come to an end.


Would love to see a proper release of Lithium out, along with up-to-date docs.


You basically ripped off Silex

http://silex.sensiolabs.org/


Router? As in, TCP/IP? In PHP?


"Router" as in URL routing. Here's an example from his page:

  $app->route('/pork', function($app) {
    return "Delicious pork.";
  });


OK, but why would you do that, instead of just having pork.cgi (or pork/index.cgi)? That "routing" is built into Apache or indeed any HTTP server!


Mostly because mod_rewrite rules are fairly complicated, and the ability to put them in the server's conf file means any changes require a server restart. You could put them in .htaccess, but now your having to parse out the route every time. Using a front controller (like index.php) makes everything so much easier. Finally, you have to consider hosting support. This hasn't even touched on numerous other benefits.

Yes, their are tradeoffs, but they don't overcome the benefits, not the least of which is simplicity.


It's been long time since web developers moved away from URLs representing filesystem structure on web server.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: