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(...);
}
Related
My overarching question is, how can I pass the correct object type to a method dynamically?
I'm working on an old application and I'm trying to perform some improvements.
We have an endpoint for handling API requests. This endpoint dynamically calls a method with some code similar to this:
public function processApi()
{
$func = strtolower(trim(str_replace("/", "", $_REQUEST['rquest'])));
// Example: 'someEndpoint'
if (method_exists($this, $func)) {
$this->$func();
}
}
public function someEndpoint()
{
$someVariable = perform_some_sanitization($_REQUEST['some-variable']);
// Do stuff
}
I'd like to improve this a bit, and coming from Laravel, I would love to do something like this:
public function someEndpoint(SomeRequest $request)
{
$validatedData = $request->validated();
// Do stuff
}
This application is slated for depreciation, so the code doesn't have to be perfect or all-encompassing, so I'm just trying to develop some MVP logic.
So far I've been able to create a basic Request class and do something like this:
public function processApi()
{
$func = strtolower(trim(str_replace("/", "", $_REQUEST['rquest'])));
if (method_exists($this, $func)) {
// Passing a request object
$this->$func(new Request($_REQUEST));
}
}
// Type-hinting the request
public function someEndpoint(Request $request)
{
$validatedData = $request->validated();
// Do stuff
}
I want to be able to extend the Request class and create custom requests though. Since I'm doing $this->$func(new Request($_REQUEST)), it doesn't seem to work when I do public function someEndpoint(CustomRequest $request).
Any advice would be greatly appreciated!
Ok so my question is, i have a Facebook call back function that I'm using. The callback function uses an instance as an argument facebookCallBack(LaravelFacebookSdk $fb)
The function works great,I'm using this in my auth controller in larvel , but now I want to reuse this function within the same controller something like $this>facebookReusableCallBack(LaravelFacebookSdk $fb)
I'm having issues ,i get this error facebookReusableCallBack() must be an instance of SammyK\LaravelFacebookSdk\LaravelFacebookSdk, none given.
here's a sample of my code:
public function facebookCallBack(LaravelFacebookSdk $fb) {
$this->facebookReusableCallBack();
}
public function facebookReusableCallBack() {
//All my code here
}
how can I reuse this function within my controller?
i've tried :
public function facebookCallBack(LaravelFacebookSdk $fb) {
$this->facebookReusableCallBack(LaravelFacebookSdk $fb);
}
but the argument doesn't get passed as an instance?
You don't need to define the type of a variable when you pass it in.
Method definition:
public function facebookReusableCallBack(LaravelFacebookSdk $fb) {
//some code
}
Execution within another method:
function myMethod() {
$var = new LaravelFacebookSdk();
$this->facebookReusableCallBack($var);
}
What you have done is define the type of the variable in your function definition (good) to force the type. However these are only needed in the definition.
So for your explicit example:
public function facebookCallBack(LaravelFacebookSdk $fb) {
$this->facebookReusableCallBack($fb);
}
Because $fb must be an instance of LaravelFacebookSdk when calling facebookCallBack it will be fine for facebookReusableCallBack.
I'm currently creating an object in PHP that is declared on page load. The purpose of the Object is to check the connection between the client and a remote service via a function called 'checkHeartbeat'. This function will be called intermittently to see if the connection exists via AJAX.
I am trying to implement a testing parameter to alter the connection to results without querying the remote service:
fail
succeed
fail after x attempts
succeed after x attempts
The parameter is currently passed through via the URL which is picked up by the construct function of the object and placed into a variable via $_GET. Each time the checkHeartbeat function is called by AJAX it currently checks if the 'test' variable is set.
My aim is the eliminate this check on each call of checkHeartbeat however I am unsure the best method to do this. The AJAX Query will always call 'checkHeartbeat' and so I have been looking into extending/altering this function.
I attempted to re-define the object of $heartbeat within the construct to be the test object and therefore overwrite the checkHeartbeat function however this doesn't work and only returns that the connection is live. This is my re-factored code below as a demonstration of the desired outcome.
class heartbeat {
public function __construct() {
if(isset($_GET['test'])) {
$heartbeat = new testHeartbeat;
}
}
public function checkHeartbeat() {
echo 'Live connection works!';
}
}
class testHeartbeat {
public function checkHeartbeat() {
echo 'Test connection works!';
}
}
$heartbeat = new heartbeat;
$heartbeat->checkHeartbeat();
Looking into PHP objects further I have attempted to look at using Object Interfaces to define a separate function however I'm not sure how this would work and if it would still work by just calling the one function.
$heartbeat->checkHeartbeat();
My query is, what is the best method to change the 'checkHeartbeat' function to not check if the test parameter has been set on each AJAX call and only do this on the initial construct of the object. If the test parameter has been passed the checkHeartbeat will only return the test status OR will instantly make the call to the remote service if no test parameter has been set.
I mock this up playing around and does the work.
I am sure there is a better way, actually cleaner.
I did not found a way to modify the public variable instantiation inside the same object.
Hope this helps:
class heartbeat {
public $foo;
public function __construct()
{
$this->checkTest();
return $this->foo;
}
public function checkTest()
{
if(isset($_GET['test']))
{
$this->foo = 'true';
}
else
{
$this->foo = 'false';
}
}
public function __toString()
{
return $this->foo;
}
public function checkHeartbeat() {
echo 'Live connection works!';
}
}
class testHeartbeat {
public function checkHeartbeat() {
echo 'Test connection works!';
}
}
new heartbeat()=='true' ? $heartbeat = new testHeartbeat() : $heartbeat = new heartbeat();
$heartbeat->checkHeartbeat();
Bb!
Hello
folks,
I wrote a low level implementation for a XmlRPC-Api and I've trouble to test the communication.
Here is my code.
abstract class BaseClient
{
protected function call($method, array $params = array())
{
$request = xmlrpc_encode_request($method, $parameters);
$file = file_get_contents($this->getDSN(), false, $context);
$response = xmlrpc_decode($file);
if ($response && xmlrpc_is_fault(array($response))) {
trigger_error("xmlrpc: {$response[faultString]} ({$response[faultCode]})");
}
return $response;
}
}
Client extends BaseClient
{
public function testCall($msg)
{
return $this->call('testMethid', array($msg));
}
}
And here is my Test.
ClientTest extends PHPUnit_FrameWork_TestCase
{
public function testTestCall()
{
$c = new Client();
$resp = $c->testCall('Hello World');
$this->assertEquals('Hello World', $resp);
}
}
This test will crash every time, because its not possible to access the api inside a testing environment.
I can't see a solution to mock and inject the call function. What can I do? Maybe my object structure is bad and not able to test
and how can I improve this structure (if this happen)?
Cheers.
Since you're trying to test an external API, I would begin by wrapping your file_get_contents() call in another class and injecting that into your BaseClient. In the simplest form, it might look something like this:
class RemoteFileRetriever
{
public function retrieveFileContents($url)
{
// Do some work to create $context
...
// Now grab the file contents
$contents = file_get_contents($url, false, $context);
return $contents;
}
}
abstract class BaseClient
{
private $fileRetriever;
public function __construct(RemoteFileRetriever $fileRetriever)
{
$this->fileRetriever = $fileRetriever;
}
protected function call($method, array $params = array())
{
...
$file = $this->fileRetriever->retrieveFileContents($this->getDSN());
...
}
}
Now in your test, you can use a mock object to inject as the file retriever. E.g.:
class ClientTest extends PHPUnit_FrameWork_TestCase
{
public function testTestCall()
{
$mockRetriever = new MockRemoteFileRetriever();
$c = new Client($mockRetriever);
$resp = $c->testCall('Hello World');
$this->assertEquals('Hello World', $resp);
}
}
PHPUnit atually has some built-in helpers for mocking. See PHPUnit's Mock Objects.
You don't want to mock the call function.
If you can't setup a fake service then you want to mock the php functions which you can do using PHP Namespacing (Have to have PHP 5.3). You can then create mocks for internal php functions that you are calling in your call method.
http://www.schmengler-se.de/-php-mocking-built-in-functions-like-time-in-unit-tests
If you are not able to do this, testing can be pretty difficult. Can you create a fake api that you can hit for testing? Remember that you aren't actually testing the methods of the api, rather you are trying to make sure that you code makes the request to the api and handles the response in the manner you intend.
As a rule, assume that third party code has been tested and works properly.
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.