google 广告系列 、 预算、 账号列表 2

This commit is contained in:
hgc 2024-12-16 21:09:23 +08:00
parent d771a95c42
commit ab8e40d9cf
6 changed files with 753 additions and 0 deletions

251
app/util/ArgumentNames.php Normal file
View File

@ -0,0 +1,251 @@
<?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 app\util;
/**
* Provides argument name constants for examples.
*/
final class ArgumentNames
{
public const ACCESS_ROLE = 'accessRole';
public const ADJUSTMENT_DATE_TIME = 'adjustmentDateTime';
public const ADJUSTMENT_TYPE = 'adjustmentType';
public const ADVERTISER_UPLOAD_DATE_TIME = 'advertiserUploadDateTime';
public const AD_ID = 'adId';
public const AD_GROUP_ID = 'adGroupId';
public const AD_GROUP_IDS = 'adGroupIds';
public const AD_PERSONALIZATION_CONSENT = 'adPersonalizationConsent';
public const AD_USER_DATA_CONSENT = 'adUserDataConsent';
public const ASSET_GROUP_ID = 'assetGroupId';
public const ATTRIBUTE_VALUE = 'attributeValue';
public const AUDIENCE_ID = 'audienceId';
public const BASE_CAMPAIGN_ID = 'baseCampaignId';
public const BID_MODIFIER_VALUE = 'bidModifierValue';
public const BILLING_SETUP_ID = 'billingSetupId';
public const BRIDGE_MAP_VERSION_ID = 'bridgeMapVersionId';
public const BUSINESS_ACCOUNT_IDENTIFIER = 'businessAccountIdentifier';
public const BUSINESS_PROFILE_LOCATION = 'businessProfileLocation';
public const BUSINESS_NAME = 'businessName';
public const CALL_START_DATE_TIME = 'callStartDateTime';
public const CALLER_ID = 'callerId';
public const CALLOUT_TEXT = 'calloutText';
public const CAMPAIGN_BUDGET_ID = 'campaignBudgetId';
public const CAMPAIGN_EXPERIMENT_ID = 'campaignExperimentId';
public const CAMPAIGN_ID = 'campaignId';
public const CAMPAIGN_IDS = 'campaignIds';
public const CARRIER_COUNTRY_CODE = 'carrierCountryCode';
public const CHAIN_ID = 'chainId';
public const CHECK_IN_DAY_CRITERION_ID = 'checkInDayCriterionId';
public const CONVERSION_ACTION_ID = 'conversionActionId';
public const CONVERSION_ACTION_IDS = 'conversionActionIds';
public const CONVERSION_CUSTOM_VARIABLE_ID = 'conversionCustomVariableId';
public const CONVERSION_CUSTOM_VARIABLE_VALUE = 'conversionCustomVariableValue';
public const CONVERSION_DATE_TIME = 'conversionDateTime';
public const CONVERSION_RATE_MODIFIER = "conversionRateModifier";
public const CONVERSION_VALUE = 'conversionValue';
public const COUNTRY_CODE = 'countryCode';
public const CPC_BID_CEILING_MICRO_AMOUNT = 'cpcBidCeilingMicroAmount';
public const CPC_BID_MICRO_AMOUNT = 'cpcBidMicroAmount';
public const CRITERION_ID = 'criterionId';
public const CURRENCY_CODE = 'currencyCode';
public const CUSTOMER_ID = 'customerId';
public const CUSTOMIZER_ATTRIBUTE_NAME = 'customizerAttributeName';
public const CUSTOM_KEY = 'customKey';
public const DRAFT_ID = 'draftId';
public const EMAIL_ADDRESS = 'emailAddress';
public const END_DATE_TIME = "endDateTime";
public const EXTERNAL_ID = 'externalId';
public const FEED_ID = 'feedId';
public const FEED_ITEM_ID = 'feedItemId';
public const FEED_ITEM_IDS = 'feedItemIds';
public const FEED_ITEM_SET_ID = 'feedItemSetId';
public const FINAL_URL = 'finalUrl';
public const FLIGHT_PLACEHOLDER_FIELD_NAME = 'flightPlaceholderFieldName';
public const FREE_FORM_KEYWORD_TEXT = 'freeFormKeywordText';
public const GBRAID = 'gbraid';
public const GCLID = 'gclid';
public const GEO_TARGET_CONSTANT_ID = 'geoTargetConstantId';
public const BUSINESS_PROFILE_ACCESS_TOKEN = 'businessProfileAccessToken';
public const BUSINESS_PROFILE_EMAIL = 'businessProfileEmail';
public const HOTEL_CENTER_ACCOUNT_ID = 'hotelCenterAccountId';
public const IMAGE_ASSET_ID = 'imageAssetId';
public const ITEM_ID = 'itemId';
public const KEYWORD_PLAN_ID = 'keywordPlanId';
public const KEYWORD_TEXT = 'keywordText';
public const KEYWORD_TEXTS = 'keywordTexts';
public const LABEL_ID = "labelId";
public const LANGUAGE_CODE = 'languageCode';
public const LANGUAGE_ID = 'languageId';
public const LANGUAGE_NAME = 'languageName';
public const LOCALE = 'locale';
public const LOCATION_ID = 'locationId';
public const LOCATION_IDS = 'locationIds';
public const LOCATION_NAMES = 'locationNames';
public const LOGIN_CUSTOMER_ID = 'loginCustomerId';
public const MANAGER_CUSTOMER_ID = 'managerCustomerId';
public const MARKETING_IMAGE_ASSET_ID = 'marketingImageAssetId';
public const MERCHANT_CENTER_ACCOUNT_ID = 'merchantCenterAccountId';
public const NAME_PREFIX = 'namePrefix';
public const OFFLINE_USER_DATA_JOB_ID = 'offlineUserDataJobId';
public const OFFLINE_USER_DATA_JOB_TYPE = 'offlineUserDataJobType';
public const OMIT_UNSELECTED_RESOURCE_NAMES = 'omitUnselectedResourceNames';
public const ORDER_ID = 'orderId';
public const OUTPUT_FILE_PATH = 'outputFilePath';
public const PAGE_URL = 'pageUrl';
public const PARTNER_ID = 'partnerId';
public const PAYMENTS_ACCOUNT_ID = 'paymentsAccountId';
public const PAYMENTS_PROFILE_ID = 'paymentsProfileId';
public const PERCENT_CPC_BID_MICRO_AMOUNT = 'percentCpcBidMicroAmount';
public const PHONE_COUNTRY = 'phoneCountry';
public const PHONE_NUMBER = 'phoneNumber';
public const PLACE_ID = 'placeId';
public const RECOMMENDATION_ID = 'recommendationId';
public const RESTATEMENT_VALUE = 'restatementValue';
public const CREATE_DEFAULT_LISTING_GROUP = 'createDefaultListingGroup';
public const DELETE_EXISTING_FEEDS = 'deleteExistingFeeds';
public const REPLACE_EXISTING_TREE = 'replaceExistingTree';
public const QUANTITY = 'quantity';
public const RUN_JOB = 'runJob';
public const SALES_COUNTRY = 'salesCountry';
public const SITELINK_TEXT = 'sitelinkText';
public const SQUARE_MARKETING_IMAGE_ASSET_ID = 'squareMarketingImageAssetId';
public const START_DATE_TIME = "startDateTime";
public const THINGS_TO_DO_CENTER_ACCOUNT_ID = 'thingsToDoCenterAccountId';
public const USER_AGENT = 'userAgent';
public const USER_LIST_ID = 'userListId';
public const USER_LIST_IDS = 'userListIds';
public const WBRAID = 'wbraid';
public static $ARGUMENTS_TO_DESCRIPTIONS = [
self::ACCESS_ROLE => 'The user access role',
self::ADJUSTMENT_DATE_TIME => 'The adjustment date time',
self::ADJUSTMENT_TYPE => 'The adjustment type',
self::ADVERTISER_UPLOAD_DATE_TIME => 'The advertiser upload date time',
self::AD_ID => 'The ad ID',
self::AD_GROUP_ID => 'The ad group ID',
self::AD_GROUP_IDS => 'The ad group IDs',
self::AD_PERSONALIZATION_CONSENT => 'The ad personalization consent, e.g., GRANTED',
self::AD_USER_DATA_CONSENT => 'The ad user data consent, e.g., GRANTED',
self::ASSET_GROUP_ID => 'The asset group ID',
self::ATTRIBUTE_VALUE => 'The attribute value',
self::AUDIENCE_ID => 'The audience ID',
self::BASE_CAMPAIGN_ID => 'The base campaign ID',
self::BID_MODIFIER_VALUE => 'The bid modifier value',
self::BILLING_SETUP_ID => 'The billing setup ID',
self::BRIDGE_MAP_VERSION_ID
=> 'The version of partner IDs to be used for store-sale uploads',
self::BUSINESS_ACCOUNT_IDENTIFIER => 'The account number of the Business Profile account',
self::BUSINESS_PROFILE_LOCATION => 'The Business Profile location resource name',
self::BUSINESS_NAME => 'The Business Profile business name',
self::CALL_START_DATE_TIME => 'The call start date time',
self::CALLER_ID => 'The caller ID',
self::CALLOUT_TEXT => 'The callout text',
self::CAMPAIGN_BUDGET_ID => 'The campaign budget ID',
self::CAMPAIGN_EXPERIMENT_ID => 'The campaign experiment ID',
self::CAMPAIGN_ID => 'The campaign ID',
self::CAMPAIGN_IDS => 'The campaign IDs',
self::CARRIER_COUNTRY_CODE => 'The carrier country code',
self::CHAIN_ID => 'The retail chain ID',
self::CHECK_IN_DAY_CRITERION_ID => 'The hotel check-in day criterion ID',
self::CONVERSION_ACTION_ID => 'The conversion action ID',
self::CONVERSION_ACTION_IDS => 'The conversion action IDs',
self::CONVERSION_CUSTOM_VARIABLE_ID => 'The conversion custom variable ID',
self::CONVERSION_CUSTOM_VARIABLE_VALUE => 'The conversion custom variable value',
self::CONVERSION_DATE_TIME => 'The conversion date time',
self::CONVERSION_RATE_MODIFIER => 'The conversion rate modifier',
self::CONVERSION_VALUE => 'The conversion value',
self::COUNTRY_CODE => 'The country code',
self::CPC_BID_CEILING_MICRO_AMOUNT => 'The CPC bid ceiling micro amount',
self::CPC_BID_MICRO_AMOUNT => 'The CPC bid micro amount',
self::CRITERION_ID => 'The criterion ID',
self::CURRENCY_CODE => 'The currency code',
self::CUSTOMER_ID => 'The customer ID without dashes',
self::CUSTOMIZER_ATTRIBUTE_NAME => 'The customizer attribute name',
self::CUSTOM_KEY => 'The custom key',
self::DRAFT_ID => 'The draft ID',
self::EMAIL_ADDRESS => 'The email address',
self::END_DATE_TIME => 'The end date time',
self::EXTERNAL_ID => 'The external ID',
self::FEED_ID => 'The feed ID',
self::FEED_ITEM_ID => 'The feed item ID',
self::FEED_ITEM_IDS => 'The feed item IDs',
self::FEED_ITEM_SET_ID => 'The feed item set ID',
self::FINAL_URL => 'The final URL',
self::FLIGHT_PLACEHOLDER_FIELD_NAME => 'The flight placeholder field name',
self::FREE_FORM_KEYWORD_TEXT => 'The free-form keyword text',
self::GBRAID => 'The GBRAID identifier for an iOS app conversion',
self::GCLID => 'The Google Click ID',
self::GEO_TARGET_CONSTANT_ID => 'The geo target constant ID',
self::BUSINESS_PROFILE_ACCESS_TOKEN => 'The access token used for uploading Business Profile '
. 'location feed data',
self::BUSINESS_PROFILE_EMAIL => 'The email address associated with the Business Profile account',
self::HOTEL_CENTER_ACCOUNT_ID => 'The hotel center account ID',
self::IMAGE_ASSET_ID => 'The image asset ID',
self::ITEM_ID => 'The item ID',
self::KEYWORD_PLAN_ID => 'The keyword plan ID',
self::KEYWORD_TEXT => 'The keyword text',
self::KEYWORD_TEXTS => 'The list of keyword texts',
self::LABEL_ID => 'The label ID',
self::LANGUAGE_CODE => 'The language code',
self::LANGUAGE_ID => 'The language ID',
self::LANGUAGE_NAME => 'The language name',
self::LOCALE => 'The locale',
self::LOCATION_ID => 'The location ID',
self::LOCATION_IDS => 'The list of location IDs',
self::LOCATION_NAMES => 'The list of location names',
self::LOGIN_CUSTOMER_ID => 'The login customer ID',
self::MANAGER_CUSTOMER_ID => 'The manager customer ID',
self::MARKETING_IMAGE_ASSET_ID => 'The ID of marketing image asset',
self::MERCHANT_CENTER_ACCOUNT_ID => 'The Merchant center account ID',
self::NAME_PREFIX => 'The name prefix',
self::OFFLINE_USER_DATA_JOB_ID => 'The offline user data job ID',
self::OFFLINE_USER_DATA_JOB_TYPE => 'The offline user data job type',
self::OMIT_UNSELECTED_RESOURCE_NAMES => 'Whether to omit unselected resource names',
self::ORDER_ID => 'The order ID',
self::OUTPUT_FILE_PATH => 'The output file path',
self::PAGE_URL => 'The page URL',
self::PARTNER_ID => 'The partner ID',
self::PAYMENTS_ACCOUNT_ID => 'The payments account ID',
self::PAYMENTS_PROFILE_ID => 'The payments profile ID',
self::PERCENT_CPC_BID_MICRO_AMOUNT =>
'The CPC bid micro amount for the Percent CPC bidding strategy',
self::PHONE_COUNTRY => 'The phone country',
self::PHONE_NUMBER => 'The phone number',
self::PLACE_ID => 'The place ID',
self::RECOMMENDATION_ID => 'The recommendation ID',
self::RESTATEMENT_VALUE => 'The restatement value',
self::CREATE_DEFAULT_LISTING_GROUP =>
'Whether it should create a default listing group',
self::DELETE_EXISTING_FEEDS =>
'Whether it should delete the existing feeds',
self::REPLACE_EXISTING_TREE =>
'Whether it should replace the existing listing group tree on an ad group/asset group',
self::QUANTITY => 'The quantity',
self::RUN_JOB => 'Whether it should run the offline user data job',
self::SALES_COUNTRY => 'The sales country',
self::SITELINK_TEXT => 'The sitelink text',
self::SQUARE_MARKETING_IMAGE_ASSET_ID => 'The ID of square marketing image asset',
self::START_DATE_TIME => 'The start date time',
self::USER_AGENT => 'The user agent',
self::USER_LIST_ID => 'The user list ID',
self::USER_LIST_IDS => 'The user list IDs',
self::THINGS_TO_DO_CENTER_ACCOUNT_ID => 'The Things to Do Center account ID',
self::WBRAID => 'The WBRAID identifer for an iOS web conversion'
];
}

