Coder Social home page Coder Social logo

klein.php's Introduction

Klein.php

Build Status

klein.php is a fast & flexible router for PHP 5.3+

Getting started

  1. PHP 5.3.x is required
  2. Install Klein using Composer (recommended) or manually
  3. Setup URL rewriting so that all requests are handled by index.php
  4. (Optional) Throw in some APC for good measure

Composer Installation

  1. Get Composer
  2. Require Klein with php composer.phar require klein/klein
  3. Add the following to your application's main PHP file: require 'vendor/autoload.php';

Example

Hello World - Obligatory hello world example

<?php
require_once __DIR__ . '/vendor/autoload.php';

$klein = new \Klein\Klein();

$klein->respond('GET', '/hello-world', function () {
    return 'Hello World!';
});

$klein->dispatch();

Example 1 - Respond to all requests

$klein->respond(function () {
    return 'All the things';
});

Example 2 - Named parameters

$klein->respond('/[:name]', function ($request) {
    return 'Hello ' . $request->name;
});

Example 3 - So RESTful

$klein->respond('GET', '/posts', $callback);
$klein->respond('POST', '/posts', $callback);
$klein->respond('PUT', '/posts/[i:id]', $callback);
$klein->respond('DELETE', '/posts/[i:id]', $callback);
$klein->respond('OPTIONS', null, $callback);

// To match multiple request methods:
$klein->respond(array('POST','GET'), $route, $callback);

// Or you might want to handle the requests in the same place
$klein->respond('/posts/[create|edit:action]?/[i:id]?', function ($request, $response) {
    switch ($request->action) {
        //
    }
});

Example 4 - Sending objects / files

$klein->respond(function ($request, $response, $service) {
    $service->xml = function ($object) {
        // Custom xml output function
    }
    $service->csv = function ($object) {
        // Custom csv output function
    }
});

$klein->respond('/report.[xml|csv|json:format]?', function ($request, $response, $service) {
    // Get the format or fallback to JSON as the default
    $send = $request->param('format', 'json');
    $response->$send($report);
});

$klein->respond('/report/latest', function ($request, $response, $service) {
    $response->file('/tmp/cached_report.zip');
});

Example 5 - All together

$klein->respond(function ($request, $response, $service, $app) use ($klein) {
    // Handle exceptions => flash the message and redirect to the referrer
    $klein->onError(function ($klein, $err_msg) {
        $klein->service()->flash($err_msg);
        $klein->service()->back();
    });

    // The fourth parameter can be used to share scope and global objects
    $app->db = new PDO(...);

    // $app also can store lazy services, e.g. if you don't want to
    // instantiate a database connection on every response
    $app->register('db', function() {
        return new PDO(...);
    });
});

$klein->respond('POST', '/users/[i:id]/edit', function ($request, $response, $service, $app) {
    // Quickly validate input parameters
    $service->validateParam('username', 'Please enter a valid username')->isLen(5, 64)->isChars('a-zA-Z0-9-');
    $service->validateParam('password')->notNull();

    $app->db->query(...); // etc.

    // Add view properties and helper methods
    $service->title = 'foo';
    $service->escape = function ($str) {
        return htmlentities($str); // Assign view helpers
    };

    $service->render('myview.phtml');
});

// myview.phtml:
<title><?php echo $this->escape($this->title) ?></title>

Route namespaces

$klein->with('/users', function () use ($klein) {

    $klein->respond('GET', '/?', function ($request, $response) {
        // Show all users
    });

    $klein->respond('GET', '/[:id]', function ($request, $response) {
        // Show a single user
    });

});

foreach(array('projects', 'posts') as $controller) {
    // Include all routes defined in a file under a given namespace
    $klein->with("/$controller", "controllers/$controller.php");
}

Included files are run in the scope of Klein ($klein) so all Klein methods/properties can be accessed with $this

Example file for: "controllers/projects.php"

// Routes to "/projects/?"
$this->respond('GET', '/?', function ($request, $response) {
    // Show all projects
});

Lazy services

Services can be stored lazily, meaning that they are only instantiated on first use.

<?php
$klein->respond(function ($request, $response, $service, $app) {
    $app->register('lazyDb', function() {
        $db = new stdClass();
        $db->name = 'foo';
        return $db;
    });
});

//Later

$klein->respond('GET', '/posts', function ($request, $response, $service, $app) {
    // $db is initialised on first request
    // all subsequent calls will use the same instance
    return $app->lazyDb->name;
});

Validators

To add a custom validator use addValidator($method, $callback)

$service->addValidator('hex', function ($str) {
    return preg_match('/^[0-9a-f]++$/i', $str);
});

You can validate parameters using is<$method>() or not<$method>(), e.g.

$service->validateParam('key')->isHex();

Or you can validate any string using the same flow..

$service->validate($username)->isLen(4,16);

Validation methods are chainable, and a custom exception message can be specified for if/when validation fails

$service->validateParam('key', 'The key was invalid')->isHex()->isLen(32);

Routing

[ match_type : param_name ]

Some examples

*                    // Match all request URIs
[i]                  // Match an integer
[i:id]               // Match an integer as 'id'
[a:action]           // Match alphanumeric characters as 'action'
[h:key]              // Match hexadecimal characters as 'key'
[:action]            // Match anything up to the next / or end of the URI as 'action'
[create|edit:action] // Match either 'create' or 'edit' as 'action'
[*]                  // Catch all (lazy)
[*:trailing]         // Catch all as 'trailing' (lazy)
[**:trailing]        // Catch all (possessive - will match the rest of the URI)
.[:format]?          // Match an optional parameter 'format' - a / or . before the block is also optional

Some more complicated examples

/posts/[*:title][i:id]     // Matches "/posts/this-is-a-title-123"
/output.[xml|json:format]? // Matches "/output", "output.xml", "output.json"
/[:controller]?/[:action]? // Matches the typical /controller/action format

Note - all routes that match the request URI are called - this allows you to incorporate complex conditional logic such as user authentication or view layouts. e.g. as a basic example, the following code will wrap other routes with a header and footer

$klein->respond('*', function ($request, $response, $service) { $service->render('header.phtml'); });
//other routes
$klein->respond('*', function ($request, $response, $service) { $service->render('footer.phtml'); });

Routes automatically match the entire request URI. If you need to match only a part of the request URI or use a custom regular expression, use the @ operator. If you need to negate a route, use the ! operator

// Match all requests that end with '.json' or '.csv'
$klein->respond('@\.(json|csv)$', ...

// Match all requests that _don't_ start with /admin
$klein->respond('!@^/admin/', ...

Views

You can send properties or helpers to the view by assigning them to the $service object, or by using the second arg of $service->render()

$service->escape = function ($str) {
    return htmlentities($str);
};

$service->render('myview.phtml', array('title' => 'My View'));

// Or just: $service->title = 'My View';

myview.phtml

<title><?php echo $this->escape($this->title) ?></title>

Views are compiled and run in the scope of $service so all service methods can be accessed with $this

$this->render('partial.html')           // Render partials
$this->sharedData()->get('myvar')       // Access stored service variables
echo $this->query(array('page' => 2))   // Modify the current query string

API

Below is a list of the public methods in the common classes you will most likely use. For a more formal source of class/method documentation, please see the PHPdoc generated documentation.

$request->
    id($hash = true)                    // Get a unique ID for the request
    paramsGet()                         // Return the GET parameter collection
    paramsPost()                        // Return the POST parameter collection
    paramsNamed()                       // Return the named parameter collection
    cookies()                           // Return the cookies collection
    server()                            // Return the server collection
    headers()                           // Return the headers collection
    files()                             // Return the files collection
    body()                              // Get the request body
    params()                            // Return all parameters
    params($mask = null)                // Return all parameters that match the mask array - extract() friendly
    param($key, $default = null)        // Get a request parameter (get, post, named)
    isSecure()                          // Was the request sent via HTTPS?
    ip()                                // Get the request IP
    userAgent()                         // Get the request user agent
    uri()                               // Get the request URI
    pathname()                          // Get the request pathname
    method()                            // Get the request method
    method($method)                     // Check if the request method is $method, i.e. method('post') => true
    query($key, $value = null)          // Get, add to, or modify the current query string
    <param>                             // Get / Set (if assigned a value) a request parameter

$response->
    protocolVersion($protocol_version = null)       // Get the protocol version, or set it to the passed value
    body($body = null)                              // Get the response body's content, or set it to the passed value
    status()                                        // Get the response's status object
    headers()                                       // Return the headers collection
    cookies()                                       // Return the cookies collection
    code($code = null)                              // Return the HTTP response code, or set it to the passed value
    prepend($content)                               // Prepend a string to the response body
    append($content)                                // Append a string to the response body
    isLocked()                                      // Check if the response is locked
    requireUnlocked()                               // Require that a response is unlocked
    lock()                                          // Lock the response from further modification
    unlock()                                        // Unlock the response
    sendHeaders($override = false)                  // Send the HTTP response headers
    sendCookies($override = false)                  // Send the HTTP response cookies
    sendBody()                                      // Send the response body's content
    send()                                          // Send the response and lock it
    isSent()                                        // Check if the response has been sent
    chunk($str = null)                              // Enable response chunking (see the wiki)
    header($key, $value = null)                     // Set a response header
    cookie($key, $value = null, $expiry = null)     // Set a cookie
    cookie($key, null)                              // Remove a cookie
    noCache()                                       // Tell the browser not to cache the response
    redirect($url, $code = 302)                     // Redirect to the specified URL
    dump($obj)                                      // Dump an object
    file($path, $filename = null)                   // Send a file
    json($object, $jsonp_prefix = null)             // Send an object as JSON or JSONP by providing padding prefix

$service->
    sharedData()                                    // Return the shared data collection
    startSession()                                  // Start a session and return its ID
    flash($msg, $type = 'info', $params = array()   // Set a flash message
    flashes($type = null)                           // Retrieve and clears all flashes of $type
    markdown($str, $args, ...)                      // Return a string formatted with markdown
    escape($str)                                    // Escape a string
    refresh()                                       // Redirect to the current URL
    back()                                          // Redirect to the referer
    query($key, $value = null)                      // Modify the current query string
    query($arr)
    layout($layout)                                 // Set the view layout
    yieldView()                                     // Call inside the layout to render the view content
    render($view, $data = array())                  // Render a view or partial (in the scope of $response)
    partial($view, $data = array())                 // Render a partial without a layout (in the scope of $response)
    addValidator($method, $callback)                // Add a custom validator method
    validate($string, $err = null)                  // Validate a string (with a custom error message)
    validateParam($param, $err = null)                  // Validate a param
    <callback>($arg1, ...)                          // Call a user-defined helper
    <property>                                      // Get a user-defined property

$app->
    <callback>($arg1, ...)                          //Call a user-defined helper

$validator->
    notNull()                           // The string must not be null
    isLen($length)                      // The string must be the exact length
    isLen($min, $max)                   // The string must be between $min and $max length (inclusive)
    isInt()                             // Check for a valid integer
    isFloat()                           // Check for a valid float/decimal
    isEmail()                           // Check for a valid email
    isUrl()                             // Check for a valid URL
    isIp()                              // Check for a valid IP
    isAlpha()                           // Check for a-z (case insensitive)
    isAlnum()                           // Check for alphanumeric characters
    contains($needle)                   // Check if the string contains $needle
    isChars($chars)                     // Validate against a character list
    isRegex($pattern, $modifiers = '')  // Validate against a regular expression
    notRegex($pattern, $modifiers ='')
    is<Validator>()                     // Validate against a custom validator
    not<Validator>()                    // The validator can't match
    <Validator>()                       // Alias for is<Validator>()

Unit Testing

Unit tests are a crucial part of developing a routing engine such as Klein. Added features or bug-fixes can have adverse effects that are hard to find without a lot of testing, hence the importance of unit testing.

This project uses PHPUnit as its unit testing framework.

The tests all live in /tests and each test extends an abstract class AbstractKleinTest

To test the project, simply run php composer.phar install --dev to download a common version of PHPUnit with composer and run the tests from the main directory with ./vendor/bin/phpunit

Contributing

See the contributing guide for more info

More information

See the wiki for more information

Contributors

License

(MIT License)

Copyright (c) 2010 Chris O'Hara [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

klein.php's People

Contributors

abackstrom avatar alexmingoia avatar anderssvendal avatar bafs avatar berklee avatar chriso avatar chuyskywalker avatar codezues avatar conormcd avatar eimajenthat avatar fliespl avatar gabrielbull avatar gbouthenot avatar harrisonpage avatar jeandenis-k avatar kbsali avatar l3pp4rd avatar limenet avatar mcos avatar mostlygeek avatar nickl- avatar rafekettler avatar rican7 avatar robi50 avatar servel333 avatar shrink avatar siebelstim avatar tfermm avatar tudborg avatar userlond avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

klein.php's Issues

Request params() with filter is broken

Description

Trying to get a set of parameters using params($filter) returns an array with the keys set to all null values.

Steps to Reproduce

Submit a page with GET or POST values. Attempt to extract just those values using params($filter).

Expected Result

An array with those name/value pairs.

Actually See

An array with expected names, but all null values.

Probable Cause

The Request->params() function uses array_merge() to merge the result of all($mask) on the get, post, cookies, and named params collections.

array_merge() works so that "the later value for that key will overwrite the previous one". But the DataCollection->all() method returns an array with all $mask keys set to null if it didn't exist in the current collection.

The result is that params_named->all() overwrites the get / set data with all nulls.

Possible Solution

Move the array_flip and associated code out of the DataCollection into Request->params() so it's only done once. I'm not sure if this affects other assumptions about the behavior of DataCollection->all() though.

Route matching will 404 if not matching the method, even if the endpoint exists

Problem

If a route endpoint is matched, but the method used to get to the endpoint is not matched by a specified route, the router defaults to throwing a 404 error, which is not exactly what's happening.

Potential Resolution

Instead, too be more RESTful, we should let the developer/client know that the endpoint exists through a different method by sending back a 405 "Method Not Allowed" error.

Example

That way, when defining routes, we could simply do this:

<?php

respond( 'POST', '/user/profile/?', function( $request, $response, $app ) {
});

Without having to then do this for each route:

<?php

respond( '/user/profile/?', function( $request, $response, $app ) {
    if ( $request->method() != 'POST') {
        // Respond with 405
    }
});

As you can imagine, that would get pretty dirt over time with numerous routes or multiple methods per route.

Rules merge parameters

Rule A respond('/loltag/[:_id]/[del:del]?' ...
Rule B respond('POST', '/loltag/image'...

Exemple :
I POST some data to /loltag/image with

[
'_id => '123'
]
set as my key / value.

Both rules ares matched, even if rule B is the final one (choosen by the router).

The problem is
_id paramater is set to "image", whereas I expect to get "123" from POST array.

It's sound like parameter matched from rule A are merged to rule B... is this the normal behaviour ?

addControllerMap helper function

I made a helper function to add controller#action style routes using klein.php . This is moving into controller territory so I'm not sure if this is a vital addition to klein.php but it may be useful for those who use controllerClass->actionMethod() style routings.

I'm actually already expanding the baseController with viewController methods to help with easily rendering views.

https://gist.github.com/3921631

Die after redirect

Is it intentional that the script won't immediately exit after a redirect()? In fact, it won't even break out of the callback unless I return right after. Seems a little counter-intuitive to me, but might be I'm missing something.

canceling a route match from within the response function

Is there a way to canceling a route match from within the response function? If you have a url param that you need to match against something like a database id for it to be valid, how do you tell the router that this match is not correct and have it keep looking at the remaining routes?

$router->respond('/articles/[:article_slug]/', function ($request, $response, $service){
    $article = getArticleFromSlug($request->article_slug);
    // if no article with matching slug is found
    if(!$article){
        // this route does not match, keep looking at other routes
    } else {
        // display the matching article
    }
});

/[*:cpath]/[:slug].php doesnt match

/[*:cpath]/[:slug].php doesnt match

/[*:cpath]/[a:slug].php does match

how come? isnt

/[*:cpath]/[:slug].php == /(.+?)/([^/]++).php

and

/[*:cpath]/[a:slug].php == /(.+?)/([0-9A-Za-z]++).php

trying to match /category1/categoryX/slug.php

$request->params($key, $default = null) for non empty

I was triying to get a optional request param, setting a default value, but the function is defined to check if isset, instead of !empty, so always return the empty param. Don't know if this affect to other use cases:

for the example:

$klein->respond("/anamedmodule/[:controller]?/[:action]/[**:params]?", function($request, $response, $service) {
      var_dump($request->param('controller', 'index')); 
});

using "/anamedmodule/anamedaction/" controller is set, so it never takes the default value.
The change is easy, at Request.php line 312.

    /**
     * Return a request parameter, or $default if it doesn't exist
     *
     * @param string $key       The name of the parameter to return
     * @param mixed $default    The default value of the parameter if it contains no value
     * @access public
     * @return string
     */
    public function param($key, $default = null)
    {
        // Get all of our request params
        $params = $this->params();

        return !empty($params[$key]) ? $params[$key] : $default;
    }

Allow params following '?' for queries in GET

So after spending about half an hour trying to figure why my regex wouldn't work, I stumbled upon, well, lines 88-90:

if (false !== strpos($uri, '?')) {
    $uri = strstr($uri, '?', true);
}

Why is that there? Is it cos of some dogmatic kind of belief that params have no place in REST? Would you consider taking it out?

Cheers

Notice: Undefined offset: 4

Line 161 in 206b6a
list($block, $pre, $type, $param, $optional) = $match;

causes PHP to display "Notice: Undefined offset: 4" when optional is not found in match

ignore script_name if part of URI

Why not ignore $_SERVER['SCRIPT_NAME'] if its part of the URI being dispatched?

so that /index.php/ and / are the same
incase your frontend controller is frontend.php so /frontend.php/ and / are the same also

Line 87:

        // if SCRIPT_NAME is part of the REQUEST_URI, remove it from $uri since we ignore it
        if ($_SERVER['SCRIPT_NAME'] == substr($_SERVER['REQUEST_URI'], 0, strlen($_SERVER['SCRIPT_NAME']))) {
            $uri = substr($_SERVER['REQUEST_URI'], strlen($_SERVER['SCRIPT_NAME']));
        }

Question about handling file upload.

Chris -

Huge fan of klein, I've been using it for a couple of projects.
I wanted to ask a question about best practices around handling file uploads.

One can just use the $_FILES variable to process the file, but I was curious about your thoughts about merging $_FILES into the klein $_REQUEST.

I'm not sure what other issues or vulnerabilities it might introduce, but changing line 105 to:

$_REQUEST = array_merge($_GET, $_POST, $_FILES);

Would give you access to a _FILE object in the request.

$request->barcode_image['name']
$request->barcode_image['type']
$request->barcode_image['tmp_name']
$request->barcode_image['error']
$request->barcode_image['size']

Was just curious about your thoughts in a best practice for handling that kind of thing.

Thanks a lot. And I'd love to help out in any ways I can.

"/?" is added to all regexes

An optional slash is appended to all regexes, which is not always desirable. For example:

respond('/foo/[i:id]/', function ($request, $response, $app, $matched) {
    $response->redirect('/bar/', 307);
});

In this example, "/foo/123/" gets correctly redirected to "/bar/", but "/foo/123//" (two trailing slashes) also gets redirected to "/bar/", which shouldn't happen.

The guilty code is https://github.com/chriso/klein.php/blob/f7774ef3441778eb2ed59c14738219289fe28f27/klein.php#L181

In my opinion, the user should manually append "/?" to the route when he wants to do so. For example, I might want to do a 301 redirect from "/foo/[i:id]/" (with slash) to "/foo/[i:id]" (without slash). Unfortunately, this breaks backward compatibility.

Another "safer" solution is to append "/?" only if the route doesn't already end in "/".

Parmaeters for Controller based routes.

I am currently using this route to load a controller, however I was wandering how you modify the route to allow for optional parameters without the trailing slash. Also the catch statement doesn't show the 404 error page (the 404 route has been defined).

respond('/[:controller]?/[:action]?', function ($request, $response) {
$controller = $request->param('controller', 'index') . 'Controller';
$action = $request->param('action', 'index') . 'Action';
try {
$controller = new $controller();
$controller->$action();
} catch (Exception $e) {
$response->code(404);
}
});

How regex-y is respond?

So I've read through most of the issues here and have seen some similar to what I have been working through. This may be more of a wiki/notes kind of thing, but it stumped me for a while, and I thought I'd share.

If I match on '/users' I want all users,
The thing to note here is that '/users' WILL NOT MATCH '/users/' (at least in my experience)

If I match on '/user/[i:id]/' I only want one.
If I'm going to potentially include other stuff and I don't want the '/user/[i:id]/' rule run, I can simply remove the last slash in it - setting it to '/user/[i:id]' then '/user/id/myotherstuff' won't trip the rule.

NOTE: the one gotcha here is that '/user/[i:id]' will match everything after '/user/' as id (as long as there is no slash)! So, if you are using it as an api (as I am) make sure your requests are well-formed! Add logic in the response handler as well to ensure you are only getting what you expect!

As a request, I'd like to see a bit more regex in the request...as an example, being able to add $ for end of string would make this a LOT easier to write for (IMHO) (and maybe you can? I haven't tried it because it's not in the docs)

Would a dev/expert like to chime in and correct me if I'm wrong? Perhaps a bit more about matching should go in the docs? I found the no-slash rule by accident!

trigger 404

I don't know if this is the right section to post a question.. but I would like to know how to trigger 404 error programmatically...
I know that this:

respond('404', function ($request) {
$page = $request->uri();
echo "Oops, it looks like $page doesn't exist..\n";
});

will be triggered when a route doesn't exists.. and this works well! but I would like to know how to manually call "something" in way to trigger the above respond.
thank you

Question about dynamically creating endpoints

Feel free to close this if it doesn't seem relevant but here is what I am trying to do:

$urls = array(array('point' => '/method1.[:format]', 'params' => 'test'));

// using old code format
foreach($urls as $url)
{
  respond('GET', $url['point'], function ($req, $resp, $app) {
    echo $app->always_run_same_function($url['params']);
  }
}

Is there a way for me to create all the endpoints and pass in extra data in somehow?
I'd be more than happy to elaborate if required. Thanks!

params() overwrites $_REQUEST params with slug names

klein.php seems to silently(?) overwrite $_REQUEST params with slug names.
Example:

respond("/[:name]?", function($request)
{
    print_r($request->params());
})

When invoked as "http://example.com/foobar?name=barquux", then the result is approximately this:

Array
(
    [name] => foobar
    [0] => foobar
)

Even though it should be this instead:

Array
(
    [name] => barquux
    [0] => foobar
)

I'm not sure why params() has an additional array value, and I don't know what it is supposed to contain, so for now, i'm just ignoring it.

I know that one could technically use faux-namespaces for slugs (such as "/[:slug_name]"), but it seems like this could be classified as bogus behaviour.

auto create key-value pair for "key:value" string in URI

URI:

/catalog/photo/user:13/group:48

ROUTE:

/[:model]/[:action]/[*]

$_REQUEST:

0 = catalog
model = catalog
1 = photo
action = photo
2 = 13
user = 13
3 = 48
group = 48

Actually, I wanted to say.
If the template is no clear guidance, and the value has the format "key: value" - to make the separation of the template

(.*):(.*)

Where the first entry to get the key and the second value.
Sorry for bad English (use - google translate).

Rendering a partial within a view

klein should have a facility for rendering a partial view within a layout:

<?php $this->partial( 'sidebar.phtml' ); ?>
<?php $this->yield(); ?>

In the above code, sidebar.phtml will be rendered twice:

  1. Developer sets a layout and calls render( 'index.phtml' )
  2. render() sets $response->_view to index.phtml
  3. The layout calls partial()
  4. partial() calls render()
  5. render() sets $response->_view to sidebar.html
  6. Layout calls yield(), which includes $response->_view (now sidebar.html)

Possible resolutions:

  • Force render() to set $response->_view back to its initial value after execution
    • Avoids possible duplicate $response->chunk()
  • Include a new convenience function for rendering a partial within a view

Callback to stop matching

I'd like to place something near the top of my routes like:

respond(function ($request, $response) {
  if (not_authenticated()) {
    $klein->stop_matching_routes_below();
  }
}

Is there a good way to do this? I don't want to exit at this point.

Regex route syntax fails in namespaces

Regex style routes fail if respond() is called within a namespace. Sample code for testing purposes:

<?php

include __DIR__ . '/klein/klein.php';

echo '<pre>';

respond( '/', function( $request, $response, $app ) {
    echo 'Hello, World.';
});

with( '/entities', function() {
    respond( function(){ var_dump( null ); } );
    respond( '*', function(){ var_dump( '*' ); } );
    respond( '/?', function(){ var_dump( '/?' ); } );
    respond( '/people/[:name]', function(){ var_dump( '/people/[:name]' ); } );

    respond( '@foo', function(){ var_dump( '@foo' ); } );
    respond( '!@foo', function(){ var_dump( '!@foo' ); } );

    respond( '!@^/foo', function(){ var_dump( '!@^/foo' ); } );
    respond( '@^/foo', function(){ var_dump( '@^/foo' ); } );

    respond( '@foo$', function(){ var_dump( '@foo$' ); } );
    respond( '!@foo$', function(){ var_dump( '!@foo$' ); } );

    respond( '@^/foo$', function(){ var_dump( '@^/foo$' ); } );
    respond( '!@^/foo$', function(){ var_dump( '!@^/foo$' ); } );

    respond( '@^/.*\.(json|csv)$', function(){ var_dump( '@^/.*\.(json|csv)$' ); } );
    respond( '!@^/.*\.(json|csv)$', function(){ var_dump( '!@^/.*\.(json|csv)$' ); } );
});

dispatch();

In the current version of klein, most URIs (e.g. /entities/foo) will only match the catch-alls:

NULL
string(1) "*"

klein should be patched so that route regexes are appended to the current namespace.

Can not open files with - in [a:action]

If I add the "-" dash to the regex pattern it allows it to go through:
'a' => '[0-9A-Za-z-]++',

However, even with that, The yield() method has a problem requiring the file.

My Code:
respond('lab/[a:action]', function($request, $response) {
$response->render('lab/open-source.php');
require('lab/' . $request->action . '.php'); // works
$response->render("lab/{$request->action}.php"); // fails
});

Handling GET query with Klein & Nginx

I may have an issue with my Nginx setup, but I cannot seem to pull out parameters for a URI like: mydomain.com/search/?query=findthis

Any clue as to my issue?

Separating route namespaces into separate files causes the inability to test via PHPUnit

When writing tests for a project using Paulus, I noticed that my routes were only be loaded once. It took me a few hours to finally figure out that the "with" function in Klein was using a "require_once", which was causing PHP to not load the routes for the next test (the first test always would run flawlessly).

Changing the "require_once" to simply a "require" should fix this issue.

onError not working with the new merge.

PHP Fatal error: Call to undefined method Klein\Response::onError() in /htdocs/index.php on line 16

$klein->respond('*', function ($request, $response, $app) {
$response->onError(function ($klein, $err_msg) {
error_log(print_r($err_msg,true));
$klein->flash($err_msg);
$klein->back();
});
});

[Feature request] Modular design

Klein is a router. That's what is said in the first line of a description. But actually it is a functional as micro-framework, like Limonade. Wouldn't it be nice if we had an option to choose a set of boilerplate methods to load? E.g.:

   // First, load all the basic stuff - routing, perhaps errors, HTTP codes, ..
   require('klein/klein.php'); 

   // Then modules
   // this one defines $response->render, $response->set, ..  
   // in this fashion: https://github.com/chriso/klein.php/issues/45#issuecomment-6520681
   require('klein/klein-views.php'); 

   // Validation
   require('klein/klein-validators.php');

respond('404'...) example on wiki broken

respond('404', function ($request) {
$page = $request->uri();
echo "Oops, it looks like $page doesn't exist..\n";
});

throws a uri() on non-object whereas the same code will work sans 404.

Git Tags for Stable Markings

Ok, so this isn't something I can commit and submit a pull request for, unfortunately...

Git tags are a really simple way of differentiating between stable releases in a code-base. I'm sure you've used them, but the references here are good for historical purposes, at least.

Klein.php is growing past the point of a stable code library, and is gaining features over time. It would help the community if certain points in time in the project's release were tagged with a version number of some sort.

Tagging a release is quite simple. In fact, its as easy as

git tag -a v1.0.1

What would be extremely helpful, is if some (few at best) previous points/commits were tagged, so that previously stable points in time could be referred to or used. Retroactively tagging a commit is, again, simple:

git tag -a v0.8.3 9fceb02

Finally, the most obvious and immediate benefit, is that this would allow for the easier installation of klein.php when using the composer package manager (re #69).

PS: Don't forget that you have to specify when pushing tags:

git push origin --tags

pluggable templating engine?

for a tiny proof of concept project, I cobbled together a little "unframework"/microframework... I started out with vanilla php and mustache for templating, then decided to pull in klein for it's lightweight routing...

the long and short of it is that now I'm just echoing $mustache->render() calls in klein's respond() callbacks... and not using either the $response object or $response->render() at all. I'm fishing for a better path, where I might be able to continue leveraging mustache's logicless language-agnostic templates, while also gaining the benefits of the klein $response api.

thoughts? snide remarks?

Feature - Routes as Objects

Currently, when calling the respond() method in the main Klein class, it simply adds your defined callback (and some meta) to an array property of $routes.

Although that's efficient and currently works, it is an interesting idea (brought up by people like @unstoppablecarl and @gbouthenot) to implement the routes as instances of a new Route class.

When doing this, we gain some interesting abilities:

  • More meta properties and a consistent way of accessing them
  • Method chaining that effects the route and not the Klein instance
  • Future expandability

Ideally, the current $routes property in the main Klein class would also be converted to a new data-structure of some sort (like an SplObjectStore structure or similar) so that we can add features such as route naming or reverse routing: allowing us to refer to a particular defined route through a unique name/index in a separate context.

Finally, we could use some sexy magic to keep compatibility with the current 2.0 codebase and to, well, show off a bit. Here's how:

Currently, the respond() method returns the callback that you passed into it, so that you could chain off the passed callback. If we define the __invoke() magic method (introduced in PHP 5.3) in the new Route class, we could still return the instance of the new route while allowing the returned Route instance to behave as a callable callback function also. I mean, that's sexy, right? #ithoughtso

Anyway, marking this as an issue so I don't forget my quick brainstorm on how this could work. ๐Ÿ˜„

No tests

Other than your example, there are no tests.

Please rectify this

dispatch() assumes app is not served from a subdirectory

I know you're not trying to make this everything to everybody - it's already quite fantastic as it stands. I fully understand if this is more than you'd want to include.

Description

The current setup always assumes klein is being using from the server's root. If I have multiple apps I want to serve independently of one another in various subdirectories, that assumption doesn't hold.

Solution Option 1

The code would be in the section that defines the $uri variable, right after the part where you trim off the query string

<?php
if ($appDir != '/' AND strpos($uri, $appDir) !== false) {
   $uri = substr($uri, strlen($appDir));
}
?>

I can do a pull request for the code if you like, but I thought you would be best able to determine how a user can define their $appDir.

Solution Option 2

You could leave query string munging up to the user. They're responsible for stripping out their $appDir prefix and must always pass an explicit URI to dispatch().

However, then they would also need to know when or if to trim off the query string, or handle whatever other future cases you have in your portion of the URI code.

kleinV2 can no longer provide custom methods

It seems that since KleinV2, you can no longer provide custom methods to Response. The new ServiceProvider class behaves the same.

Currently I found 2 workarounds:
1/ define the methods in \Klein\App, but this is not as elegant.
2/ use $tmp = $rs->custom; $tmp(); This causes new problems as $rs is not available in the closure to call other functions.

This is a feature I really loves in KleinV1.

Also it is no longer possible to overwrite existing methods.

git checkout v1.1.0

<?php
require "klein.php";

respond(function($rq, $rs, $ap) {
    $rs->custom = function(){echo "CUSTOM METHOD\n";};
});

respond("/", function($rq, $rs, $ap) {
    $rs->custom();
});

dispatch("/");

->

CUSTOM METHOD

git checkout v2.0.1

<?php
require "vendor/autoload.php";

$k = new \Klein\Klein();

$k->respond(function($rq, $rs, $sr, $ap) {
    $rs->custom = function(){echo "CUSTOM METHOD\n";};
});

$k->respond("/", function($rq, $rs, $sr, $ap) {
    $rs->custom();
});

$rq = \Klein\Request::createFromGlobals();
$rq->server()->set("REQUEST_URI", "/");

$k->dispatch($rq);

->

PHP Fatal error:  Call to undefined method Klein\Response::custom() in /home/gilles/web/kleinv2/disa2.php on line 11

Route regex compile fails on older PCRE versions

The compile_route function builds a regex to match routes. When finding a named parameter in the route, it uses the PCRE syntax (?<foo>), where foo is the param name. Even though the manual page says that this syntax was introduced in PHP 5.2.2, it was really introduced through the underlying PCRE library.

When PHP is compiled against an OS-provided version of PCRE instead of with the PHP-bundled version, support for this syntax may be absent.

Simply adding a P between the ? and the < will make things work again. Thus:

            $pattern = '(?:'
                     . ($pre !== '' ? $pre : null)
                     . '('
                     . ($param !== '' ? "?P<$param>" : null)
                     . $type
                     . '))'
                     . ($optional !== '' ? '?' : null);

The ?P<foo> format has been supported since 4.0, released in 2003, while the newer ?'foo' and ?<foo> have only existed since 7.0, released in 2006. (Changelog)

For some reason, my PHP is compiled against PCRE 6.6.

Usage help

Couldn't really find a better way to get help with this other than contacting the developer via email so creating this quick ticket.

I can't figure out why this isnt giving me access to both parameters:

respond('GET', '/service/[:id].[xml|json:format]?', function ($req, $resp) {
}

For the call site.com/service/1386.json, this is what the array has:

Array
(
    [id] => 1386.json
)

How can I get access to the format fields as well?

Thanks

URL rewriting - Apache syntax errors

If I put the Apache portion of your URL rewriting example in an .htaccess file, the Apache will fail to load that directory because it expects spaces after {REQUEST_FILENAME}.

Also, the RewriteRule which directs to /index.php is incorrect if the app in question is in a subdirectory and not the server's root.

See possible fixes at https://gist.github.com/910325

HMVC

First of all great framework :) Quick question, would it be easy to call a response method (route) internally i.e. you would get the exact same data returned as if you visited the url in your browser but you can call it within the system like $response= visit('som/url/in/your/app'); maybe even chain a header on there. I'll be taking a look at your code over the weekend when I have more free time :)

Many thanks,

CJ

Blank page, in basic app

One directory (testapp):

composer.json

{
        "name": "test/app",
        "require": {
                "php": ">= 5.3.0",
                "klein/klein": "dev-master"
        }
}

index.php

<?php
require_once __DIR__ . '/vendor/autoload.php';
respond('GET', '/hello', function ($request) {
    echo 'Hello World';
});
dispatch();

.htaccess

Options -Indexes
<IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule . index.php [L]
</IfModule>

than! a do:

... am I doing something wrong?
(And Yes, I have well configurated server, and I have mod_rewrite turned on, other apps, based on for example Nette Framework, works normaly)

Update Documentation for Version 2

Issue edits ongoing.....

(reference: https://github.com/Rican7/klein.php/tree/klein-v2)

OOP Changes

  • Change simple respond examples to add:
    • An autoload require_once
    • A Klein app instanciator
    • A $app-> reference for the Klein method calls
  • Change "with" example
    • To use ( $app )
    • To have the included files reference $this with the respond calls

API Changes

Breaking

Added functionality

  • $response->onError has a new, optional "allow_duplicates" argument
  • New OPTIONS request support (make sure to document the OPTIONS route too)
  • New HEAD request support (automatic, like a GET without the "body")

Sections to Add

  • Unit testing (how and why)
  • Contributing (how, best practices, code standards, PHPDocs, etc)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.