I'm writing an API class, and my general goal is for it to be easy to make any class's methods accessible via the API, without having to make any serious changes to the class itself. Essentially, I should be able to instantiate an API class instance on any class that I want to use (within my little framework), and have it just work.
For example, In my API class, I have a method call, that I want to use $_GET to call the correct function from the class that I want to make accessible (let's call it Beep). So I specify an action parameter in my API, so that the action is the method of Beep to call, with the remaining arguments in $_GET being, presumably, the arguments for the method. In API->call, I can do $BeepInstance->$_GET['action'](), but I have no way of determining which arguments from $_GET to send, and in what order to send them.
func_get_args will only return the list of given arguments for the function in which it is called, and I don't necessarily know the correct order in which to pass them with call_user_func_array.
Has anyone tried to do something similar to this?
Here's a solution + example that uses reflection to map your input arguments to method parameters. I also added a way to control which methods are exposed to make it more secure.
class Dispatch {
private $apis;
public function registerAPI($api, $name, $exposedActions) {
$this->apis[$name] = array(
'api' => $api,
'exposedActions' => $exposedActions
);
}
public function handleRequest($apiName, $action, $arguments) {
if (isset($this->apis[$apiName])) {
$api = $this->apis[$apiName]['api'];
// check that the action is exposed
if (in_array($action, $this->apis[$apiName]['exposedActions'])) {
// execute action
// get method reflection & parameters
$reflection = new ReflectionClass($api);
$method = $reflection->getMethod($action);
// map $arguments to $orderedArguments for the function
$orderedArguments = array();
foreach ($method->getParameters() as $parameter) {
if (array_key_exists($parameter->name, $arguments)) {
$orderedArguments[] = $arguments[$parameter->name];
} else if ($parameter->isOptional()) {
$orderedArguments[] = $parameter->getDefaultValue();
} else {
throw new InvalidArgumentException("Parameter {$parameter->name} is required");
}
}
// call method with ordered arguments
return call_user_func_array(array($api, $action), $orderedArguments);
} else {
throw new InvalidArgumentException("Action {$action} is not exposed");
}
} else {
throw new InvalidArgumentException("API {$apiName} is not registered");
}
}
}
class Beep {
public function doBeep($tone = 15000)
{
echo 'beep at ' . $tone;
}
public function notExposedInAPI()
{
// do secret stuff
}
}
Example:
// dispatch.php?api=beep&action=doBeep&tone=20000
$beep = new Beep();
$dispatch = new Dispatch();
$dispatch->registerAPI($beep, 'beep', array('doBeep'));
$dispatch->handleRequest($_GET['api'], $_GET['action'], $_GET);
We did something similar in our API. We used a proxy method _methodName($p) and passed in the $_GET or $_REQUEST array. The proxy method knows the order of the parameters required for the real method, so it invokes the real method correctly. Using call_user_func_array() worked pretty well with that.
Not sure if that's the best way to go about it, but it works well for us.
The controller looks something like this:
if (method_exists($server, "_$method"))
$resp = call_user_func_array("{$server}::_$method", array($_REQUEST));
And then the model is setup like:
public function test($arg1, $arg2) { ... }
public function _test($p) {
return $this->test($p['arg1'], $p['arg2']);
}
I'd propose to pass an associative array the the respective method. Since the assoc. array provides a name to value mapping.
Moreover, never do something like this:
$BeepInstance->$_GET['action']()
This is highly insecure.
Probably define another associate array, which maps actions passed as GET 'action' parameters to actual method names.
Related
I have a class that someone else wrote. It does a lot of things for me, and one thing it does for me is it makes requests to an external service.
public function makeRequest() {
...bunch of curl stuff here
}
So what I wanted to do is modify the constructor of the class so that I could pass in a function, and the function could get called after the curl stuff. I come from a javascript background so I'm definitely doing this the wrong way, but this is what I did:
private $requestLogger;
public function __construct(...other variables, $requestLogger = null) {
if ($requestLogger) {
$this->requestLogger = $requestLogger;
}
}
public function makeRequest() {
...bunch of curl stuff here
if ($this->requestLogger) {
$curlInfo = curl_getinfo($ch);
$this->requestLogger($curlInfo['url'], $curlInfo['http_code'], $request, $response);
}
}
And that when when I make a new instance, I can do it like this
$client = new ApiClient(..., function($url, $responseCode, $requestText, $responseText){
// do whatever i want here
});
However, this hasn't worked. I get this message: 500: Call to undefined method ApiClient::requestLogger()
How do I set myself up to pass a callback function to this class?
When you do: $this->requestLogger(...), PHP thinks you're trying to call a class method called requestLogger().
If you want to call a function in a class property, you can use call_user_func_array(). Something like this:
call_user_func_array($this->requestLogger, [
$curlInfo['url'],
$curlInfo['http_code'],
$request,
$response
]);
A tip, before trying to call it, make sure it contains something that's callable and not just empty:
if (is_callable($this->requestLogger)) {
call_user_func_array(...);
}
Is using computed include / require a bad code smell and does it have a bad impact on the performance? And I guess that having the included file execute code is also a bad thing to do, but is it ok if that behavior is documented?
Background information / Reason for my question:
I need to call an API to get information about some services. I have about 50 services with each service needing to call the API for 0-6 times. So I'm looking for a way to configure
The parameters for the API call (argumenttype differs between calls, may be a string but it also may be an array)
Define which API to call
I thought of having a single file for each service containing the calls and return the information as a single array like this:
<?php
// Params for Call 1
$anArrayWithSomeParams = array('param1', 'param2', 'param3', 'param4');
// Params for Call 2
$aString = 'string1';
$anotherString = 'string2'
// Params for Call 3-6
...
$someInformation = $dmy->getSomeInformation($anArrayWithSomeParams);
$notNeededHere = NULL;
$moreInformation = $dmy->getMoreInformation($aString,$anotherString);
...
$allData = array( '0' => $someInformation,
'1' => $notNeededHere
'2' => $tablespace,
....
);
?>
I then could include that file and use the variable alldata to access the data and do something with it like this:
require_once('class.dummy.php');
$directories = array("dir1", "dir2", "dir3");
$dmy = new dummy();
foreach($directories as $path) {
$allData = NULL;
$executeDataCollection = $path.'myFile.php';
require($executeDataCollection);
print "<pre>";
print_r($allData);
print "</pre>";
}
While this might work, it does not seem like an elegant solution. I was wondering if somebody could give me a hint towards a more elegant/sophisticated way of handling this.
Thanks in advance!
Using require and any of similiar approach is bad practice.
You should think more in OOP way how to implement this. To achieve something like this I would suggest to use interface and abstract class. In your case you need to call some APIS with different parameters on demand you should use following patterns/principles:
Adapter
Factory
Gateway
S.O.L.I.D - some of the principles will help you to design better what you need
Interface will look like:
interface ApiGateway {
/**
* This will execute call with optional parameters
*
**/
public function call($parameters = null);
}
Abstract class
abstract class ApiGatewayAbstract implements ApiGateway
{
/** return Adapter for handle API call **/
abstract protected function getAdapter();
/** return list of arguments for call */
abstract protected function getArguments();
public function call($parameters = null)
{
$adapter = $this->getAdapter();
$arguments = $this->getArguments();
// this will be HTTPAdapter for executing API call you need with specific params and arguments
return $adapter->execute($arguments, $parameters);
}
}
Now you can start implementing specific ApiGateways:
class MyApiGateway extends ApiGatewayAbstract
{
protected $arguments = [];
protected $adapter;
public function __construct(HttpClientInterface $httpClient, array $arguments = [])
{
$this->arguments = $arguments;
$this->adapter = $httpClient;
}
protected function getArguments()
{
return $this->arguments;
}
protected function getAdapter()
{
return $this->adapter;
}
}
Final step would be Factory for your ApiGateways:
class ApiGatewayFactory
{
// dynamic way to get Specific api gateway by name, or you can implement for each api gateway specific method
public function getApiGateway($name, HttpClientInterface $adapter, array $arguments)
{
$className = 'Namespace\\'.$name;
if (!class_exist($className) {
throw new \Exception('Unsuported ApiGateway');
}
// here you can use reflection or simply do:
return new $className($adapter, $arguments);
}
}
By this approach you will achieve clean way of what you want and also follow some of the principles from S.O.L.I.D. So you can add more ApiGateways with specific use cases, or different adapters ( soap, http, socket ) etc.
Hope this helps, also this is just an example have a look at the patterns and how to implement them. But this example should help you understand the approach.
I have seen in Laravel calling multiple method in the single line, example:
DB::get('test')->toJson();
I have a cool class and view method in that class.
$this->call->view('welcome')->anotherMethod();
I would like to call another method also? Where should I make that method?
DB::get() seems to be a method returning an object, where you can call other functions (I think a result object of a database query). If you want to call multiple functions on one object in one line, you have to return $this in your functions, e.g.:
class View {
public static function factory {
// The question is: How useful is this factory function. In fact: useless in
// the current state, but it can be extended in any way
return new self;
}
public function one() {
// do something
return $this;
}
public function two() {
// do something
return $this;
}
}
Then you can do:
$class = new View();
$class->one()->two();
// it's also possible to use the `factory` function
// you should think about, how useful this approach is in your application
$class = View::factory()->one()->two();
That's how you can do it in php, if laravel has some helpers for that, i can't say :)
When I am using CodeIgniter to implement a small application, I want all business checking functions in controllers could be defined outside the controller for more flexible.
The checking functions just like session checking, user information completion checking, or has user post an article, and so on.
Firstly, I tried to use helper to implement. In my core controller which extended from CI_Controller I wrote a check function:
protected function check () {
$this->checked = TRUE;
$methods = func_get_args();
foreach ($methods as $method) {
$m = 'check_' . $method;
if (!function_exists($m)) {
$this->load->helper("filters/$m");
}
if ($m() === FALSE) {
return FALSE;
}
}
return TRUE;
}
Then in any controller I can use this method to check my business logic like this:
public function something ()
if (!$this->check('session')) {
return $this->relogin();
}
// ...
if (!$this->check('userinfo')) {
return $this->redirect('settings');
}
// ...
if (!this->check('has_post')) {
// ...
}
// ...
}
But it has a problem that all helper function are global, and can't invoke protected functions in $CI instance. I didn't find a way how to invoke a instance function outside like JavaScript's call/apply.
So I turned to check the hook document of CI. But I don't think it helps, because the hook point can't be inside of any controller functions. It must be outside.
At last, I can just fallback to put all checking functions into core controller class. So I wonder is there any way to use interceptor as in Java structs?
You can use _remap()!
add a function into the class:
_remap($method, $params=[]) {
if (true) {
$this->$method($params);
} else {
$this->method5();
}
}
The logic is that everytime someone call a Method, the API would FIRST execut the REMAP function... inside you can do whatever you need and then decide if the API should execute the called method or another or none...
Well,
I have a problem (ok, no real problem, but I wanna try out something new) with creating objects. Actually I have some orders, which contains a list of orderitems.
These orderitems are used and so spreaded in the whole application, and I need a way to create them. The main problem is, I want to be able to create these objects in many different ways.
Actually I do this in the class constructor and check if the argument which is given.
(I'm using php, so there is no overloading support from the language as you surely know :))
A simple and quick Example
class foo {
protected $_data=null;
public function __contruct($bar){
if (is_array($bar)){
$this->_data=$bar;
}
else {
$dataFromDb=getDataFromDatabase
$this->_data=$dataFromDb;
}
}
}
Anyway, if I want to create my object by giving another type of parameter, lets say a xml-document encapsulated in a string I need to put all this stuff in my constructor.
If the process for creating an object is more complicated, I eventually need to create a seperate method for each type, I want to initiate. But this method is only called when this special type is created. (I think you got the problem :))
Another problem comes to mind, if I need more parameters in the constructor to create a concrete object, I have modify all my code, cause the contructor changed. (Ok, I can give him more and more parameters and work with default values, but that is not what I really want).
So my Question is, which pattern fits this problem to solve my creation of a concrete object. I thought about creating a factory for each way I want to create the concrete object. But I'm not sure if this is a common solution to solve such a problem.
IF its only the signature of the constructor changing i would do it like so (a la the Zend Framework universal constructor):
class foo {
// params
public function __construct($options = null)
{
if(null !== $options)
{
$this->setOptions($options);
}
}
public function setOptions(array $options){
foreach ($options as $name => $value){
$method = 'set' . $name;
if(method_exists($this, $method)
{
$this->$method($value);
}
}
return $this;
}
}
And this essntially means all your constructor parameters are array elements with named keys, and anything you want used in this array during initialization you create a setter for and then its automatically called. The down side is the lack of effective hinting in IDEs.
On the otherhand if you want to have specific constructors then i might go with a factory but still use much the same approach:
class foo {
public static function create($class, $options)
{
if(class_exists($class))
{
$obj = new $class($options);
}
}
}
Of course you could alternatively use PHP's reflection to determine how to call the constructor instead of just injecting an arbitrary array argument.
you could simply make it a factory with optional params :)
class Example_Factory
{
public static function factory($mandatoryParam, $optionalParam = null)
{
$instance = new self;
$instance->setMandatory($mandatoryParam);
if ($optionalParam !== null) {
$instance->setOptional($optionalParam);
}
return $instance;
}
public function setMandatory($in)
{
// do something....
}
public function setOptional($in)
{
// do some more...
}
}