110
app/util/ArgumentParser.php Normal file
View File

@ -0,0 +1,110 @@
<?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 app\util;
use GetOpt\ArgumentException;
use GetOpt\GetOpt;
use InvalidArgumentException;
/**
* Wraps `GetOpt` and normalizes arguments parsed by it.
*
* @see GetOpt
*/
class ArgumentParser
{
/**
* Parses any arguments specified via the command line. For those in the provided argument
* names that are not passed, provides null values instead.
*
* @param array $argumentNames the associative array of argument names to their types
* @return array the argument names to their values
*/
public function parseCommandArguments(array $argumentNames)
{
$getOpt = new GetOpt();
$normalizedOptions = [];
$numRequiredArguments = 0;
$getOpt->addOption(['h', 'help', GetOpt::NO_ARGUMENT, 'Show this help and quit']);
foreach ($argumentNames as $argumentName => $argumentType) {
$normalizedOptions[$argumentName] = null;
// Adds an option for an argument using a long option name only.
$getOpt->addOption(
[
null,
$argumentName,
$argumentType,
ArgumentNames::$ARGUMENTS_TO_DESCRIPTIONS[$argumentName]
]
);
if ($argumentType === GetOpt::REQUIRED_ARGUMENT) {
$numRequiredArguments++;
}
}
// Parse arguments and catch exceptions.
try {
$getOpt->process();
} catch (ArgumentException $exception) {
// When there are any errors regarding arguments, such as invalid argument names, or
// specifying required arguments but not providing values, 'ArgumentException' will
// be thrown. Show the help text in these cases.
echo PHP_EOL . $getOpt->getHelpText();
throw $exception;
}
// Show help text when requested.
if (!is_null($getOpt->getOption('help'))) {
$this->printHelpMessageAndExit($getOpt);
// Help text is printed, so no arguments are passed. The below line is reached only
// in tests.
return [];
}
$numPassedRequiredArguments = 0;
foreach ($getOpt->getOptions() as $optionName => $optionValue) {
if ($argumentNames[$optionName] === GetOpt::REQUIRED_ARGUMENT) {
$numPassedRequiredArguments++;
}
$normalizedOptions[$optionName] = $optionValue;
}
// Don't allow the case when optional arguments are passed, but required arguments are not.
if (
count($getOpt->getOptions()) > 0
&& $numPassedRequiredArguments !== $numRequiredArguments
) {
echo PHP_EOL . $getOpt->getHelpText();
throw new InvalidArgumentException(
'All required arguments must be specified.' . PHP_EOL
);
}
return $normalizedOptions;
}
/**
* Print the help message and exit the program.
*
* @param GetOpt $getOpt the GetOpt object to print its help text
*/
public function printHelpMessageAndExit(GetOpt $getOpt)
{
echo PHP_EOL . $getOpt->getHelpText();
exit;
}
}

