Skip to content

Instantly share code, notes, and snippets.

@Vijaysinh
Created January 21, 2021 08:38
Show Gist options
  • Save Vijaysinh/028cce3e826a53bed922c18f3e2dd267 to your computer and use it in GitHub Desktop.
Save Vijaysinh/028cce3e826a53bed922c18f3e2dd267 to your computer and use it in GitHub Desktop.
Adwords FieldMasks file
<?php
/**
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\Ads\GoogleAds\Util;
use Google\ApiCore\GPBLabel;
use Google\ApiCore\GPBType;
use Google\ApiCore\Serializer;
use Google\Protobuf\BoolValue;
use Google\Protobuf\BytesValue;
use Google\Protobuf\Descriptor;
use Google\Protobuf\DescriptorPool;
use Google\Protobuf\DoubleValue;
use Google\Protobuf\FieldDescriptor;
use Google\Protobuf\FieldMask;
use Google\Protobuf\FloatValue;
use Google\Protobuf\Int32Value;
use Google\Protobuf\Int64Value;
use Google\Protobuf\Internal\Message;
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\StringValue;
use Google\Protobuf\UInt32Value;
use Google\Protobuf\UInt64Value;
use InvalidArgumentException;
use UnexpectedValueException;
/** Utility methods for working with field masks.*/
class FieldMasks
{
private static $WRAPPER_TYPES = [
DoubleValue::class,
FloatValue::class,
Int64Value::class,
UInt64Value::class,
Int32Value::class,
UInt32Value::class,
BoolValue::class,
StringValue::class,
BytesValue::class,
];
private static $descriptorPool = null;
/**
* Compares two protobuf message objects and computes a FieldMask based on the differences
* between the two objects. This method can be used to help construct the FieldMask object
* required by some API methods.
*
* Example usage:
* ```
* $originalFoo = new Foo();
* $updatedFoo = (new Foo())->setBar("new-bar");
* $fieldMask = FieldMasks::compare($originalFoo, $updatedFoo);
* $client->updateFoo($updatedFoo, $fieldMask);
* ```
*
* @param Message $original the original protobuf message object.
* @param Message $modified the modified protobuf message object.
* @return FieldMask a FieldMask reflecting the changes between the original and modified
* objects.
*/
public static function compare(Message $original, Message $modified)
{
if (get_class($original) !== get_class($modified)) {
throw new InvalidArgumentException(sprintf(
'Both input messages must be of the same type, got '
. 'original: %s, modified: %s',
get_class($original),
get_class($modified)
));
}
$paths = [];
self::buildPaths($paths, '', $original, $modified);
return (new FieldMask())->setPaths($paths);
}
/**
* Computes a FieldMask based on all of the fields of message that have been set.
*
* For a message object `Foo`, FieldMasks::allSetFieldsOf($foo) is equivalent to
* FieldMasks::compare(new Foo(), $foo)
*
* @param Message $message a protobuf message object.
* @return FieldMask a FieldMask reflecting all fields set in $message.
*/
public static function allSetFieldsOf(Message $message)
{
$messageClass = get_class($message);
$defaultMessage = new $messageClass();
return self::compare($defaultMessage, $message);
}
/**
* Returns true if the provided repeated field is null or doesn't have any members.
*
* @param RepeatedField|null $field the repeated field to check
* @return bool true if the field is empty
*/
private static function isEmpty(?RepeatedField $field): bool
{
return is_null($field) || count($field) === 0;
}
/**
* Builds the paths to the fields that are different between original and modified message.
*
* @param array $paths the resulting paths from the computation
* @param string $currentField the current field name
* @param Message|null $original the original message
* @param Message|null $modified the modified message
*/
private static function buildPaths(
array &$paths,
$currentField,
?Message $original,
?Message $modified
) {
if (is_null($original) && is_null($modified)) {
return;
}
$descriptor = is_null($original)
? self::getDescriptorForMessage($modified) : self::getDescriptorForMessage($original);
for ($i = 0; $i < $descriptor->getFieldCount(); $i++) {
$fieldDescriptor = $descriptor->getField($i);
$fieldName = self::getFieldName($currentField, $fieldDescriptor);
$getter = Serializer::getGetter($fieldDescriptor->getName());
$originalValue = is_null($original) ? null : $original->$getter();
$modifiedValue = is_null($modified) ? null : $modified->$getter();
if (self::isFieldRepeated($fieldDescriptor)) {
// For protobuf objects, the repeated fields that have no members are semantically
// the same as the ones that are null. If both are empty because of any cases, we
// will not add their field name to the path, because nothing has changed.
if (
!((self::isEmpty($originalValue) && self::isEmpty($modifiedValue))
|| $originalValue == $modifiedValue)
) {
$paths[] = $fieldName;
}
} else {
// Evaluates if the field value changed.
// If both $original and $modified are null, this function will return at the
// beginning.
$hasser = self::getHasser($fieldDescriptor->getName());
$hasValueChanged =
// In most cases, both hassers are available so we can compare them.
(
!is_null($original) && method_exists($original, $hasser)
&& !is_null($modified) && method_exists($modified, $hasser)
&& $original->$hasser() != $modified->$hasser()
)
// In the special case where $original is not set, we can check if $modified
// has a value set. This ensures a better performance than a deep comparison
// but also covers the special case of having a `false` boolean value in
// $modifiedValue.
|| (is_null($original) && method_exists($modified, $hasser)
&& $modified->$hasser())
// If hassers are not enough to determine if the value changed, a deep
// comparison is used at last resort (lower performance).
|| $originalValue != $modifiedValue;
// Handles based on the field type.
switch ($fieldDescriptor->getType()) {
case GPBType::MESSAGE:
if ($hasValueChanged) {
if (self::isWrapperType($fieldDescriptor->getMessageType())) {
// For wrapper types, just emit the field name.
$paths[] = $fieldName;
} elseif (is_null($modifiedValue)) {
// Just emit the deleted field name.
$paths[] = $fieldName;
} else {
// Recursively compare to find different values.
self::buildPaths(
$paths,
$fieldName,
$originalValue,
$modifiedValue
);
}
}
break;
case GPBType::DOUBLE:
case GPBType::FLOAT:
case GPBType::INT64:
case GPBType::UINT64:
case GPBType::INT32:
case GPBType::FIXED64:
case GPBType::FIXED32:
case GPBType::BOOL:
case GPBType::STRING:
case GPBType::BYTES:
case GPBType::UINT32:
case GPBType::ENUM:
case GPBType::SFIXED32:
case GPBType::SFIXED64:
case GPBType::SINT32:
case GPBType::SINT64:
// Handle all supported types except MESSAGE.
if ($hasValueChanged) {
$paths[] = $fieldName;
}
break;
default:
throw new InvalidArgumentException("Unexpected type "
. $fieldDescriptor->getType() . " encountered for field $fieldName");
}
}
}
}
/**
* Gets the value of the specified field of the specified object.
*
* @param string $fieldMaskPath the field mask path
* @param Message $object the object whose field value to be get
* @return mixed the value of the specified field of the specified object
*/
public static function getFieldValue(string $fieldMaskPath, Message $object)
{
$fieldMaskParts = explode('.', $fieldMaskPath);
$descriptor = self::getDescriptorForMessage($object);
foreach ($fieldMaskParts as $part) {
$fieldValue = null;
for ($i = 0; $i < $descriptor->getFieldCount(); $i++) {
$fieldDescriptor = $descriptor->getField($i);
if ($fieldDescriptor->getName() !== $part) {
continue;
}
$getter = Serializer::getGetter($fieldDescriptor->getName());
$fieldValue = $object->$getter();
if ($fieldDescriptor->getType() === GPBType::MESSAGE) {
$object = $fieldValue;
if (is_null($object)) {
return null;
// throw new UnexpectedValueException(
// 'The object to get its field value is null. '
// . 'This could happen when the passed field mask path points to the '
// . ' wrong place, where the nested message is not set.'
// );
}
$descriptor = self::getDescriptorForMessage($fieldValue);
}
// There is only one field that matches the field mask part, so no need to loop
// when the field is found.
break;
}
}
if (!isset($fieldValue)) {
throw new UnexpectedValueException('The field value cannot be obtained because the '
. 'given field mask path is unrecognized.');
}
return $fieldValue;
}
/**
* @param string $currentField the current field name
* @param FieldDescriptor $fieldDescriptor the field descriptor to get the field name
* @return string the field name based on the current field name and provided field descriptor
*/
private static function getFieldName($currentField, FieldDescriptor $fieldDescriptor)
{
if (empty($currentField)) {
return $fieldDescriptor->getName();
} else {
return "$currentField." . $fieldDescriptor->getName();
}
}
/**
* @param Message $message the message to get its descriptor
* @return Descriptor the descriptor of the message
*/
private static function getDescriptorForMessage(Message $message)
{
if (is_null(self::$descriptorPool)) {
self::$descriptorPool = DescriptorPool::getGeneratedPool();
}
return self::$descriptorPool->getDescriptorByClassName(get_class($message));
}
/**
* @param FieldDescriptor $fieldDescriptor the field descriptor to check if it's repeated
* @return bool true if the field descriptor is repeated
*/
private static function isFieldRepeated(FieldDescriptor $fieldDescriptor)
{
return $fieldDescriptor->getLabel() === GPBLabel::REPEATED;
}
/**
* @param Descriptor $descriptor the descriptor to check
* @return bool true if this is a wrapper type
*/
private static function isWrapperType(Descriptor $descriptor)
{
return in_array($descriptor->getClass(), self::$WRAPPER_TYPES);
}
// TODO: We can remove this function when it's supported in google/gax-php:
// https://github.com/googleapis/gax-php/issues/285
/**
* @param string $name
* @return string the name of hasser function
*/
private static function getHasser(string $name)
{
return 'has' . ucfirst(Serializer::toCamelCase($name));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment