Test error handling of the PHP S3Client uploadAsync method - php

Method uploadAsync automatically decides and uploads object in single or multiple chunks. And we can expect 2 different kind of expectations S3Exception and S3MultipartUploadException in our $result['reason']. I'm trying to mock part of the S3 client to throw the exception, I have done this by MockHandler:
$s3->getHandlerList()->setHandler(new MockHandler([$result], null, null));
And the $result is:
new S3Exception("", new Command("mockCommand"), [
'code' => 'mockCode',
'response' => new Response(401)
])
Or either this:
new S3MultipartUploadException(new UploadState(['testid']))
Since the S3MultipartUploadException is not instance of AwsException I got this exception:
InvalidArgumentException: Expected an Aws\ResultInterface or Aws\Exception\AwsException.
How can I handle such scenario?

I have asked this on the project repo and they confirmed this is a bug https://github.com/aws/aws-sdk-php/issues/2143.

Related

Listing buckets in cloud storage project using PHP API

I'm trying to list out all (some, even) of the buckets in my storage project. If I know the name of a bucket, the "bucket" function will get the bucket. But I can't use "buckets" to list the buckets in my project:
$client = new StorageClient(
[
'projectId' => <my project id>,
'keyFile' => json_decode(file_get_contents(<my json file>))
]
);
$bucket_name = 'idx-mls-info-gs-ihouseprd.com';
$one_bucket = $client->bucket( $bucket_name );
print "GOT BUCKET: " . $one_bucket->name() . "\n";
// NOTE: this works
$prefix = 'idx-';
print "Getting buckets (prefix: $prefix)\n";
$buckets = $client->buckets( ['prefix' => $prefix] );
foreach ( $buckets as $bucket )
{
printf('Bucket: %s' . PHP_EOL, $bucket->name());
}
print "done with buckets"
// THIS DOES NOTHING
My service account has the "Storage Admin" role. I am perplexed.
NOTE: I am using PHP 5.6, in case that matters. Composer didn't have a problem installing the GCS library, so I assumed that was ok.
Ok, so I must be missing something. Using my test case of getting a single bucket, I have then used $one_bucket->object(), and successfully gotten an object. But if I try $one_bucket->objects(), I again get nothing. So the multiple case for entities in the GCS is not working for me, whether buckets or objects. Perhaps that's a clue.
Further information:
There is an exception when hitting the iterator (foreach $buckets as $bucket):
exception 'Exception' with message 'exception 'Google\Cloud\Core\Exception\ServiceException' with message '4096:Argument 2 passed to Google\Auth\CredentialsLoader::makeCredentials() must be of the type array, object given, called in /home/httpd/idxv3/vendor/google/cloud-core/src/RequestWrapperTrait.php on line 158 and defined in /home/httpd/idxv3/vendor/google/auth/src/CredentialsLoader.php on line 135' in /home/httpd/idxv3/vendor/google/cloud-core/src/RequestWrapper.php:362
Not sure why the authentication is causing problems.
I've found a link for your first question and I hope this helps guide you. It describes how to list the buckets in your project.
Ok, I found out what the problem was/is. It is in the creation of the storage client. My call the json_decode was missing a parameter. As in my original code, what gets passed into the constructor is a stdClass Object, which is not liked down in the depths of the code. Adding ", true" to the call to json_decode, what gets passed in is then an array, which is desired:
$client = new StorageClient(
[
'projectId' => <my project id>,
'keyFile' => json_decode(file_get_contents(<my json file>), true)
]
);
This fixes the problems I was having. Not sure why I didn't get errors earlier on, like in the constructor.

Laravel Exception handler logs also the request

I'm working on a Laravel 5.8 project, and I want to log inside the daily log file the request that has caused the exception, if some exception occurs.
I've tried this in the public function report(Exception $exception)
parent::render(request());
but it doesn't work at all. I've also tried this
\Log::error(json_encode(request()));
But it logs this
local.ERROR: {"attributes":{},"request":{},"query":{},"server":{},"files":{},"cookies":{},"headers":{}}
How should i do it? I need it in order to understand which request has caused that exception, and if it's possible, i need to log also other values, but i think that solved this, i can reuse the code to logs also the others
You can't just json_encode() the entire request as many properties are private/protected and require the use of getters for access. You will need to determine which values are important to you and build an appropriate response.
$response = [
'method' => request()->method(),
'url' => request()->url(),
'full_url' => request()->fullUrl(),
'data' => request()->all(),
];
Then you can pass your response array as a second parameter to the log handler, without needing to use json_encode().
\Log::error('Request details: ', $response);

S3 CreateEventSourceMapping triggers "Operation not found" error