211
app/util/Feeds.php Normal file
View File

@ -0,0 +1,211 @@
<?php
/**
* Copyright 2019 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 app\util;
use Google\Ads\GoogleAds\Lib\V18\GoogleAdsClient;
use Google\Ads\GoogleAds\V18\Enums\FlightPlaceholderFieldEnum\FlightPlaceholderField;
use Google\Ads\GoogleAds\V18\Enums\RealEstatePlaceholderFieldEnum\RealEstatePlaceholderField;
use Google\Ads\GoogleAds\V18\Resources\FeedAttribute;
use Google\Ads\GoogleAds\V18\Resources\FeedItem;
use Google\Ads\GoogleAds\V18\Resources\FeedItemAttributeValue;
use Google\Ads\GoogleAds\V18\Services\GoogleAdsRow;
use Google\Ads\GoogleAds\V18\Services\SearchGoogleAdsRequest;
/**
* Utilities that are shared between code examples related to feeds.
*/
final class Feeds
{
/**
* Retrieves a feed item and its attribute values given a resource name.
*
* @param string $feedItemResourceName the feed item resource name
* @param int $customerId the customer ID
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @return FeedItem the feed item
*/
public static function feedItemFor(
string $feedItemResourceName,
int $customerId,
GoogleAdsClient $googleAdsClient
) {
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
// Constructs the query to get the feed item with attribute values.
$query = "SELECT feed_item.attribute_values FROM feed_item"
. " WHERE feed_item.resource_name = '$feedItemResourceName'";
// Issues a search request.
$response =
$googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query));
// Returns the feed item attribute values, which belongs to the first item. We can ensure
// it belongs to the first one because we specified the feed item resource name in the
// query.
return $response->getIterator()->current()->getFeedItem();
}
/**
* Gets the index of the target feed item attribute value. This is needed to specify which feed
* item attribute value will be updated in the given feed item.
*
* @param FeedItemAttributeValue $targetFeedItemAttributeValue the new feed item attribute value
* that will be updated
* @param FeedItem $feedItem the feed item that will be updated. It should be populated with
* the current attribute values
* @return int the attribute index
*/
public static function attributeIndexFor(
FeedItemAttributeValue $targetFeedItemAttributeValue,
FeedItem $feedItem
) {
$attributeIndex = -1;
// Loops through attribute values to find the index of the feed item attribute value to
// update.
foreach ($feedItem->getAttributeValues() as $feedItemAttributeValue) {
/** @var FeedItemAttributeValue $feedItemAttributeValue */
$attributeIndex++;
// Checks if the current feedItemAttributeValue is the one we are updating
if (
$feedItemAttributeValue->getFeedAttributeId()
=== $targetFeedItemAttributeValue->getFeedAttributeId()
) {
break;
}
}
if ($attributeIndex === -1) {
throw new \InvalidArgumentException(
'No matching feed attribute for feed item attribute ID: '
. $targetFeedItemAttributeValue->getFeedAttributeId()
);
}
return $attributeIndex;
}
/**
* Retrieves the place holder fields to feed attributes map for a flights feed.
* See FlightPlaceholderField.php for all available placeholder field values.
*
* @see Feeds::placeholderFieldsMapFor()
*
* @param string $feedResourceName the feed resource name to get the attributes from
* @param int $customerId the customer ID
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @return array the map from placeholder fields to feed attributes
*/
public static function flightPlaceholderFieldsMapFor(
string $feedResourceName,
int $customerId,
GoogleAdsClient $googleAdsClient
) {
return self::placeholderFieldsMapFor(
$feedResourceName,
$customerId,
$googleAdsClient,
[
'Flight Description' => FlightPlaceholderField::FLIGHT_DESCRIPTION,
'Destination ID' => FlightPlaceholderField::DESTINATION_ID,
'Flight Price' => FlightPlaceholderField::FLIGHT_PRICE,
'Flight Sale Price' => FlightPlaceholderField::FLIGHT_SALE_PRICE,
'Final URLs' => FlightPlaceholderField::FINAL_URLS
]
);
}
/**
* Retrieves the place holder fields to feed attributes map for a real estate feed.
* See RealEstatePlaceholderField.php for all available placeholder field values.
*
* @see Feeds::placeholderFieldsMapFor()
*
* @param string $feedResourceName the feed resource name to get the attributes from
* @param int $customerId the customer ID
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @return array the map from placeholder fields to feed attributes
*/
// [START add_real_estate_feed]
public static function realEstatePlaceholderFieldsMapFor(
string $feedResourceName,
int $customerId,
GoogleAdsClient $googleAdsClient
) {
return self::placeholderFieldsMapFor(
$feedResourceName,
$customerId,
$googleAdsClient,
[
'Listing ID' => RealEstatePlaceholderField::LISTING_ID,
'Listing Name' => RealEstatePlaceholderField::LISTING_NAME,
'Final URLs' => RealEstatePlaceholderField::FINAL_URLS,
'Image URL' => RealEstatePlaceholderField::IMAGE_URL,
'Contextual Keywords' => RealEstatePlaceholderField::CONTEXTUAL_KEYWORDS
]
);
}
// [END add_real_estate_feed]
/**
* Retrieves the placeholder fields to feed attributes map for a feed. The initial
* query retrieves the feed attributes, or columns, of the feed. Each feed attribute will also
* include the feed attribute ID, which will be used in a subsequent step.
*
* Then a map is created for the feed attributes (columns) and returned:
* - The keys are the placeholder types that the columns will be.
* - The values are the feed attributes.
*
* @param string $feedResourceName the feed resource name to get the attributes from
* @param int $customerId the customer ID
* @param GoogleAdsClient $googleAdsClient the Google Ads API client
* @param array $feedAttributeNamesMap the associative array mapping from feed attribute names
* to placeholder fields
* @return array the map from placeholder fields to feed attributes
*/
private static function placeholderFieldsMapFor(
string $feedResourceName,
int $customerId,
GoogleAdsClient $googleAdsClient,
array $feedAttributeNamesMap
) {
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
// Constructs the query to get the feed attributes for the specified feed resource name.
$query = "SELECT feed.attributes FROM feed WHERE feed.resource_name = '$feedResourceName'";
// Issues a search request.
$response =
$googleAdsServiceClient->search(SearchGoogleAdsRequest::build($customerId, $query));
// Gets the first result because we only need the single feed we created previously.
/** @var GoogleAdsRow $googleAdsRow */
$googleAdsRow = $response->getIterator()->current();
// Gets the attributes list from the feed and creates a map with keys of placeholder fields
// and values of feed attributes.
$feedAttributes =
iterator_to_array($googleAdsRow->getFeed()->getAttributes()->getIterator());
$placeholderFields = array_map(
function (FeedAttribute $feedAttribute) use ($feedAttributeNamesMap) {
if (!array_key_exists($feedAttribute->getName(), $feedAttributeNamesMap)) {
throw new \RuntimeException('Invalid feed attribute name.');
}
return $feedAttributeNamesMap[$feedAttribute->getName()];
},
$feedAttributes
);
return array_combine($placeholderFields, $feedAttributes);
}
}

