SitePoint Sponsor

User Tag List

Results 1 to 19 of 19
  1. #1
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)

    [PHP] PHPRouter - Simple routing when you dont need an MVC

    I'll go ahead and be the first to kick this sub forum off (yay!). I just published this project today. It's a reiteration of something I already have in production for high performance JSON RESTful APIs. I will be supplying some more documentation as well as more examples later tonight or tomorrow.

    Project goals:
    • Avoid conventional "MVC" structure so commonly used in PHP (I mimicked what it would be like to create a project in Golang less the http package)
    • Be high performing (higher than most frameworks)
    • Allow for code to be separate from the framework so that updates to the package can easily be done (most frameworks has you place your source code inside of a predefined folder, making a git pull near impossible without replacing your code)
    • Less is more. Simple code is a must


    https://github.com/KyleWolfe/PHPRouter

    Except for comments such as "I think you should implement MVC", what do you think of the structure?

  2. #2
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,278
    Mentioned
    18 Post(s)
    Tagged
    0 Thread(s)
    The Good and General Comments:

    * The avoid "MVC" is certainly a good goal when you're building just the router. Any router is better if it doesn't mandate how the larger application must work. A good router works just as well with MVC and non-MVC alike.

    * High performance is always a good goal. You could add some benchmarking tests to your repo. It's useful to you in order to know what changes have a significant performance impact and which don't.

    * Allowing code to be separate from the framework is definitely a useful feature. These days, the modern frameworks rely on composer for this.

    Important Issues:

    * I recommend not trimming slashes. What if I want to send different responses depending on whether a trailing slash is present? Such as, for example, maybe I would want to send a redirect response from the trailing slash version to the non-trailing slash version, or vice versa.

    * Also, consider letting us use regular expressions instead of plain strings for the routes. Otherwise, no part of the URL can ever by dynamic. No IDs, no slugs, no categories, etc. Not being able to use those kinds of URLs would be severly limiting.

    Medium Issues:

    * Your router pulls directly from $_SERVER['REQUEST_URI']. I think it would be better if you accept the URI as a constructor argument. The larger application can still pass in $_SERVER['REQUEST_URI'] if it chooses to, but it would also have the option to pass in an arbitrary URI, which is useful for testing and other automated tasks.

    * Right now the callbacks have to be an instance of a certain class (RouteHandler). Consider allowing the router to accept anything callable. These could even be, for example, anonymous functions.

    Or, to take it a step further, don't invoke or even require any callbacks at all. Different frameworks have different ways that they identify or invoke the "controller" code. Some frameworks use anonymous functions; some use object-method pairs; some use string names. Let the user of the router decide how and what is important. All the router needs to do is return the set of parameters that are associated with whichever route was matched.

    Small Suggestions:

    * We might want to match against other properties of the request, such as the method (GET vs POST) or the host (example.com vs subdomain.example.com), among possibly others.

    * It's considered good practice to have one class per file. Normally I would consider this at least a medium issue, but since you have just a few classes and they all get used, I think you manage to get away with it.

    * Consider using a widely accept coding style, such as PSR-1/2.

    Super-small Suggestions:

    * We know this is PHP. The PHP in PHPRouter seems redundant. You could call it "KyleWolfeRouter". And in the code, just refer to it as "Router", since it's already namespaced by "KyleWolfe".
    "First make it work. Then make it better."

  3. #3
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by Jeff Mott View Post
    * High performance is always a good goal. You could add some benchmarking tests to your repo. It's useful to you in order to know what changes have a significant performance impact and which don't.
    This is definitely in my plans. Along with some unit tests.

    Quote Originally Posted by Jeff Mott View Post
    * Allowing code to be separate from the framework is definitely a useful feature. These days, the modern frameworks rely on composer for this.
    I haven't hopped on the composer bandwagon yet, but I do plan on incorporating it / publishing to packigist since it is a very popular tool.

    Quote Originally Posted by Jeff Mott View Post
    * I recommend not trimming slashes. What if I want to send different responses depending on whether a trailing slash is present? Such as, for example, maybe I would want to send a redirect response from the trailing slash version to the non-trailing slash version, or vice versa.
    This did cross my mind, however I viewed the gain from not having to deal with trailing slashes before hand a plus. Perhaps I should incorporate a switch to allow for turning off slash trimming (on by default)

    Quote Originally Posted by Jeff Mott View Post
    * Also, consider letting us use regular expressions instead of plain strings for the routes. Otherwise, no part of the URL can ever by dynamic. No IDs, no slugs, no categories, etc. Not being able to use those kinds of URLs would be severly limiting.
    Can you provide an example of what you mean? I do have this set up so that you either define /foo as a router or foo/bar as a second one, as far as "arguments" being passed after that, they can be picked up by parsing the URI within the handler class (though this does bring me to another feature I need to add, which is the ability to easily access the parsed URI)
    Quote Originally Posted by Jeff Mott View Post
    * Your router pulls directly from $_SERVER['REQUEST_URI']. I think it would be better if you accept the URI as a constructor argument. The larger application can still pass in $_SERVER['REQUEST_URI'] if it chooses to, but it would also have the option to pass in an arbitrary URI, which is useful for testing and other automated tasks.
    Well received, but again, I think it should behave as it currently does by default, injecting URI would be optional.

    Quote Originally Posted by Jeff Mott View Post
    * Right now the callbacks have to be an instance of a certain class (RouteHandler). Consider allowing the router to accept anything callable. These could even be, for example, anonymous functions.
    I may look back in to this, however I was running into some issues with straight anonymous classes while using namespaces. I would like to allow this, if I find what was causing my bug but the abstract class of RouteHandler is currently acting as an anonymous function with benefit of object oriented class.


    Thank you very much for your input!

    PS - I don't like the names I come up with either, despite how "clever" they are

  4. #4
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    I'll take more suggestions for a project name as well

  5. #5
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    5,162
    Mentioned
    152 Post(s)
    Tagged
    0 Thread(s)
    Okay, first off I agree with every comment that Jeff wrote in his Good, Important, and Medium sections.

    I'll add one thing, the abstract class, seems useless to me and should be an Interface instead, as that is what it is posing as. It really isn't providing any of the benefits an abstract class can/should provide. Instead it is creating a contract to follow, and that by definition is an interface. Unfortunately, switching to an Interface will break the idea of anonymous functions (I think), but I don't really care, I'd rather of the OO approach.

    I'm not a huge fan of trimming the starting/trailing slashes either. I think those should be left and handled by the end-user.

    I also agree that if I want to define a route as "([a-z]+-[0-9]+)", I should be able to. Seems like it could be straight-forward to implement too.

    I also agree that $_SERVER['REQUEST_URI'] should be passed it, or utilized if NULL was sent in.

    I'm not sure I'd ever need to filter on host, but I can see how that'd be useful, and it could just be an optional argument to addRoute.

    I'm really not sure what guidelines you broke in regards to PSR-1 or PSR-2... (I'm also not 100% familiar with each one, as PHP is not my everyday language -- lame excuse, I know!)

  6. #6
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by cpradio View Post
    I'll add one thing, the abstract class, seems useless to me and should be an Interface instead, as that is what it is posing as. It really isn't providing any of the benefits an abstract class can/should provide. Instead it is creating a contract to follow, and that by definition is an interface. Unfortunately, switching to an Interface will break the idea of anonymous functions (I think), but I don't really care, I'd rather of the OO approach.
    I think I had plans of adding in some predefined body to a function or two that is currently not present, otherwise I do agree with you here. A public function for retrieving URI details would be a perfect example (if that would go there, not sure yet)

    Quote Originally Posted by cpradio View Post
    I'm not sure I'd ever need to filter on host, but I can see how that'd be useful, and it could just be an optional argument to addRoute.
    Filter by host? This could be handled by a globalHandler() couldn't it?

  7. #7
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    5,162
    Mentioned
    152 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by K. Wolfe View Post
    Filter by host? This could be handled by a globalHandler() couldn't it?
    I'm not sure it would...

    Let's say I have example1.com and example2.com. Both point to the same folder for serving my site. How would you see me allowing the routes for example1 to be different than those for example2?

    example1.com/foo would echo "foo"
    example2.com/foo would echo "bar"

    Without attaching the domain to the route, I couldn't use two different routes, I'd have to check which domain was hit within my route handler...

  8. #8
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by cpradio View Post
    I'm not sure it would...

    Let's say I have example1.com and example2.com. Both point to the same folder for serving my site. How would you see me allowing the routes for example1 to be different than those for example2?

    example1.com/foo would echo "foo"
    example2.com/foo would echo "bar"

    Without attaching the domain to the route, I couldn't use two different routes, I'd have to check which domain was hit within my route handler...
    Why not define which Handler to send before defining it as the handler? Sounds like this should be handled by the virtual hosts on apache anyway unless we are talking about just one handler rather than them all. Either way I think its up to the developer to determine which handler to pass, not the routers since you've already told it what to do.

  9. #9
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    5,162
    Mentioned
    152 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by K. Wolfe View Post
    Why not define which Handler to send before defining it as the handler? Sounds like this should be handled by the virtual hosts on apache anyway unless we are talking about just one handler rather than them all. Either way I think its up to the developer to determine which handler to pass, not the routers since you've already told it what to do.
    I'm just thinking this is cleaner:
    PHP Code:
    $router->addRoute('/foo'myFooHandler2'example2.com');
    $router->addRoute('/foo'myFooHandler1'example1.com'); 
    Versus:
    PHP Code:
    if ($domain === 'example2.com')
        
    $router->addRoute('/foo'myFooHandler2);
    else if (
    $domain === 'example2.com')
        
    $router->addRoute('/foo'myFooHandler1); 
    That's all. The Business Logic would get ugly really fast if you had to manage different routes for different domains.

    Business Use:
    We have 3 companies tied to the same source code at our workplace (all three domains go to the same server, hit the same virtual directories, etc). Each company has its own rules for how the product(s) we offer should behave and what is permissible. Sometimes that means some pages are off limits, other times, they need to serve up radically different content. We have a similar routing process that allows us to pass in the domain/company so we can easily have the route only apply to that one company/domain.

    Just a suggestion. Wouldn't be that hard to customize it to do that, if I were to use it, but I definitely see benefits in having it.

  10. #10
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    5,162
    Mentioned
    152 Post(s)
    Tagged
    0 Thread(s)
    Name Suggestions:
    TiniRoute
    Rou.te (short-link style implies it is "small"?) -- I personally like this one, bet that'd be a fun class/namespace
    DamnSmallRouter (play on Damn Small Linux)

  11. #11
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by cpradio View Post
    I
    Business Use:
    We have 3 companies tied to the same source code at our workplace (all three domains go to the same server, hit the same virtual directories, etc). Each company has its own rules for how the product(s) we offer should behave and what is permissible. Sometimes that means some pages are off limits, other times, they need to serve up radically different content. We have a similar routing process that allows us to pass in the domain/company so we can easily have the route only apply to that one company/domain.

    Just a suggestion. Wouldn't be that hard to customize it to do that, if I were to use it, but I definitely see benefits in having it.
    Wow, that sounds like a royal pain to deal with, especially if you ever wanted to decouple them.

  12. #12
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    5,162
    Mentioned
    152 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by K. Wolfe View Post
    Wow, that sounds like a royal pain to deal with, especially if you ever wanted to decouple them.
    No chance of decoupling them, the other two companies are companies we bought out, but we kept their brand name as they work in regions we don't and they continue to operate within their own regions. All software is developed in the headquarters though, so we get all business/projects requests for all companies. The general workflow/process is 80% the same for all companies, and the premise behind each company is the same, there are just those "differences" that you have to deal with due to the regions they operate in.

  13. #13
    Utopia, Inc. silver trophy
    ScallioXTX's Avatar
    Join Date
    Aug 2008
    Location
    The Netherlands
    Posts
    9,085
    Mentioned
    153 Post(s)
    Tagged
    2 Thread(s)
    line 12:
    PHP Code:
    $this->requestURIString rtrim(ltrim($_SERVER['REQUEST_URI'], '/'), '/'); 
    is the same as

    PHP Code:
    $this->requestURIString trim($_SERVER['REQUEST_URI'], '/'); 
    which is shorter, so I'd go with that.

    line 17:
    PHP Code:
    if(!($handler instanceof PHPRouterRouteHandler)) throw new PHPRouterException("Supplied handler must be an instance of RouteHandler"); 
    I would make the PHPRouterRouteHandler a type hint for the $handler parameter and let PHP handle people supplying invalid arguments.

    line 23:
    PHP Code:
    if(!($handler instanceof PHPRouterRouteHandler)) throw new PHPRouterException("Supplied handler must be an instance of RouteHandler"); 
    You already have type hinting on this function, so PHP will already stop when someone passes an object to that function that is not an instance of PHPRouterRouteHandler. You can (and should) therefore remove this line.

    line 42:
    PHP Code:
    if(!is_string($route)) throw new PHPRouterException("Route argument must be a string"); 
    PHP has a built in exception for this sort of thing InvalidArgumentException. I would use that one as it more specific and commonly known.

    line 43
    PHP Code:
    return rtrim(ltrim($route'/'), '/'); 
    see comment about line 12

    lines 51-61
    PHP Code:
    class PHPRouterException extends \Exception {
        public function 
    __construct($message$code 0Exception $previous null) {
            
    parent::__construct($message$code$previous);
        }
    }

    class 
    PHPRouterRequestException extends \Exception {
            public function 
    __construct($message$code 0Exception $previous null) {
                    
    parent::__construct($message$code$previous);
            }

    You're in a namespace (github\com\KyleWolfe\PHPRouter), and you haven't defined an Exception class yourself, so the __construct of those classes must accept \Exception instead of just Exception

    As a last general comment, I would add doc comments to the class so it's easier for people to see what's what.

    Other than that I do like the idea of the light weight router and I'm interested where this goes

    I don't know if you take feature requests, but a method of easily caching and reloading routes would be a great feature IMHO. We just found out at work that loading the routes takes up most of the processing time for any request. Since we're now caching the routes we can handle ~35 req/sec on our dev server instead of ~15.
    Rémon - Hosting Advisor

    SitePoint forums will switch to Discourse soon! Make sure you're ready for it!

    Minimal Bookmarks Tree
    My Google Chrome extension: browsing bookmarks made easy

  14. #14
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Pushed an update to the repo. Removed RouteHandler function to allow for use of either class or anonymous function as long as it is callable. Classes become callable using __invoke() magic method.

    Scallio, It was my preference to remove the first slash so that routes would be defined as "foo/bar" rather than "/foo/bar". I suppose I should look at other frameworks to see what they do, but the one I was modelling does use a slash first. As for caching, due to the nature of PHP, I think this is probably best served by a third party tool / optimizer. Loading from a flat file would probably be worse than procedurally creating these routes each time the script is invoked. Accessing shared memory in PHP is way too messy IMO. But if there is a clean way of creating these routes in shmem that would be beneficial and clean, I'm definitely up for trying it!

  15. #15
    Hosting Team Leader silver trophybronze trophy
    cpradio's Avatar
    Join Date
    Jun 2002
    Location
    Ohio
    Posts
    5,162
    Mentioned
    152 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by K. Wolfe View Post
    Scallio, It was my preference to remove the first slash so that routes would be defined as "foo/bar" rather than "/foo/bar".
    But you are removing the beginning and ending slashes... you have nested rtrim(ltrim(..., '/'), '/') which is the same as using trim(..., '/') ...

  16. #16
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by cpradio View Post
    But you are removing the beginning and ending slashes... you have nested rtrim(ltrim(..., '/'), '/') which is the same as using trim(..., '/') ...
    Oh, misread that. Thanks

  17. #17
    SitePoint Wizard silver trophybronze trophy asp_funda's Avatar
    Join Date
    Jun 2003
    Location
    ether
    Posts
    4,497
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Some suggestions on formatting:

    1) Each class in its own file, doesn't matter if the class is just 2-3 lines.

    2) Use an underscore prefix for private/protected vars and function names - makes it easier to identify them in code.

    3) Add a space each after start and before end of a bracket, so instead of:
    PHP Code:
    $this->requestURIString $this->formatRouteString($_SERVER['REQUEST_URI']); 
    you'd do:
    PHP Code:
    $this->requestURIString $this->formatRouteString$_SERVER['REQUEST_URI'] ); 
    4) Even on single line if blocks, use parenthesis, keeps the code uniform.

    5) I'm wondering why is your class under "github\com\" namespace? Do you work for Github? Or this code originate on Github (as in Github Inc)?
    Our lives teach us who we are.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Me - Photo Blog - Personal Blog - Dev Blog
    iG:Syntax Hiliter -- Colourize your code in WordPress!!

  18. #18
    Always A Novice bronze trophy
    K. Wolfe's Avatar
    Join Date
    Nov 2003
    Location
    Columbus, OH
    Posts
    2,182
    Mentioned
    66 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by asp_funda View Post
    5) I'm wondering why is your class under "github\com\" namespace? Do you work for Github? Or this code originate on Github (as in Github Inc)?
    I like the way that Go handles their imports. When you do a "go get" to download a third party package, you supply thw whole url, and it downloads it in that structure toyou "GOPATH". So go get github.com/KyleWolfe/PHPRouter would create the structure $GOPATH/github/KyleWolfe/PHPRouter and importing that package would be "import github/KyleWolfe/PHPRouter". Long story short I like the idea of having the entire url of where it came from in the namespace, lime Go does.

  19. #19
    SitePoint Wizard silver trophybronze trophy asp_funda's Avatar
    Join Date
    Jun 2003
    Location
    ether
    Posts
    4,497
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Interesting but its only for Go lang, doesn't benefit here (unless I'm missing something). If you're going to put your lib on Composer then it wont matter anyways since you create a manifest there listing out URL of source & any dependencies etc.
    Our lives teach us who we are.
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Me - Photo Blog - Personal Blog - Dev Blog
    iG:Syntax Hiliter -- Colourize your code in WordPress!!


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •