klein / klein.php Goto Github PK
View Code? Open in Web Editor NEWA fast & flexible router
License: MIT License
A fast & flexible router
License: MIT License
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:
render( 'index.phtml' )
render()
sets $response->_view
to index.phtml
partial()
partial()
calls render()
render()
sets $response->_view
to sidebar.html
yield()
, which includes $response->_view
(now sidebar.html
)Possible resolutions:
render()
to set $response->_view
back to its initial value after execution
$response->chunk()
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!
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.
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?
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)
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!
/catalog/photo/user:13/group:48
/[:model]/[:action]/[*]
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).
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 "/".
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.
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.
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
.
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.
Hi all,
it seems that the solution described in the wiki Sub Directory Installation, is obsolete and does not work anymore.
Do you have an updated solution to this problem?
Thank you
Giancarlo
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.
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.
<?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
<?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
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.
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
}
});
ob_clean(): failed to delete buffer. No buffer to delete in Klein.php on line 589
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
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
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.
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
});
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.
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.
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.
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:
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. ๐
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');
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
The MUST NOT is clear and the constraint is stipulated in RFC2616 section 3.6 Transfer Codings
Trying to get a set of parameters using params($filter)
returns an array with the keys set to all null values.
Submit a page with GET or POST values. Attempt to extract just those values using params($filter)
.
An array with those name/value pairs.
An array with expected names, but all null values.
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.
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.
e.g. to get a string representing a POST body
Other than your example, there are no tests.
Please rectify this
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']));
}
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 ?
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
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.
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.
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;
}
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
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.
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.
(reference: https://github.com/Rican7/klein.php/tree/klein-v2)
respond
examples to add:
require_once
$app->
reference for the Klein method callsuse ( $app )
$this
with the respond
calls$response->onError
has a new, optional "allow_duplicates" argumentfor 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?
The json()
member function in your README.md documentation is listed under the wrong class - it currently shows up under $service
, while in the source it lives under $request
.
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
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();
});
});
in klein.php on line 21 - according to Apache.
respond('GET', '/posts', function($request)
{
echo "hello";
});
This code throws a 500 error.
Is there a reason https://gist.github.com/912833 doesn't work? So I goto someurl.com/hello/cj and it shows a blank page.
/[*: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
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);
}
});
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.
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
Would be great to have a few in either the wiki or in the readme.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.