67
app/util/Helper.php Normal file
View File

@ -0,0 +1,67 @@
<?php
/**
* Copyright 2020 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 app\util;
use DateTime;
/**
* General utilities that are shared between code examples.
*/
final class Helper
{
/**
* Generates a printable string for the current date and time in local time zone.
* @return string the result string
*/
public static function getPrintableDatetime(): string
{
return (new DateTime())->format("Y-m-d\TH:i:s.vP");
}
/**
* Generates a short printable string for the current date and time in local time zone.
* @return string the result string
*/
public static function getShortPrintableDatetime(): string
{
return (new DateTime())->format("mdHisv");
}
/**
* Converts an amount from the micro unit to the base unit.
*
* @param int|float|null $amount the amount in micro unit
* @return float the amount converted to the base unit if not null otherwise 0
*/
public static function microToBase($amount): float
{
return $amount ? $amount / 1000000.0 : 0.0;
}
/**
* Converts an amount from the base unit to the micro unit.
*
* @param float|int|null $amount the amount in base unit
* @return int the amount converted to the micro unit if not null otherwise 0
*/
public static function baseToMicro($amount): int
{
return $amount ? (int) ($amount * 1000000) : 0;
}
}

View File

@ -0,0 +1,5 @@
<?php
return [
'enable' => false,
'default_app' => '', //默认应用,如需开启请填写默认应用的名称
];

