When creating an api each valid URI is mapped to an action. This action can be a specific function call or can set some parameters passed to a generic function.
My question is how or what are the good method to map an uri such as /auth/create to the right action.
To illustrate my attempts:
I thought about naming a function the same as a the URI replacing the / with Z to directly call the function by its name. I could basically simply execute the $request_uri directly without testing.
// from $request_uri = '/auth/create' I make;
$request_uri ='ZauthZcreate';
function ZauthZcreate($email, $password) {
echo "i've been called as expected \n";
}
$request_uri($_GET[email],$_GET[password]);
but it wouldn't work with something like /user/123123. I am trying to avoid falling in an endless cascade of if-else.
EDIT
I've iterated on this concept and found another solution:
$request_uri = '/api/auth/login';
$request_path = ltrim($request_uri,'/');
$request = explode('/', $request_path);
// begin point for api
if($method = array_shift($request)) {
if ($method == 'api') {
$method($request);
}
}
function api($request) {
$method = __FUNCTION__.'_'.array_shift($request);
if(is_callable($method)) {
$method($request);
}
}
// In a dedicated file for the scope auth
function api_auth($request) {
$method = __FUNCTION__.'_'.array_shift($request);
if(is_callable($method)) {
$method($request);
}
}
function api_auth_login($request) {
// api end point implementation here
}
function api_auth_create($request) {
// api end point implementation here
}
I wouldn't use those Z's, that's going to be needlessly difficult to read. In your above example you could do the same thing with just AuthCreate. You could also do this with OO design by making a base class for your main verbs (like Auth) and then having them declare their member functions.
Ultimately you wont want to resolve this with if/else blocks, but rather parse each part of the URI and see if a function in the right namespace exists, and once it doesnt start using your slashes as inputs (for your example above with /user/123123).
It might also do you well to look at how other REST API's are structured, because this is something of a solved problem
Related
I am writing my own php mvc framework (just for training). The question is how to handle exception when the requested controller doesn't exist? Should I call 404 class or create and show new View from Router? I'll be glad if you have any advices for me!
Here are my autoload.php:
function __autoload($class)
{
$filename = __DIR__ . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
if (file_exists($filename))
{
require $filename;
}
else
{
throw new \Exception('The file doesn\'t exists!');
}
}
and Route.php:
namespace App;
class Route
{
public static function start ()
{
$controller_name = 'News';
$controller_action = 'Index';
if (isset($_GET['furl']))
{
// Getting rid of spaces
$url = str_replace(' ', '', $_GET['furl']);
if (substr($url, -1) == '/')
{
$url = substr($url, 0, count($url) - 2);
}
$arr = explode('/', $url);
foreach($arr as &$value)
{
$value = strtolower($value);
$value = ucfirst($value);
}
$controller_action = $arr[count($arr) - 1];
unset($arr[count($arr) - 1]);
$controller_name = implode('\\', $arr);
}
$controller_name = '\App\Controllers\\' . $controller_name;
try
{
$controller = new $controller_name();
}
catch (\Exception $e)
{
//HELP ME PLS!
}
$controller->action($controller_action);
}
}
No matter how many web frameworks, routers, autoloaders, etc are there already: keep doing what you think it's right for you and suitable to your momentarily understanding level, in order to LEARN. Actually, by confronting yourself with problems arised along the process of implementing yourself different parts of your application, you will not only gain the opportunity to learn and discover new things, but also to learn how and what to study in the already existing frameworks' design.
Study the PHP Standard Recommendations (the ones marked as "accepted"). Especially PSR-1,2,4,7. They are used by many frameworks and PHP projects. Read FAQs to find out more about the project itself.
Autoloader:
The PSR-4 provides a link with examples at the document end.
#mike suggested, that you should use the Composer autoloader. I agree with him and I strongly recommend it to you too. BUT I suggest you to do this only after you correctly implement and make use of your own autoloader (PSR-4 conform). Why? You definitely need to learn how the autoloading process works. And in some future situations you will still need your own autoloader implementation, even after Composer is installed and running.
Also be aware that you must not raise any exceptions from autoloader itself!
Router:
Btw, your class should be called "Router".
The router should not be responsible for validating the controller class/file and the action, nor for calling the action. These tasks are part of the "front controller" responsibilities. Your router should just return the components resulted after parsing, e.g. "exploding" the request URI ($_GET['furl']), in some form (as a Route object (with them as properties), as array, etc). These components are the controller name, the action name, the action parameters list (NB: the action parameters are not the query string parameters). The front controller uses them further to validate/access the controller class/file and its action and to call the action.
But please note that a router works actually in other way. In short: it matches (e.g. compares) the request method (GET, POST, etc) and the request URI against an existing (e.g. predefined by you) list of route definitions. A route definition contains the infos related to a specific controller, action, etc. If the HTTP method and the request URI "correspond" to one of the route definitions, then the router returns the matched definition's components to the front controller (in some form: as object, as array, etc).
For more details describing this principle see:
How to load classes based on pretty URLs in MVC-like page?
FastRoute
Aura.Router
Front controller:
It can be a class, but it can also be just vanilla code in the entry point of your app (index.php, bootstrap.php, etc). In the latter case, the front controller code should reside in a file outside of the document root of the app. For example in a bootstrap.php file, which is to be just included in index.php - whereas index.php resides inside the document root.
"controller/action not found" specific handling:
If a controller, or an action is not found/valid, then call a predefined action (for example displayError) of a predefined Error controller, which informs the user that, for a specific part of his request (actually of his provided request URI), no resource was found.
For example, consider that the user provided the request URI www.example.com/books/show/12. Conform to your app workflow the controller is Book, the action (e.g. the controller's method) is show and the action parameter is 12 (the value is passed as argument to the show method and defined as $bookId parameter in it). But, if the controller class is not defined, or no controller file exists, then the front controller should call the action displayError of Error controller, which should display a message like No resource found for your 'book' request. A similar info should be displayed when the show method is not yet defined in the Book controller.
Note that, if the Error controller or its action is not found/valid, then the PHP engine raises a corresponding error/exception. If you follow the next links I provided, you'll end up implementing three custom error/exception handling functions (referenced by set_error_handler, set_exception_handler and register_shutdown_function, respectively). They will catch and handle the described situation properly.
To read: Manage the errors of a framework
General error/exception handling in MVC:
Here are some good resources:
Again: Manage the errors of a framework
Error logging, in a smooth way
Error reporting basics
The (im)proper use of try..catch
Other MVC related resources:
Build a PHP MVC Application (Just for the start...)
Dependency Injection and Dependency Inversion in PHP
MVC for advanced PHP developers (A further list of resources)
Tom Butler's Programming Blog. MVC, PHP, Best practices
Clean, high quality code
P.S: Avoid the use of statics, globals, singletons. Why? Read here and here, for example.
Good luck.
I want to test a helper function using Request::fullUrl in it.
function foo($arg)
{
// Get current full URL.
$url = Request::fullUrl();
// Return modified URL.
return $url;
}
The docs says:
You should not mock the Request facade. Instead, pass the input you desire into the HTTP helper methods such as get and post when running your test.
What are "the HTTP helper methods"?
They mean "TestCase::get" and "TestCase::post"?
Yes, my problem was solved by using $this->get().
But is this correct way?
class MyHelperTest extends TestCase
{
public function testFoo()
{
// Move to index page.
$this->get('/');
// Get a modified URL.
$url = foo('arg');
$this->assertEquals('Expected URL', $url);
}
}
It solved.
Using $this->get('/') is correct way.
https://laravel.com/docs/5.4/http-tests
The get method makes a GET request into the application
Using Laravel 5.2, and using a middleware, I need to remove a certain part from the URI of the request before it gets dispatched. More specifically, in a url like "http://somewebsite.com/en/company/about", I want to remove the "/en/" part from it.
This is the way I am doing it:
...
class LanguageMiddleware
{
public function handle($request, Closure $next)
{
//echo("ORIGINAL PATH: " . $request->path()); //notice this line
//duplicate the request
$dupRequest = $request->duplicate();
//get the language part
$lang = $dupRequest->segment(1);
//set the lang in the session
$_SESSION['lang'] = $lang;
//now remove the language part from the URI
$newpath = str_replace($lang, '', $dupRequest->path());
//set the new URI
$request->server->set('REQUEST_URI', $newpath);
echo("FINAL PATH: " . $request->path());
echo("LANGUAGE: " . $lang);
$response = $next($request);
return $response;
}//end function
}//end class
This code is working fine - when the original URI is "en/company/about", the resulting URI is indeed "company/about". My issue is this: notice that the line where I echo the ORIGINAL PATH is commented (line 8). This is done on purpose. If I uncomment this line, the code is not working; when the original URI is "en/company/about", the resulting URI is still "en/company/about".
I can only reach two conclusions from this: Either sending output before manipulating the request is somehow the culprit (tested - this is not the case), or calling the $request->path() method to get the URI has something to do with this. Although in production I will never need to echo the URI of course, and while this is only for debugging purposes, I still need to know why this is happening. I only want to get the URI of the request. What am I missing here?
Sidenote: The code originated from the first answer to this post:
https://laracasts.com/discuss/channels/general-discussion/l5-whats-the-proper-way-to-create-new-request-in-middleware?page=1
I don't think that line#8 is manipulating your output.
Here is the path() method from laravel's code:
public function path()
{
$pattern = trim($this->getPathInfo(), '/');
return $pattern == '' ? '/' : $pattern;
}
As you can see it is just extracting the pathInfo without editing the request itself.
I'm working on a RESTful and am stuck on message gathering for returning to the user. Basically, depending on the options selected, a few classes will be included dynamically. I'll try to provide a real-world break down. We have a HTML-email-tempalte maker - depending on the template chosen a php script will be included. This script may have warnings and I need to pass them "upstream" so that the API can report them. So we have something like this ( -> = includes )
API -> HTMLGenerator -> (dynamically) template-script.php
I need the template-script to be able to report errors to the API controller so the API can report them to the API user. Not sure the best way/practice to accomplish this.
So far , my thoughts are maybe a singleton or session variable that the template-script can add messages to, then the API Controller can report them. Any thoughts?
Main API
REST create by POST to /v1/html basically just:
class API {
require($dynamic_script);
$errors = array('warnings'=>array('warning1',waring2'));
//set http header and return JSON
}
HTMLGenerator
class HTMLGenerator {
//basically some wrappers for junior / non-programmers
function addHeading($text) {
//Add a header and do some checks.
if(strlen($text) > $warnTooLong )
HTMLErrors::addWarning("Message");
}
}
Dynamic Script
$h = new HTMLGenerator();
$h->addHeader($text);
$h->addImage($imageUrl);
You need to use a custom error handler.
See this link - http://php.net/manual/en/function.set-error-handler.php
It allows us to handle a error that might be thrown to capture it and process it. So, when you capture it, you can pass this to the parent class and furthur upstream for further processing.
Global object would work, set_error_handler too, but these are just hacks. The cleanest option is to modify your app elements to do what they are suppose to do - return those messages.
These shouldn't be too hard to do:
function myOldFunction($param1, $param2)
{
// do something
}
modify this way:
function myOldFunction($param1, $param2, array &$messages = array())
{
// do something
$messages[] = 'hey mama, i\'m on stack overflow!';
}
usage:
$messages = array();
myOldFunction(1, 2, $messages);
print_r($messages);
Much of my work lately has involved expanding and bug fixing ajax actions. But the action lists are fairly unmanageable in size, and since I was not the original author and the comments are sparse, I spend a great deal of time tracing the code paths and trying to figure out which jquery event triggered the action and if it sent the proper data with the request.
Right now the ajax request scripts are basically just about a hundred if-else blocks split up into different files based loosely on their function.
Is there a relevant design pattern or a php idiom to help me better organize the php part of the ajax requests?
I was thinking of maybe making some sort of dispatch interface. (Don't know if it is a good or workable idea.) where I can register actions and somehow indicate what data they require. The dispatcher would then call the function from the appropriate place. Then I could route all ajax requests through a single script and organize my functions however I want. And I can have an overview of what data is required to call a certain action without reading its implementation line by line. I would potentially have access to my server-side class heirarchy from the client side.
Does this sound workable? Is this safe? Are there other ways that might work better? The inspiration for this was basically smalltalk style message passing. My major worry is that I am going to introduce a cross-side request forgery vulnerability or that there is already one present in the code and due to it being difficult to read, I missed it.
I use a RPC-style mechanism to achieve what I think you want.
Disclaimer: I've successfully implemented this scheme in JS+PHP and JS+Python, so it is workable. But it might not be secure. You have to take all appropriate verification steps to make sure it is secure (especially w.r.t. to code/SQL injection and XSS attacks)
The idea is to have a single PHP script that processes the RPC requests, receiving the method name and its argument through both GET and POST, and outputs JSON back to the Javascript side.
For instance, on the client side:
API.rpc('getItemById', 1532, function(item) { console.log(item); });
would write
Object(id=1532,name="foo",whatever="bar")
on the console.
The communication protocol I use is the following:
the client sends an HTTP request to the RPC handler script, using
either GET or POST. The restrictions are that the 'method' must
always be provided in the GET, and that all arguments must be
URL-encoded. Otherwise, all arguments are given as key=value pairs and can be part of the request (GET) or the payload (POST)
the server always responds with an HTTP 200 (otherwise it means that a very nasty thing happened). It responds only with JSON data. The returned object has at least 2 members.
the 'success' member is always there, and indicates if the call succeeded - i.e. that no exception was thrown
if successful, the 'ret' members contains the return value of the function
if an exception was thrown, the 'message' member contains the exception message (I prefer sending the whole backtrace here, but that's certainly not good for sensitive environments)
(1) On the javascript side (assuming jQuery, coding as I think, so this may be buggy):
API = function() {
this.rpc = function(method, args, callback) {
return $.ajax({
url: 'rpcscript.php?method='+encodeURIComponent(args.method),
data: args,
type: 'post', //only the method name is sent as a GET arg
dataType: 'json'
error: function() {
alert('HTTP error !'); // This is e.g. an HTTP 500, or 404
},
success: function(data) {
if (data.success) {
callback(data.ret);
} else {
alert('Server-side error:\n'+data.message);
}
},
});
}
}
You can then add shortcut functions such as syncRPC() to perform synchronous calls, etc.
(2) On the PHP side (slightly modified running code):
class MyAPI
{
function getItemById($id)
{
// Assuming the $db is a database connection returning e.g. an associative array with the result of the SQL query. Note the (int) typecast to secure the query - all defensive measures should be used as usual.
return $db->query("SELECT * FROM item WHERE id = ".(int)$id.";");
}
}
class RemoteProcedureCall
{
function __construct()
{
$this->api = new MyAPI();
}
function serve()
{
header("Content-Type: application/json; charset=utf-8");
try
{
if (!isset($_GET['method']))
throw new Exception("Invalid parameters");
$methodDesc = array($this->api, $_GET['method']);
if (!method_exists($methodDesc[0], $methodDesc[1]) || !is_callable($methodDesc))
throw new Exception("Invalid parameters");
$method = new ReflectionMethod($methodDesc[0], $methodDesc[1]);
$params = array();
foreach ($method->getParameters() as $param)
{
// The arguments of the method must be passed as $_POST, or $_GET
if (isset($_POST[$param->getName()]))
// OK, arg is in $_POST
$paramSrc = $_POST[$param->getName()];
elseif (!in_array($param->getName(),array('action','method'))
&& isset($_GET[$param->getName()])
&& !isset($paramSrc[$param->getName()]))
// 'action' and 'method' are reserved $_GET arguments. Arguments for the RPC method
// can be any other args in the query string, unless they are already in $_POST.
$paramSrc = $_GET[$param->getName()];
if (!isset($paramSrc))
{
// If the argument has a default value (as specified per the PHP declaration
// of the method), we allow the caller to use it - that is, not sending the
// corresponding parameter.
if ($param->isDefaultValueAvailable())
$p = $param->getDefaultValue();
else
throw new Exception("Invalid parameters");
}
else
{
$p = $paramSrc;
}
$params[$param->getName()] = $p;
unset($paramSrc);
}
$ret = $method->invokeArgs($db, $params);
echo json_encode(array('success' => true, 'ret' => $ret));
}
catch (Exception $e)
{
echo json_encode(array('success' => false, 'message' => $e->getMessage()."\n".$e->getBacktrace()));
}
}
};
$rpc = RemoteProcedureCall();
$rpc->serve();
There are many application-specific assumptions here, including the kind of exceptions that may be thrown, the reserved keywords, etc ...
Anyway I hope this provides a good starting point for your problem.
You can have a look here: http://www.phpapi.org/
From description:
"This is the skeleton upon which you can develop a web-system from a simple Web Calculator to the most sofisticated CRM/ERP/CMS/ETC. What PHP-API provides is: a general structure of the code, a very simple extendable API code structure,JavaScript connectivity with the API ( with an easy way of adding new modules/method handlers ), ...."