I tried implementing the below code but it is showing the EachPromise Error class not found and Promise Error class not found. Guzzle library is installed. Then also this error was there.
<?php
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Psr7\Response;
$users = ['one', 'two', 'three'];
$promises = (function () use ($users) {
foreach ($users as $user) {
// Using generator
yield $this->getAsync(
'https://api.demo.com/v1/users?username='
. $user);
}
})();
$eachPromise = new EachPromise($promises, [
// Number of concurrency
'concurrency' => 4,
'fulfilled' => function (Response $response) {
if ($response->getStatusCode() == 200) {
$user = json_decode(
$response->getBody(), true);
// processing response of the user
}
},
'rejected' => function ($reason) {
// handle promise rejected
}
]);
$eachPromise->promise()->wait();
?>
Seem there is no autoloader that locates and loads the class files mentioned in your code.
If your Guzzle package is installed with composer, you may try require_once() the /vendor/autoloader.php file in your source root.
Otherwise, try loading those class files individually - which is not recommended in modern PHP world(use autoloader whenever possible).
require_once("path-to/EachPromise.php");
require_once("path-to/Response.php");
EDIT
In the second sight, your code uses $this that requires an object instance context with $this->getAsync() being available, but apparently not so. The code seems like a partially copy-pasted code fragment from a Class object that won't run in your given set up.
You may need to set up the code context of your sample code.
Related
I am using Serverless framework to deploy my PHP code as IBM Cloud Function.
Here is the code from the action PHP file:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
}
And this is the serverless.yml file:
service: cloudfunc
provider:
name: openwhisk
runtime: php
package:
individually: true
exclude:
- "**"
include:
- "vendor/**"
functions:
test-sentry:
handler: actions/test-sentry.main
annotations:
raw-http: true
events:
- http:
path: /test-sentry
method: post
resp: http
package:
include:
- actions/test-sentry.php
plugins:
- serverless-openwhisk
When I test the action handler from my local environment(NGINX/PHP Docker containers) the errors are being sent to Sentry.
But when I try to invoke the action from IBM Cloud nothing appears in the Sentry console.
Edit:
After some time trying to investigate the source of the problem I saw that its related with the async nature of sending the http request to Sentry(I have other libraries that make HTTP/TCP connections to Loggly, RabbitMQ, MySQL and they all work as expected):
vendor/sentry/sentry/src/Transport/HttpTransport.php
in the send method where the actual http request is being sent:
public function send(Event $event): ?string
{
$request = $this->requestFactory->createRequest(
'POST',
sprintf('/api/%d/store/', $this->config->getProjectId()),
['Content-Type' => 'application/json'],
JSON::encode($event)
);
$promise = $this->httpClient->sendAsyncRequest($request);
//The promise state here is "pending"
//This line here is being logged in the stdout of the invoked action
var_dump($promise->getState());
// This function is defined in-line so it doesn't show up for type-hinting
$cleanupPromiseCallback = function ($responseOrException) use ($promise) {
//The promise state here is "fulfilled"
//This line here is never logged in the stdout of the invoked action
//Like the execution never happens here
var_dump($promise->getState());
$index = array_search($promise, $this->pendingRequests, true);
if (false !== $index) {
unset($this->pendingRequests[$index]);
}
return $responseOrException;
};
$promise->then($cleanupPromiseCallback, $cleanupPromiseCallback);
$this->pendingRequests[] = $promise;
return $event->getId();
}
The requests that are registered asynchronously are sent in the destructor of the HttpTransport instance or when PHP shuts down as a shutdown function is registered. In OpenWhisk we never shut down as we run in a never-ending loop until the Docker container is killed.
Update: You can now call $client-flush() and don't need to worry about reflection.
main() now looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$client->flush();
return [
'body' => ['result' => 'ok']
];
}
Original explanation:
As a result, to make this work, we need to call the destructor of the $transport property of the Hub's $client. Unfortunately, this private, so the easiest way to do this is to use reflection to make it visible and then call it:
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
This will make the $transport property visible so that we can retrieve it and call its destructor which will in turn call cleanupPendingRequests() that will then send the requests to sentry.io.
The main() therefore looks like this:
function main($args): array {
Sentry\init(['dsn' => 'SENTRY_DSN' ]);
try {
throw new \Exception('Some error')
} catch (\Throwable $exception) {
Sentry\captureException($exception);
}
$client = Sentry\State\Hub::getCurrent()->getClient();
$property = (new ReflectionObject($client))->getProperty('transport');
$property->setAccessible(true);
$transport = $property->getValue($client);
$transport->__destruct();
return [
'body' => ['result' => 'ok']
];
}
Incidentally, I wonder if this Sentry SDK works with Swoole?
Function runtimes are "paused" between requests by the platform. This means any background processes will be blocked if they aren't finished when the function returns.
It looks like the asynchronous HTTP request doesn't get a chance to complete before the runtime pauses.
You will need to find some way to block returning from the function until that request is completed. If the Sentry SDK has some callback handler or other mechanism to be notified when messages have been sent, you could use that?
i'm throwing a exception in my controller when the user is not authorized to make any action, i created a custom exception class to do it. i'm using this package too: https://github.com/esbenp/heimdal to format my response in a better format.
however, when i set my status code in my exception to 401, my response is 500.
<?php
namespace App\Acl\Exceptions;
use Exception;
use Throwable;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class UserUnauthorizedException extends Exception
{
public function __construct($message = "", $code = 0, \Throwable $previous = null)
{
parent::__construct('you are not authorized to perform this action', 401, $previous);
}
Instead of
class UserUnauthorizedException extends Exception
try:
class UserUnauthorizedException extends UnauthorizedHttpException
Note that 401 that you are using in the constructor is not a HTTP code. It's an arbitrary value that you can set for custom evaluation somewhere else.
Try below code:
<?php
namespace App\Acl\Exceptions;
use Exception;
use Throwable;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class UserUnauthorizedException extends UnauthorizedHttpException
{
public function __construct($message = "", $code = 0, \Throwable $previous = null)
{
parent::__construct('you are not authorized to perform this action', 401, $previous);
}
This is caused by the library you use. You could create an issue or submit a PR yourself otherwise you might have to switch to an alternative.
For reference, this is where the response status code is set: https://github.com/esbenp/heimdal/blob/master/src/Formatters/ExceptionFormatter.php#L13
edit: Basically you have to either extend the correct HttpException that is already being handled by the HttpExceptionFormatter in your library or update the configuration specifying that your Exception should be handled by it. The config could look something like this:
return [
'formatters' => [
\App\Acl\Exceptions\UserUnauthorizedException::class => \Optimus\Heimdal\Formatters\HttpExceptionFormatter::class,
\Exception::class => \Optimus\Heimdal\Formatters\ExceptionFormatter::class,
]
];
This will use the HttpExceptionFormatter for your exception (and every class inheriting from it) whereas all other exceptions are handled by the generic ExceptionFormatter.
The default config can be found in the project's README: https://github.com/esbenp/heimdal#formatters
From this you can also gather that if you extend Symfony\Component\HttpKernel\Exception\HttpException the HttpExceptionFormatter should kick in and should use the status code that you provide.
Having a NuSOAP web service defined in an inline route closure function works great, but having it in a route closure controller does not.
Example: working
routes.php:
Route::any('api', function() {
require_once ('nusoap.php');
$server = new \nusoap_server();
$server->configureWSDL('TestService', false, url('api'));
$server->register('test',
array('input' => 'xsd:string'),
array('output' => 'xsd:string'),
);
function test($input){
return $input;
}
$rawPostData = file_get_contents("php://input");
return \Response::make($server->service($rawPostData), 200, array('Content-Type' => 'text/xml; charset=ISO-8859-1'));
});
SOAP Test Client
require_once('nusoap.php');
$client = new \nusoap_client('http://my-laravel-installation.com/api?wsdl', true);
$result = $client->call("test", "HelloWorld");
print_r($result); exit();
response
HelloWorld
This works as expected.
Example: not working
Moving the code to a dedicated controller breaks it:
routes.php:
Route::any('api', 'SoapController#server');
SoapController.php:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SoapController extends Controller {
public function server() {
require_once ('nusoap.php');
$server = new \nusoap_server();
$server->configureWSDL('TestService', false, url('api'));
$server->register('test',
array('input' => 'xsd:string'),
array('output' => 'xsd:string'),
);
function test($input){
return $input;
}
$rawPostData = file_get_contents("php://input");
return \Response::make($server->service($rawPostData), 200, array('Content-Type' => 'text/xml; charset=ISO-8859-1'));
}
}
SOAP Test Client
require_once('nusoap.php');
$client = new \nusoap_client('http://my-laravel-installation.com/api?wsdl', true);
$result = $client->call("test", "HelloWorld");
print_r($result); exit();
response
"method 'test' ('test') not defined in service('' '')
Steps To Reproduce:
Install a fresh copy of Laravel v5.2.45
Download the last NuSOAP version (v0.9.5)
Define the route for the NuSOAP Web Service - copy and paste the code from above
Create the SoapController - copy and paste the code from above
Create a new temp route to act as the SOAP client, and copy and paste the code beneath SOAP Test Client above into this route's closure function
Load the temp route page in the browser to execute a SOAP call to the Web Service
Conclusion:
This indicates, that for some reason, the output is different when using an inline route function versus using a dedicated route controller.
How could this be?
Your insight greatly appreciated:
If you have:
any experience with this and you have a solution
or, you have an idea on why this could occur
or, you have any thoughts on why having the NuSOAP code in the controller generates a different response than having it in an inline route function
...please chime in.
Thank you for your thoughts!
I have a simple solution for this.
Just install nusoap via composer.
composer require econea/nusoap
Call nusoap as needed:
$client = new \nusoap_client('http://my-laravel-installation.com/api?wsdl', true);
I hope that helps.
CSRF validation should be passive for current laravel versions.
app\Http\Middleware\VerifyCsrfToken.php
protected $except = [
'your way'
];
Must be.
I'm using Laravel to create a JSON REST API, and it has been quite present so far. However I'm needing to wrap my JSON outputs with a bit of meta status JSON created by say a metaDataController (or probably model) and I am curious what a good approach to this might be.
For instance, all responses would take on the following format:
{
"meta": {
"status": 200,
"notifications": 2
},
"response": {
//JSON from the route's Controller/Model/etc
}
}
From what I can tell I either need to modify the Laravel Response defaults and delegate to a metaDataController, or create some sort of Route::any that merges the two sections of JSON as mentioned in Returning Multiple Laravel Eloquent Models as JSON. Although I always know metaDataController, the other controller is in flux depending on the route.
I'm thinking there must be a way to declare this structure as a default for all routes or a Route::group.
Thanks!
I don't think doing json_decode->json_encode cycle is an acceptable solution (as in Chris answer).
Here is another solution
use Illuminate\Http\Response;
use Illuminate\Http\Request;
Route::filter('apisuccess', function($route, Request $request, Response $response = null) {
$response->setContent(json_encode([
'data' => $response->original,
'meta' => ['somedata': 'value']
]));
});
Then I would attach this filter to my REST API routes.
Edit: another solution (more complex).
Create a custom Response class:
use Illuminate\Http\Response;
class ApiResponse extends Response
{
protected $meta;
protected $data;
public function __construct($content = '', $status = 200, $headers = array())
{
parent::__construct([], $status, $headers);
$this->meta = [];
}
public function withMeta($property, $value = null)
{
if (is_array($property))
$this->meta = $property;
else
array_set($this->meta, $property, $value);
return $this;
}
public function withData($data)
{
$this->data = $data;
return $this;
}
public function sendContent()
{
echo json_encode(['success' => true, 'data' => $this->data, 'meta' => $this->meta, 'echo' => ob_get_contents()]);
}
}
Put it as a singleton in IOC container:
$this->app->bindShared('ApiResponse', function() {
return new \Truinject\Http\ApiResponse();
});
Finally, create filter and use it as "before" on your routes:
Route::filter('apiprepare', function(Illuminate\Routing\Route $route, Illuminate\Http\Request $request) {
$data = $route->run();
return App::make('ApiResponse')->withData($data);
});
So we are basically overriding default response class with our own, but still calling the appropriate controller with $route->run() to get the data.
To set meta data, in your controller do something like this:
\App::make('ApiResponse')->withMeta($property, $value);
I've added method "meta" in my base API controller class, which encapsulates this.
You could use the global after filter in app.php to catch all responses, then reconfigure it however you please:
App::after(function($request, $response)
{
if(is_a($response, 'Illuminate\Http\JsonResponse')) {
$response->setContent(json_encode(array(
'data' => json_decode($response->getContent()),
'foo' => 'bar',
'cat' => 'dog'
)));
}
});
In the above example, you're taking all the existing json data and putting it in a child data element (this would be "response" in your example) then adding foo and bar. So foo, bar and data would be top level json objects.
If you don't like the global positioning, after is an event sent, so you could also listen to it inside a controller/elsewhere.
I'm using the Amazon Simple Email Service and am trying to implement it as an abstract class so that I can simply use it throughout as needed.
Problem
The problem occurs with the use, I cannot work out how to require the files and classes needed to use Ses as an abstract class without incurring errors.
require 'lib/aws/aws-autoloader.php';
use Aws\Common\Enum\Region;
use Aws\Ses\SesClient;
abstract class simpleemail {
function sendSesEmail($to, $subject, $body, $bodyHtml){
try {
$client = SesClient::factory(array(
'key' => "",
'secret' => "",
'region' => Region::US_EAST_1
));
$send = $client->sendEmail(array(
'Source' => 'Name <no-reply#contact.com>',
'Destination' => array('ToAddresses' => array($to)),
'Message' => array('Subject' => array('Data' => $subject), 'Body' => array('Html' => array('Data' => $bodyHtml)))));
return true;
}
catch(Exception $e){
echo $e->getMessage();
return false;
}
}
}
Error Messages
Fatal error: Class 'Aes\Ses\SesClient' not found in ....
I have tried changing the use to require but then get:
require 'lib/aws/Aws/Common/Enum/Region.php';
require 'lib/aws/Aws/Ses/SesClient.php';
Fatal error: 'SesClient' not found in ...
Solution?
How can I use/require the files I need to get this working inside an abstract class?
This doesn't work:
abstract class simpleemail
{
public function sendSesEmail()
{
use Aws\Common\Enum\Region;
use Aws\Ses\SesClient;
//...
}
}
use statements are, basically, imports, that are processed at compile-time, so they can't be scoped. They have to move to the outer scope (outside of the class).
If you want to scope them, you'll have to either, manually require them, or use class_alias.
Check my answer to this question for more details. Even more details can, as ever, be found on php.net
Side-notes:
Please, follow the coding standards as described by PHP-FIG. They're not official, but Zend, Symfony... all major players, in fact, subscribe to them.
Please get in the habbit of always specifying the accessmodifiers (public, protected and private)
When creating instances like you do $client = SesClient::factory, assign them to a property, to only create the instance once. At the moment, each method call creates the same instance over and over again. That's bad
When using properties: include them in the class definition!
You're calling sendEmail on an instance, and assign the return value to $send. You don't check the return value, nor do you return it. Either ignore the return value, or return it, so it can be checked!
Never use require, use require_once if you have to. Using require can cause errors when executing the same block of code twice: redeclaring functions/classes. If time is of the essence, you could opt for require (as require_once causes more overhead), but you have to know what you're doing.
don't require an autoloader, use spl_autoloader_register
Remember: Abstract classes can't be instantiated, only their children... children can also override the methods declared in the abstract class. Declare critical methods as final
So, the answer:
use Aws\Common\Enum\Region;
use Aws\Ses\SesClient;
abstract class simpleemail
{
protected $client = null;
final public function sendSesEmail()
{
$client = $this->getClient();//lazy-loads the client instance
return $client->sendEmail(/* ... */);//return the result
}
//lazy-loader
protected function getClient()
{
if ($this->client === null)
{
$this->client = SesClient::factory(array(
'key' => "",
'secret' => "",
'region' => Region::US_EAST_1
));
}
return $this->client;
}
}