View File

@ -0,0 +1,109 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Route;
// 已经设置过路由的uri则忽略
$routes = Route::getRoutes();
$ignore_list = [];
foreach ($routes as $tmp_route) {
$ignore_list[$tmp_route->getPath()] = 0;
}
$default_app = config('plugin.webman.auto-route.app.default_app');
$suffix = config('app.controller_suffix', '');
$suffix_length = strlen($suffix);
// 递归遍历目录查找控制器自动设置路由
$dir_iterator = new \RecursiveDirectoryIterator(app_path());
$iterator = new \RecursiveIteratorIterator($dir_iterator);
foreach ($iterator as $file) {
// 忽略目录和非php文件
if (is_dir($file) || $file->getExtension() != 'php') {
continue;
}
$file_path = str_replace('\\', '/',$file->getPathname());
// 文件路径里不带controller的文件忽略
if (strpos(strtolower($file_path), '/controller/') === false) {
continue;
}
// 只处理带 controller_suffix 后缀的
if ($suffix_length && substr($file->getBaseName('.php'), -$suffix_length) !== $suffix) {
continue;
}
// 根据文件路径计算uri
$uri_path = str_replace(['/controller/', '/Controller/'], '/', substr(substr($file_path, strlen(app_path())), 0, - (4 + $suffix_length)));
// 默认应用
$is_default_app = false;
if (is_string($default_app) && !empty($default_app)) {
$seg = explode('/', $uri_path);
if ($seg[1] == $default_app) {
$uri_path = str_replace($default_app . '/', '', $uri_path);
$is_default_app = true;
}
}
// 根据文件路径是被类名
$class_name = str_replace('/', '\\',substr(substr($file_path, strlen(base_path())), 0, -4));
if (!class_exists($class_name)) {
echo "Class $class_name not found, skip route for it\n";
continue;
}
// 通过反射找到这个类的所有共有方法作为action
$class = new ReflectionClass($class_name);
$class_name = $class->name;
$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
$route = function ($uri, $cb) use ($ignore_list) {
if (isset($ignore_list[strtolower($uri)])) {
return;
}
Route::any($uri, $cb);
if ($uri !== '') {
Route::any($uri . '/', $cb);
}
$lower_uri = strtolower($uri);
if ($lower_uri !== $uri) {
Route::any($lower_uri, $cb);
Route::any($lower_uri . '/', $cb);
}
};
// 设置路由
foreach ($methods as $item) {
$action = $item->name;
if (in_array($action, ['__construct', '__destruct'])) {
continue;
}
// action为index时uri里末尾/index可以省略
if ($action === 'index') {
// controller也为index时uri里可以省略/index/index
if (strtolower(substr($uri_path, -6)) === '/index') {
if ($is_default_app) {
$route('/', [$class_name, $action]);
}
$route(substr($uri_path, 0, -6), [$class_name, $action]);
}
$route($uri_path, [$class_name, $action]);
}
$route($uri_path.'/'.$action, [$class_name, $action]);
}
}