
Show HN: Ham, a fast PHP router/microframework I hacked up last night - antihero
https://github.com/radiosilence/Ham
======
troels
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.

~~~
orlandu63
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).

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

~~~
k4st
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.

------
hendrik-xdest
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.

~~~
antihero
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 :)

~~~
hendrik-xdest
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.

~~~
Gigablah
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

------
cilice
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.

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

~~~
antihero
Good for you :)

~~~
mise
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.

~~~
antihero
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 :)

------
dools
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

~~~
websirnik
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>

~~~
kaolinite
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.

------
jwblackwell
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.

------
whalesalad
I vote for a rename to phlask =)

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

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

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

Edit: Done - BSD 2-clause.

------
ittan
Seems much easier to use than Fat free <http://fatfree.sourceforge.net/>

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

~~~
moozeek
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.

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

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

~~~
antihero
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.

------
soulclap
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.)

------
antihero
Critique totally welcome!

------
lonnyk
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!

------
conradfr
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).

~~~
antihero
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

~~~
Gigablah
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.

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

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

------
yaix
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.

~~~
antihero
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.

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

<https://github.com/bastos/nicedog>

------
johnx123-up

      Routes are converted to regex and cached so this process 
      does not need to happen every request.
    

But, regex will again degrade performance.

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

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

------
nerdfiles
Following the feature set of <http://www.slimframework.com/> at all?

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

------
wseymour
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.

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

------
Jebus
You basically ripped off Silex

<http://silex.sensiolabs.org/>

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

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

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

~~~
gaius
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!

~~~
jasonlotito
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.

