Курсор FacebookAds SDK не работает на / reportstats endpoint
Я использую facebook-php-ads-sdk версии 2.2.4 (самая новая на момент написания этой статьи). Я заметил, что курсор, возвращаемый с вызовами $adAccount->getReportStats()
, разбивается с помощью неявной опции fetch. Курсор ожидает, что увидит в ответе следующую структуру:
{
"paging": { "cursor": { "after": "<some_url>" } }
}
Однако конечная точка /reportstats
возвращает информацию о подкачке, структурированную следующим образом:
{
"paging": { "next": "<some_url>" }
}
Я мог бы поклясться, что он работал, как и ожидалось несколько дней назад, так что, возможно, API facebook изменился?
Вот пример:
$adAccount = new AdAccount('some_id');
// cursor is an instance of FacebookAdsCursor.
$cursor = $adAccount->getReportStats($someFields, $someParams);
$cursor->setUseImplicitFetch(true);
foreach ($cursor as $item) {
// do stuff
}
// cursor is never advanced to next paged result.
Как вы можете видеть в этом вырезанном из FacebookAds Cursor, когда неявная выборка установлена в true, курсор проверяет только paging.cursor.after|before
:
<?php
namespace FacebookAds;
use FacebookAdsHttpRequestInterface;
use FacebookAdsHttpResponseInterface;
use FacebookAdsObjectAbstractObject;
class Cursor implements Iterator, Countable, arrayaccess {
// ...
/**
* @return string|null
*/
protected function getLastRequestBefore() {
$content = $this->getLastResponse()->getContent();
return isset($content['paging']['cursors']['before'])
? $content['paging']['cursors']['before']
: null;
}
/**
* @return string|null
*/
protected function getLastRequestAfter() {
$content = $this->getLastResponse()->getContent();
return isset($content['paging']['cursors']['after'])
? $content['paging']['cursors']['after']
: null;
}
// ...
}
Фактический запрос curl sdk facebook генерирует:
curl -G
-d "data_columns=["time_start","time_stop","spend","impressions","clicks","unique_clicks","social_clicks","unique_social_clicks","cpm","unique_ctr","reach","frequency","cost_per_unique_click","cost_per_action_type","cost_per_total_action","cpp","cpc","ctr","account_id","account_name","campaign_group_id","campaign_group_name","campaign_id","campaign_name"]"
-d "date_preset=last_90_days"
-d "time_increment=1"
-d "access_token=<nice_try_dude>"
-d "appsecret_proof=<not_getting_this_either>"
https://graph.facebook.com/v2.2/act_<account_id>/reportstats
И вот ответ:
{
"data": [
{
"campaign_id": "<campaign_id>",
"date_start": "2014-12-18",
"date_stop": "2014-12-18",
"time_start": 1418878800,
"time_stop": 1418965200,
"spend": 39.39,
"impressions": 5127,
"clicks": 65,
"unique_clicks": 55,
"social_clicks": 31,
"unique_social_clicks": 27,
"cpm": 7.6828554710357,
"unique_ctr": 1.0880316518299,
"reach": 5055,
"frequency": 1.0142433234421,
"cost_per_unique_click": 0.71618181818182,
"cost_per_action_type": 0.67913793103448,
"cost_per_total_action": 0.67913793103448,
"cpp": 7.7922848664688,
"cpc": 0.606,
"ctr": 1.2677979325141,
"account_id": "<account_id>",
"account_name": "<account_name>",
"campaign_group_id": "<campaign_group_id>",
"campaign_group_name": "<campaign_group_name>",
"campaign_name": "<campaign_name>"
},
{
"..." : "x49"
}
],
"limit": 50,
"offset": 0,
"paging": {
"next": "https://graph.facebook.com/v2.2/act_<account_id>/reportstats?data_columns=%5B%22time_start%22%2C%22time_stop%22%2C%22spend%22%2C%22impressions%22%2C%22clicks%22%2C%22unique_clicks%22%2C%22social_clicks%22%2C%22unique_social_clicks%22%2C%22cpm%22%2C%22unique_ctr%22%2C%22reach%22%2C%22frequency%22%2C%22cost_per_unique_click%22%2C%22cost_per_action_type%22%2C%22cost_per_total_action%22%2C%22cpp%22%2C%22cpc%22%2C%22ctr%22%2C%22account_id%22%2C%22account_name%22%2C%22campaign_group_id%22%2C%22campaign_group_name%22%2C%22campaign_id%22%2C%22campaign_name%22%5D&date_preset=last_90_days&time_increment=1&access_token=<access_token>&appsecret_proof=<appsecret_proof>&offset=50"
}
}
Тем временем я прибегаю к использованию следующей "обертки". Лучшие альтернативы?
<?php namespace PayPerClickMarketFacebookData;
use FacebookAdsCursor;
/**
* Class MyReportCursor
*
* @package PayPerClickMarketFacebookData
*/
class MyReportCursor implements Iterator, Countable, ArrayAccess {
/**
* @type int
*/
protected $position = 0;
/**
* @type Cursor[]
*/
protected $cursors = [];
/**
* @param Cursor $cursor
*/
public function __construct(Cursor $cursor) {
$cursor->setUseImplicitFetch(false);
$this->cursors[] = $cursor;
}
/**
* @return Cursor
*/
public function getCursor() {
return $this->cursors[ $this->position ];
}
public function current() {
return $this->getCursor()->current()->getData();
}
public function next() {
$this->getCursor()->next();
if ($this->getCursor()->key() === null) {
$this->advanceCursors();
}
}
protected function advanceCursors() {
if ($this->hasCursor($this->position+1)) {
$this->getCursor()->rewind();
$this->position++;
} else if ($this->hasNextPage()) {
$this->fetchNext();
}
}
/**
* @return bool
*/
protected function hasNextPage() {
return $this->getNextPage() !== null;
}
/**
* @return string|null
*/
protected function getNextPage() {
$content = $this->getCursor()->getLastResponse()->getContent();
return isset($content['paging']['next']) ? $content['paging']['next'] : null;
}
/**
* @param int $offset
* @return bool
*/
protected function hasCursor($offset) {
return isset($this->cursors[ $offset ]);
}
protected function fetchNext() {
parse_str(parse_url($this->getNextPage(), PHP_URL_QUERY), $previousParams);
$objectPrototype = clone $this->getCursor()->offsetGet($this->getCursor()->getIndexRight());
$request = $this->getCursor()->getLastResponse()->getRequest()->createClone();
$request->getQueryParams()->offsetSet('offset', $previousParams['offset']);
$this->getCursor()->rewind();
$this->position++;
$this->cursors[ $this->position ] = new Cursor($request->execute(), $objectPrototype);
}
public function key() {
return $this->getCursor()->key();
}
public function valid() {
return $this->getCursor()->valid();
}
public function rewind() {
$this->position = 0;
}
public function offsetExists($offset) {
return $this->getCursor()->offsetExists($offset);
}
public function offsetGet($offset) {
return $this->getCursor()->offsetGet($offset);
}
public function offsetSet($offset, $value) {
$this->getCursor()->offsetSet($offset, $value);
}
public function offsetUnset($offset) {
$this->getCursor()->offsetUnset($offset);
}
public function count() {
return array_reduce($this->cursors, function ($a, $cursor) {
return $a + $cursor->count();
}, 0);
}
}
3 ответа:
То, что вы видите, - это ответ, когда это "временная пагинация" https://developers.facebook.com/docs/graph-api/using-graph-api/v2.2#paging
Скорее всего, это ошибка в facebook-php-ads-sdk. Даже примеры API Facebook показывают это как:
use FacebookAds\Object\AdAccount; $account = new AdAccount('act_<AD_ACCOUNT_ID>'); $params = array( 'date_preset'=>'last_28_days', 'data_columns'=>"['adgroup_id','actions','spend']", ); $stats = $account->getReportsStats(null, $params); foreach($stats as $stat) { echo $stat->impressions; echo $stat->actions; }
К сожалению - это, действительно, временная подкачка, поэтому данные курсора не будут возвращены из него. Он уже заполнен как выпуск на GitHub - https://github.com/facebook/facebook-php-ads-sdk/issues/76
EDIT: О, это вы заполнили этот баг :)