google-ads-php
google-ads-php copied to clipboard
`grpc` extension changes exceptions being thrown.
I was testing campaign creation and particular case: name duplication error. I found out that different exceptions are thrown in the runtime:
- when
grpc
is enabled I get\Google\Ads\GoogleAds\Lib\V2\GoogleAdsException
- when
grpc
is disabled I get\Google\ApiCore\ApiException
Not to say, that first case is much more preferable, as the exception contains useful extra information about the problem.
Good feedback. In case of an error, REST and gRPC transports do not return the metadata in the exact same format:
- gRPC
- REST
The current implementation of the GoogleAdsExceptionMiddleware
which translates the response including the metadata content into GoogleAdsException
is able to process the gRPC format only.
This is not a bug per se, flagging this issue as an enhancement request.
You are saying that there is more information about the error in the GoogleAdsException
(gRPC) compared to the ApiException
(REST): could you please provide concrete examples of this?
You are saying that there is more information about the error in the GoogleAdsException (gRPC) compared to the ApiException (REST): could you please provide concrete examples of this?
Yes, to get details about the error I do this:
/** @var \Google\Ads\GoogleAds\Lib\V2\GoogleAdsException $e */
$errorArray = json_decode($e->getGoogleAdsFailure()->serializeToJsonString(), true);
# ApiException won't let me do that
That gives me roughly this data which I can work with:
[{"message": "AdGroup with the same name already exists for the campaign.", "trigger": {"stringValue": "Optimized AdGroup #5db9e837828d3"}, "location": {"fieldPathElements": [{"index": "6", "fieldName": "operations"}, {"fieldName": "create"}, {"fieldName": "name"}]}, "errorCode": {"adGroupError": "DUPLICATE_ADGROUP_NAME"}}]
Why do I serialize errors to JSON and deserialize it back? Because this is the only way I figured out to get the actual error code and details about the error. Is there a better way?
Thanks for sharing, ApiException
still provides methods like getMetadata
and gives access to raw response data. I believe it contains the same information about the error(s) even if not as well structured as you have with a GoogleAdsException
.
To be sure, do you mind executing a var_dump
of both exception types that return with the same error(s)? I would be curious to see if you can spot any difference of information (and not only structural).
Nice! Can you show an example I can use, say, for getting errorCode
value of the error?
Sure, you could print the error codes using something like this:
try {
// Does some API call(s)
} catch (ApiException $apiException) {
foreach($apiException->getMetadata() as $metadata) {
if(
array_key_exists('@type', $metadata) &&
$metadata['@type'] == 'type.googleapis.com/google.ads.googleads.v2.errors.GoogleAdsFailure' &&
array_key_exists('errors', $metadata)
) {
foreach($metadata['errors'] as $error) {
if(array_key_exists('errorCode', $error)) {
foreach($error['errorCode'] as $errorCode) {
print("Error Code: " . $errorCode);
}
}
}
}
}
}
HIH
Thank you! Let me play with that example. Is there plans to develop a more intuitive way to access error?
Tried this test, but it gave me no error codes.
$budget = new CampaignBudget([
'name' => new StringValue([
'value' => sprintf('Budget for campaign [%s]', uniqid())
]),
'delivery_method' => BudgetDeliveryMethod::STANDARD,
'amount_micros' => new Int64Value(['value' => -100]) // wrong value cuases failure
]);
$op = new CampaignBudgetOperation();
$op->setCreate($budget);
$client = $this->app->make(GoogleAdsClientBuilder::class)->build($userId, $googleClientId);
$campaignBudgetServiceClient = $client->getCampaignBudgetServiceClient();
try {
$campaignBudgetServiceClient->mutateCampaignBudgets($googleClientId, [$op]);
} catch (ApiException $e) {
foreach($e->getMetadata() as $metadata) {
if(
array_key_exists('@type', $metadata) &&
$metadata['@type'] == 'type.googleapis.com/google.ads.googleads.v2.errors.GoogleAdsFailure' &&
array_key_exists('errors', $metadata)
) {
foreach($metadata['errors'] as $error) {
if(array_key_exists('errorCode', $error)) {
foreach($error['errorCode'] as $errorCode) {
print("Error Code: " . $errorCode);
}
}
}
}
}
}
The API call logged:
Request
-------
Method Name: /google.ads.googleads.v2.services.CampaignBudgetService/MutateCampaignBudgets
Host: googleads.googleapis.com
Headers: {
"x-goog-api-client": "gl-php\/7.2.23 gapic\/ gax\/1.2.0 grpc\/1.23.1",
"x-goog-request-params": "customer_id=8530332597",
"developer-token": "REDACTED"
}
Request: {"customerId":"8530332597","operations":[{"create":{"name":"Budget for campaign [5dc173920a363]","amountMicros":"-100","deliveryMethod":"STANDARD"}}]}
Response
-------
Headers: {
"request-id": "6OL1RE-W0C1FZgRWStM1eQ",
"date": "Tue, 05 Nov 2019 13:05:24 GMT",
"alt-svc": "quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000"
}
Fault
-------
Status code: 3
Details: Request contains an invalid argument.
Failure: {"errors":[{"errorCode":{"rangeError":"TOO_LOW"},"message":"Too low.","location":{"fieldPathElements":[{"fieldName":"operations","index":"0"},{"fieldName":"create"},{"fieldName":"amount_micros"}]}}]}
PHP extensions:
php -m
Starting instaon_mysql ... done
[PHP Modules]
bcmath
Core
ctype
curl
date
dom
exif
fileinfo
filter
ftp
gd
grpc
hash
iconv
imap
json
libxml
mbstring
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
protobuf
readline
Reflection
session
SimpleXML
soap
sockets
sodium
SPL
sqlite3
standard
tokenizer
xdebug
xml
xmlreader
xmlwriter
zip
zlib
[Zend Modules]
Xdebug
The main condition I drafted might be overkill, did you try to simplify keeping only:
array_key_exists('errors', $metadata)