I'm trying to create source maps so when someone uploads something inside the bucket, Lambda will trigger a previously created function, as described here
But, when I call this:
$fnName = 'my_function';
$s3->createEventSourceMapping([
//'BatchSize' => <integer>,
'Enabled' => true, //<boolean>
'EventSourceArn' => 'arn:aws:s3:::'.S3_BUCKET.'/'.$fnName, // REQUIRED
'FunctionName' => $fnName, // REQUIRED
'StartingPosition' => 'LATEST', //'TRIM_HORIZON|LATEST|AT_TIMESTAMP',
//'StartingPositionTimestamp' => <integer || string || DateTime>,
]);
I get the following error message Operation not found: CreateEventSourceMapping.
Some of those parameters are not active because according to the documentation found in https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-lambda-2015-03-31.html#createeventsourcemapping they have default values or aren't required (as far as I undestand)
At the moment I am using PHP SDK (ver 3.94.2). Is there something I am missing?
The createEventSourceMapping() method is a method on a Lambda client, not on an S3 client.

Laravel dispatch plain json on queue

I have 2 simple questions overall. Im currently looking into some event handling in Laravel and would like to use RabbitMQ as my event store. Therefor i installed this package to start with: https://github.com/php-enqueue/enqueue-dev
To get started i registered it and i am able to push messages on to RabbitMQ:
$job = (new Sendemail())->onQueue('email')->onConnection('interop');
dispatch($job);
The problem however is that Laravel pushes a certain format on the queue and i can't figure out how to change that. An example message would be:
{
"job":"Illuminate\\\\Queue\\\\CallQueuedHandler#call",
"data":{
"command":"O:29:\\"Acme\\Jobs\\FooJob\\":4:{s:11:\\"fooBar\\";s:7:\\"abc-123\\";s:5:\\"queue\\";N;s:5:\\"delay\\";N;s:6:\\"\\u0000*\\u0000job\\";N;}"
}
}
So the question is, how can i change this? The main reason on this is that the consumer side is not even a PHP application which also can not interpret the PHP serialized model. Therefor im looking for a way to push a plain JSON object instead.
From the other hand i would also like to understand how you could build a custom listener? For the listener the same thing happens. Laravel tries to read the method but when i push plain JSON this will never work. Isn't there a way to register a handler on a topic and do further handling of the payload of the message within the handler itself?
There is a simple way for your purpose:
First install this package for rabbit:
vladimir-yuldashev/laravel-queue-rabbitmq
and in controller:
Queue::connection('rabbitmq')->pushRaw('{you can generate a json format here}', 'queue_name');
you can generate a json and put in this command.
There's a laravel-queue library that works with the php-enqueue library you linked to make it compatible with Laravel's built in queue system that Florian mentioned.
By default, it will still use a serialized object, but I think that can be overridden. If you look in Queue.php, createObjectPayload() on line 130 in the core Laravel Framework, that's where the job is being serialized.
If you extend the Queue class in the laravel-queue library, you should be able to change createObjectPayload to look something like this:
protected function createObjectPayload($job, $queue)
{
$payload = $this->withCreatePayloadHooks($queue, [
'displayName' => $this->getDisplayName($job),
'job' => 'Illuminate\Queue\CallQueuedHandler#call',
'maxTries' => $job->tries ?? null,
'timeout' => $job->timeout ?? null,
'timeoutAt' => $this->getJobExpiration($job),
'data' => [
'commandName' => $job,
'command' => $job,
],
]);
return array_merge($payload, [
'data' => [
'commandName' => get_class($job),
'command' => json_encode(clone $job),
],
]);
}
That should JSON encode the job data instead of serializing it. You may even be able to remove the encoding altogether, as I think it's already JSON encoded somewhere up the chain.

Zend_Soap_Client error calling ASP.net web service: '...not set to an instance of an object'

I'm trying to use Zend_Soap_Client to communicate with an ASP.net web service. Here's my client call:
$client = new Zend_Soap_Client(null, array(
'location' => 'http://example.com/service.asmx',
'uri' => 'http://example.com/'
));
$user = new UserDetail();
$result = $client->UserDetails($user);
However this always gives me the error:
System.NullReferenceException: Object reference not set to an instance of an object. at Service.UserDetails(UserDetail UserDetail)
some googling revealed that this is quite a common problem. The most common solution seemed to be to pass the parameters as an array, so I tried:
$result = $client->UserDetails(array('UserDetail' => $user));
but this gave the same error. I also tried passing the params as a stdClass object, nesting the array in another with 'params' as the key, and a few other things but the error is always the same.
I have the ASP code for the web service itself, the relevant method is:
public Result UserDetails(UserDetail UserDetail) {
[some stuff]
Hashtable ht = new Hashtable();
ht = UserDetail.GenerateData();
}
the error is caused by the GenerateData() call.
I assume the UserDetails method is getting null instead of my object as the parameter, but I'm not sure how I should be calling the method, or how I can debug this further. The majority of the Zend_Soap_Client examples I've found seem to be using WSDL, which this service is not; not sure if that is relevant. Any help appreciated!
I eventually solved this with:
$userDetails = new UserDetails();
$userDetails->UserDetail = $user;
$client->UserDetails($userDetails);
it seems ASP.net expects (and returns) params to be nested in an object/array with the same name as the method being called.
If you have any possibility to change the asp.net code I'd suggest you try an implementation of the method UserDetails without parameters just to make sure that code isn't broken.
I would then create a consumer-method in asp.net, debug the http-request and see how the userdetail-object is serialized/broken down in array form. Then it's "just" a matter of creating a similar http request from php.

Categories