FacebookAds SDK Cursor not working on /reportstats endpoint - php

I'm using the facebook-php-ads-sdk version 2.2.4 (the newest at the time of this writing). I've noticed that the Cursor that is returned with calls to $adAccount->getReportStats() is broken with the implicit fetch option. The cursor is expecting to see in the response the following structure:
{
"paging": { "cursor": { "after": "<some_url>" } }
}
However, the /reportstats endpoint returns the paging information structured like this:
{
"paging": { "next": "<some_url>" }
}
I could have sworn it was working as expected a few days ago, so perhaps facebook's API has changed?
Here is an example:
$adAccount = new AdAccount('some_id');
// cursor is an instance of FacebookAds\Cursor.
$cursor = $adAccount->getReportStats($someFields, $someParams);
$cursor->setUseImplicitFetch(true);
foreach ($cursor as $item) {
// do stuff
}
// cursor is never advanced to next paged result.
As you can see in this snipped from FacebookAds\Cursor, when implicit fetch is set to true, the cursor only checks for paging.cursor.after|before:
<?php
namespace FacebookAds;
use FacebookAds\Http\RequestInterface;
use FacebookAds\Http\ResponseInterface;
use FacebookAds\Object\AbstractObject;
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;
}
// ...
}
The actual curl request facebook's sdk generates:
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
And here is the response:
{
"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"
}
}
In the meantime, I've resorted to using the following "wrapper". Better alternatives?
<?php namespace PayPerClick\Market\Facebook\Data;
use FacebookAds\Cursor;
/**
* Class MyReportCursor
*
* #package PayPerClick\Market\Facebook\Data
*/
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);
}
}

What you are seeing is the response when it is a "Time-based Pagination" https://developers.facebook.com/docs/graph-api/using-graph-api/v2.2#paging

I submitted a bug to their github.

It's mostly probably bug in facebook-php-ads-sdk. Even Facebook API examples show it as:
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;
}
Unfortunately - it is, indeed, time-based paging so cursor data won't be returned from it. It's already filled as an issue on GitHub - https://github.com/facebook/facebook-php-ads-sdk/issues/76
EDIT: Oh, it is you who filled that bug :)

Related

How can I combine contextual binding and service location?

Disclaimer: I would have preferred a more generic question like "How do I keep track of the state of a recursive method?" on the code review stack exchange site, as that better describes where the problem is currently at. But the constraint on that board is that code must first be working
Background: I have a container that
can recursively hydrate and inject constructor arguments
concretes can be provided to it for use to circumvent hydration
provision context exist in two states: for classes being auto-wired, and for classes using the container as a service locator
Three of these target behaviours function as expected, save when the last 2 are combined.
Main problem: when 3a is followed by 3b, container uses an incorrect context, and I don't know how to inspect that particular state since the hydrator/service locator is recursive. Unit testing the individual methods all work correctly. Integration test of either of the two works. But at the level of multi layer hydration, I can't mock out any of the involved parts, thus, I have no way of determining what context is used there
I feel the problem is more of a philosophical one, where more than one answer is applicable. But instead of down-voting, kindly migrate to the appropriate stack exchange site
The code is in PHP, but if you aren't conversant with it, pseudo-code or a verbal solution is welcome. An acceptable solution may even be a test that demonstrates how to simulate and observe container state after hydration and service location. I already have this, but it fails
public function test_hydrated_class_with_getClass_correctly_uses_needs () {
$ourB = new BCounter; // given
$this->container->provideSelf();
$this->container->whenTypeAny()->needsAny([
BCounter::class => $ourB
]);
$this->assertSame( // then
$this->container->getClass($this->aRequires)
->getInternalB(), // when
$this->ourB
);
}
Relevant parts of the container below
<?php
use ReflectionMethod, ReflectionClass, ReflectionFunction, ReflectionType, ReflectionFunctionAbstract, ReflectionException;
class Container {
const UNIVERSAL_SELECTOR = "*";
private $provisionedNamespaces = [], // NamespaceUnit[]
$hydratingForStack = [], // String[]. Doubles as a dependency chain. #see [lastHydratedFor] for main usage
$internalMethodHydrate = false, // Used when [getMethodParameters] is called directly without going through instance methods such as [instantiateConcrete]
$hydratingArguments = false,
$constructor = "__construct",
$externalHydrators = [], $externalContainerManager,
$interfaceHydrator,
$provisionContext, // the active Type before calling `needs`
$provisionSpace; // same as above, but for namespaces
protected $provisionedClasses = []; // ProvisionUnit[]
public function __construct () {
$this->initializeUniversalProvision();
}
public function initializeUniversalProvision ():void {
$this->provisionedClasses[self::UNIVERSAL_SELECTOR] = new ProvisionUnit;
}
public function getInterfaceHydrator ():InterfaceHydrator {
return $this->interfaceHydrator;
}
/**
* Looks for the given class in this order
* 1) pre-provisioned caller list
* 2) Provisions it afresh if an interface or recursively wires in its constructor dependencies
*
* #param {includeSub} Regular provision: A wants B, but we give C sub-class of B. Sub-classes of A can't obtain B unless this parameter is used
*
* #return A class instance, if found
*/
public function getClass (string $fullName, bool $includeSub = false) {
$concrete = $this->decorateProvidedConcrete($fullName);
if (!is_null($concrete)) return $concrete;
if ($includeSub && $parent = $this->hydrateChildsParent($fullName))
return $parent;
$externalManager = $this->externalContainerManager;
if (
!is_null($externalManager) &&
$concrete = $externalManager->findInManagers($fullName)
) {
$this->saveWhenImplements($fullName, $concrete);
return $concrete;
}
return $this->initializeHydratingForAction($fullName, function ($className) {
if ($this->getReflectedClass($className)->isInterface())
return $this->provideInterface($className);
return $this->instantiateConcrete($className);
});
}
public function decorateProvidedConcrete (string $fullName) {
$freshlyCreated = $this->initializeHydratingForAction($fullName, function ($className) {
return new HydratedConcrete(
$this->getProvidedConcrete($className),
$this->lastHydratedFor()
);
});
if (!is_null($freshlyCreated->getConcrete()))
return $this->getDecorator()->scopeInjecting(
$freshlyCreated->getConcrete(),
$freshlyCreated->getCreatedFor()
); // decorator runs on each fetch (rather than only once), since different callers result in different behavior
}
public function getProvidedConcrete (string $fullName) {
$context = $this->getRecursionContext();
if ($context->hasConcrete($fullName))
return $context->getConcrete($fullName);
$globalContext = $this->provisionedClasses[self::UNIVERSAL_SELECTOR];
if ($globalContext->hasConcrete($fullName)) // current provision doesn't include this class. check in global
return $globalContext->getConcrete($fullName);
}
/**
* Switches unit being provided to universal if it doesn't exist
* #return currently available provision unit
*/
public function getRecursionContext ():ProvisionUnit {
$hydrateFor = $this->lastHydratedFor();
if (!array_key_exists($hydrateFor, $this->provisionedClasses))
$hydrateFor = self::UNIVERSAL_SELECTOR;
return $this->provisionedClasses[$hydrateFor];
}
/**
* This tells us the class we are hydrating arguments for
*/
public function lastHydratedFor ():?string {
$stack = $this->hydratingForStack;
if(empty($stack) ) return null;
$index = $this->hydratingArguments ? 2: 1; // If we're hydrating class A -> B -> C, we want to get provisions for B (who, at this point, is indexed -2 while C is -1). otherwise, we'll be looking through C's provisions instead of B
$length = count($stack);
return $stack[$length - $index];
}
/**
* Not explicitly decorating objects from here since it calls [getClass]
*/
private function hydrateChildsParent (string $fullName) {
$providedParent = $this->getProvidedParent($fullName);
if (!is_null($providedParent))
return $this->getClass($providedParent);
}
/**
* #return the first provided parent of the given class
*/
private function getProvidedParent (string $class):?string {
$allSuperiors = array_keys($this->provisionedClasses);
$classSuperiors = array_merge(
class_parents($class, true),
class_implements($class, true)
);
return current(
array_intersect($classSuperiors, $allSuperiors)
);
}
private function saveWhenImplements (string $interface, $concrete):void {
if (!($concrete instanceof $interface))
throw new InvalidImplementor($interface, get_class($concrete));
$this->storeConcrete( $interface, $concrete);
}
private function storeConcrete (string $fullName, $concrete):ProvisionUnit {
return $this->getRecursionContext()->addConcrete($fullName, $concrete);
}
private function getReflectedClass (string $className):ReflectionClass {
try {
return new ReflectionClass($className);
}
catch (ReflectionException $re) {
$message = "Unable to hydrate ". $this->lastHydratedFor() . ": ". $re->getMessage();
$hint = "Hint: Cross-check its dependencies";
throw new HydrationException("$message. $hint");
}
}
/**
* Wrap any call that internally attempts to read from [lastHydratedFor] in this i.e. calls that do some hydration and need to know what context/provision they're being hydrated for
*/
public function initializeHydratingForAction (string $fullName, callable $action) {
$this->initializeHydratingFor($fullName);
$result = $action($fullName);
$this->popHydratingFor($fullName);
return $result;
}
/**
* Tells us who to hydrate arguments for
*/
protected function initializeHydratingFor (string $fullName):void {
$isFirstCall = is_null($this->lastHydratedFor());
$hydrateFor = $isFirstCall ? $this->lastCaller(): $fullName;
$this->pushHydratingFor($hydrateFor);
}
private function lastCaller ():string {
$stack = debug_backtrace (2 ); // 2=> ignore concrete objects and their args
$caller = "class";
foreach ($stack as $execution)
if (array_key_exists($caller, $execution) && $execution[$caller] != get_class()) {
return $execution[$caller];
}
}
/**
* Updates the last element in the context hydrating stack, to that whose provision dependencies should be hydrated for
*/
protected function pushHydratingFor (string $fullName):void {
$this->hydratingForStack[] = $fullName;
}
/**
* #param {completedHydration} To guarantee push-pop consistency. When the name of what is expected to be removed doesn't match the last item in stack, it indicates we're currently hydrating an interface (where its name differs from concretes involved). When this happens, we simply ignore popping our list since those concretes were not the ones that originally got pushed
*/
private function popHydratingFor (string $completedHydration):void {
if (end($this->hydratingForStack) == $completedHydration)
array_pop($this->hydratingForStack);
}
/**
* #throws InvalidImplementor
*
* #return Concrete of the given [Interface] if it was bound
*/
protected function provideInterface (string $interface) {
$caller = $this->lastHydratedFor();
if ($this->hasRenamedSpace($caller)) {
$newIdentity = $this->relocateSpace($interface, $caller);
$concrete = $this->instantiateConcrete($newIdentity);
}
else {
$concrete = $this->getInterfaceHydrator()->deriveConcrete($interface);
if (!is_null($concrete))
$this->saveWhenImplements($interface, $concrete);
}
if (is_null($concrete))
throw new InvalidImplementor($interface, "No matching concrete" );
return $concrete;
}
/**
* A shorter version of [getClass], but neither checks in cache or contextual provisions. This means they're useful to:
* 1) To hydrate classes we're sure doesn't exist in the cache
* 2) In methods that won't be called more than once in the request cycle
* 3) To create objects that are more or less static, or can't be overidden by an extension
*
* All objects internally derived from this trigger decorators if any are applied
*/
public function instantiateConcrete (string $fullName) {
$freshlyCreated = $this->initializeHydratingForAction ($fullName, function ($className) {
if (!method_exists($className, $this->constructor))
return new HydratedConcrete(new $className, $this->lastHydratedFor() );
return $this->hydrateConcreteForCaller($className);
});
$this->storeConcrete($fullName, $freshlyCreated->getConcrete());
return $this->getDecorator()->scopeInjecting(
$freshlyCreated->getConcrete(),
$freshlyCreated->getCreatedFor()
);
}
public function hydrateConcreteForCaller (string $className):HydratedConcrete {
$dependencies = $this->internalMethodGetParameters(function () use ($className) {
return array_values($this->getMethodParameters($this->constructor, $className));
});
return new HydratedConcrete(
new $className (...$dependencies),
$this->lastHydratedFor()
);
}
public function internalMethodGetParameters (callable $action) {
$this->internalMethodHydrate = true;
$result = $action();
$this->internalMethodHydrate = false;
return $result;
}
/**
* Fetch appropriate dependencies for a callable's arguments
*
* #param {callable}:string|Closure
* #param {anchorClass} the class the given method belongs to
*
* #return {Array} associative. Contains hydrated parameters to invoke given callable with
*/
public function getMethodParameters ( $callable, string $anchorClass = null):array {
$context = null;
if (is_null($anchorClass))
$reflectedCallable = new ReflectionFunction($callable);
else {
$reflectedCallable = new ReflectionMethod($anchorClass, $callable);
if (!$this->internalMethodHydrate)
$this->initializeHydratingFor($anchorClass);
$context = $this->getRecursionContext();
}
$dependencies = $this->populateDependencies($reflectedCallable, $context);
if (is_null($anchorClass)) return $dependencies;
elseif (!$this->internalMethodHydrate)
$this->popHydratingFor($anchorClass);
return $this->getDecorator()->scopeArguments( $anchorClass, $dependencies, $callable);
}
public function populateDependencies (ReflectionFunctionAbstract $reflectedCallable, ?ProvisionUnit $callerProvision):array {
$dependencies = [];
foreach ($reflectedCallable->getParameters() as $parameter) {
$parameterName = $parameter->getName();
$parameterType = $parameter->getType();
if (!is_null($callerProvision) )
$dependencies[$parameterName] = $this->hydrateProvidedParameter($callerProvision, $parameterType, $parameterName);
elseif (!is_null($parameterType))
$dependencies[$parameterName] = $this->hydrateUnprovidedParameter($parameterType);
elseif ($parameter->isOptional() )
$dependencies[$parameterName] = $parameter->getDefaultValue();
else $dependencies[$parameterName] = null;
}
return $dependencies;
}
/**
* Pulls out a provided instance of a dependency when present, or creates a fresh one
*
* #return object matching type at given parameter
*/
private function hydrateProvidedParameter (ProvisionUnit $callerProvision, ReflectionType $parameterType, string $parameterName) {
if ($callerProvision->hasArgument($parameterName))
return $callerProvision->getArgument($parameterName);
$typeName = $parameterType->getName();
if ($callerProvision->hasArgument($typeName))
return $callerProvision->getArgument($typeName);
return $this->hydrateUnprovidedParameter($parameterType);
}
private function hydrateUnprovidedParameter (ReflectionType $parameterType) {
$typeName = $parameterType->getName();
if ( $parameterType->isBuiltin()) {
$defaultValue = null;
settype($defaultValue, $typeName);
return $defaultValue;
}
if (!in_array($typeName, $this->hydratingForStack)) {
$this->hydratingArguments = true;
$concrete = $this->getClass($typeName);
$this->hydratingArguments = false;
return $concrete;
}
if ($this->getReflectedClass($typeName)->isInterface())
throw new HydrationException ("$typeName's concrete cannot depend on its dependency's concrete");
trigger_error("Circular dependency detected while hydrating $typeName", E_USER_WARNING);
}
public function whenType (string $toProvision):self {
if (!array_key_exists($toProvision, $this->provisionedClasses))
$this->provisionedClasses[$toProvision] = new ProvisionUnit;
$this->provisionContext = $toProvision;
return $this;
}
public function whenTypeAny ():self {
return $this->whenType(self::UNIVERSAL_SELECTOR);
}
public function needs (array $dependencyList):self {
if (is_null ($this->provisionContext))
throw new HydrationException("Undefined provisionContext");
$this->provisionedClasses[$this->provisionContext]->updateConcretes($dependencyList);
return $this;
}
public function needsAny (array $dependencyList):self {
$this->needs($dependencyList)
->needsArguments($dependencyList);
$this->provisionContext = null;
return $this;
}
public function needsArguments (array $argumentList):self {
if (is_null ($this->provisionContext))
throw new HydrationException("Undefined provisionContext");
$this->provisionedClasses[$this->provisionContext]->updateArguments($argumentList);
return $this;
}
public function provideSelf ():void {
$this->whenTypeAny()->needsAny([get_class() => $this]);
}
protected function getDecorator ():DecoratorHydrator {
return $this->decorator;
}
}
?>
The relevant parts are:
decorateProvidedConcrete (who I want to assert was called twice, but by mocking, getClass can no longer function)
public function test_hydrated_class_with_getClass_reads_provision () {
// given
$container = $this->positiveDouble(Container::class, [
"getDecorator" => $this->stubDecorator(),
"getProvidedConcrete" => $this->returnCallback(function ($subject) {
return $this->positiveDouble($subject, []); // return a stub
})
], [
"getProvidedConcrete" => [2, [
$this->callback(function ($subject) {
return BCounter::class == $subject; // this obviously won't work, since method attempts to hydrate other classes, as well
})
]] // then 1
]);
$this->bootContainer($container);
$this->entityBindings();
// when
$container->getClass($this->aRequires)->getInternalB();
}
getRecursionContext, who I have no way of observing until getClass returns. But I would like to know what context it's working with by the time we're doing the service location. And, that's difficult to figure out since getClass is recursive
Finally, ARequiresBCounter. I want to DI this class with provided BCounter. Then when getInternalB runs, it equally uses the provided BCounter
class ARequiresBCounter {
private $b1, $container, $primitive;
public function __construct (BCounter $b1, Container $container, string $primitive) {
$this->b1 = $b1;
$this->container = $container;
$this->primitive = $primitive;
}
public function getConstructorB ():BCounter {
return $this->b1;
}
public function getInternalB ():BCounter {
return $this->container->getClass(BCounter::class);
}
}

how to use react js for front-end and php for back-end

I had built a PHP back-end and I just want to make a simple interface for it using react js and I am using CLI script for running the PHP so I am not using any framework just PHP so this is the PHP file that I build and just I want to know how to connect PHP back-end to react js if you can help me with it or give me some link to follow it.
<?PHP
/**
* The interface provides the contract for different readers
* E.g. it can be XML/JSON Remote Endpoint, or CSV/JSON/XML local files
*/
interface ReaderInterface
{
/**
* Read in incoming data and parse to objects
*/
public function read(string $input): OfferCollectionInterface;
}
/**
* Interface of Data Transfer Object, that represents external JSON data
*/
interface OfferInterface
{
}
/**
* Interface for The Collection class that contains Offers
*/
interface OfferCollectionInterface
{
public function get(int $index): OfferInterface;
public function getIterator(): Iterator;
}
/* *********************************** */
class Offer implements OfferInterface
{
public $offerId;
public $productTitle;
public $vendorId;
public $price;
public function __toString(): string
{
return "$this->offerId | $this->productTitle | $this->vendorId | $this->price\n";
}
}
class OfferCollection implements OfferCollectionInterface
{
private $offersList = array();
public function __construct($data)
{
foreach ($data as $json_object) {
$offer = new Offer();
$offer->offerId = $json_object->offerId;
$offer->productTitle = $json_object->productTitle;
$offer->vendorId = $json_object->vendorId;
$offer->price = $json_object->price;
array_push($this->offersList, $offer);
}
}
public function get(int $index): OfferInterface
{
return $this->offersList[$index];
}
public function getIterator(): Iterator
{
return new ArrayIterator($this->offersList);
}
public function __toString(): string
{
return implode("\n", $this->offersList);
}
}
class Reader implements ReaderInterface
{
/**
* Read in incoming data and parse to objects
*/
public function read(string $input): OfferCollectionInterface
{
if ($input != null) {
$content = file_get_contents($input);
$json = json_decode($content);
$result = new OfferCollection($json);
return $result;
}
return new OfferCollection(null);
}
}
class Logger {
private $filename = "logs.txt";
public function info($message): void {
$this->log($message, "INFO");
}
public function error($message): void {
$this->log($message, "ERROR");
}
private function log($message, $type): void {
$myfile = fopen($this->filename, "a") or die("Unable to open file!");
$txt = "[$type] $message\n";
fwrite($myfile, $txt);
fclose($myfile);
}
}
$json_url = 'data.json';
$json_reader = new Reader();
$offers_list = $json_reader->read($json_url);
function count_by_price_range($price_from, $price_to)
{
global $offers_list;
$count = 0;
foreach ($offers_list->getIterator() as $offer) {
if ($offer->price >= $price_from && $offer->price <= $price_to) {
$count++;
}
}
return $count;
}
function count_by_vendor_id($vendorId)
{
global $offers_list;
$count = 0;
foreach ($offers_list->getIterator() as $offer) {
if ($offer->vendorId == $vendorId) {
$count++;
}
}
return $count;
}
$cli_args = $_SERVER['argv'];
$function_name = $cli_args[1];
$logger = new Logger();
switch ($function_name) {
case "count_by_price_range": {
$logger->info("Getting Count By Price Range From: $cli_args[2] TO $cli_args[3]");
echo count_by_price_range($cli_args[2], $cli_args[3]);
break;
}
case "count_by_vendor_id": {
$logger->info("Getting Count By vendor Id: $cli_args[2]");
echo count_by_vendor_id($cli_args[2]);
break;
}
}
Reactjs is frontend and PHP is backend, you can connect between React App and PHP services by REST API

Method chaining in php

I have the following class
class FormValidator{
public function __construct() {}
public function __destruct(){}
private $value;
public function Value($value){
$this->value = trim($value);
return $this;
}
public function Required(){
if(empty($this->value)){
return false;
}
else{
return $this;
}
}
public function MinLength($length){
$len = strlen($this->value);
if($len < $length){
return false;
}
else{
return $this;
}
}
}
In my php code, I'm calling -
$validator = new FormValidator();
$result = $validator->Value("")->Required()->MinLength(5)->SomeOtherMethod();
The above line gives the error Call to a member function MinLength() on a non-object ...
UPDATE: I require to stop to call MinLength() if Required() returns false.
How can I make my statement functioning?
You should make use of exceptions instead, which would handle errors while the methods themselves would always return the current instance.
This becomes:
<?php
class FormValidationException extends \Exception
{
}
class FormValidator
{
private $value;
public function Value($value): self
{
$this->value = trim($value);
return $this;
}
/**
* #return $this
* #throws FormValidationException
*/
public function Required(): self
{
if (empty($this->value)) {
throw new \FormValidationException('Value is required.');
}
return $this;
}
/**
* #param int $length
* #return $this
* #throws FormValidationException
*/
public function MinLength(int $length): self
{
$len = strlen($this->value);
if ($len < $length) {
throw new \FormValidationException("Value should be at least {$length} characters long.");
}
return $this;
}
}
Usage:
$validator = new FormValidator();
try {
$result = $validator->Value("lodddl")->Required()->MinLength(5);
} catch (\FormValidationException $e) {
echo 'Error: ', $e->getMessage();
}
Demo: https://3v4l.org/OFcPV
Edit: since OP is using PHP 5.2 (sadly), here's a version for it, removing \s before root namespace, return type declarations and argument types.
Demo for PHP 5.2: https://3v4l.org/cagWS
Instead of working with Exceptions (the solution of #Jeto), you can also work with an array holding all errors.
A benefit of this solution is that you'll get multiple errors in one run, instead of breaking at the first error.
<?php
class FormValidator
{
private $value;
private $_errors = array();
public function Value($value)
{
$this->value = trim($value);
return $this;
}
/**
* #return $this
*/
public function Required()
{
if (empty($this->value)) {
$this->_errors[] = 'Value is required';
}
return $this;
}
/**
* #param int $length
* #return $this
*/
public function MinLength($length)
{
$len = strlen($this->value);
if ($len < $length) {
$this->_errors[] = "Value should be at least {$length} characters long.";
}
return $this;
}
public function hasErrors(){
return (count($this->_errors) > 0);
}
public function getErrors(){
return $this->_errors;
}
}
$validator = new FormValidator();
$validator->Value("1234")->Required()->MinLength(5);
if($validator->hasErrors()){
echo implode('<br>',$validator->getErrors());
}
Example here
cause method Required() returns false not Class object:
if(empty($this->value)){
return false;
}
You must change your code to.
$result = $validator->Value("");
if($result->Required()){ // if is not false do
$result->MinLength(5)->SomeOtherMethod();
}else{
// do anything if value is empty.
}
In the following methods you are calling to a method on a boolean value.
Required();
MinLength();
In order to solve this problem in the MinLength method:
if($len < $length){
// As a flag
$this->failed = true;
return $this;
}
and the SomeOtherMethod():
public function SomeOtherMethod() {
if (! $this->failed) {
// do something...
} else {
// do nothing...
}
}
Do the same for the Requied() method

laravel 5 Added external Class not found

I have a php lib which is a set of functions,Here it is
<?php
# Copyright (c) 2010-2011 Arnaud Renevier, Inc, published under the modified BSD
# license.
namespace App\Gislib;
abstract class CustomException extends \Exception {
protected $message;
public function __toString() {
return get_class($this) . " {$this->message} in {$this->file}({$this->line})\n{$this->getTraceAsString()}";
}
}
class Unimplemented extends CustomException {
public function __construct($message) {
$this->message = "unimplemented $message";
}
}
class UnimplementedMethod extends Unimplemented {
public function __construct($method, $class) {
$this->message = "method {$this->class}::{$this->method}";
}
}
class InvalidText extends CustomException {
public function __construct($decoder_name, $text = "") {
$this->message = "invalid text for decoder " . $decoder_name . ($text ? (": " . $text) : "");
}
}
class InvalidFeature extends CustomException {
public function __construct($decoder_name, $text = "") {
$this->message = "invalid feature for decoder $decoder_name" . ($text ? ": $text" : "");
}
}
abstract class OutOfRangeCoord extends CustomException {
private $coord;
public $type;
public function __construct($coord) {
$this->message = "invalid {$this->type}: $coord";
}
}
class OutOfRangeLon extends outOfRangeCoord {
public $type = "longitude";
}
class OutOfRangeLat extends outOfRangeCoord {
public $type = "latitude";
}
class UnavailableResource extends CustomException {
public function __construct($ressource) {
$this->message = "unavailable ressource: $ressource";
}
}
interface iDecoder {
/*
* #param string $text
* #return Geometry
*/
static public function geomFromText($text);
}
abstract class Decoder implements iDecoder {
static public function geomFromText($text) {
throw new UnimplementedMethod(__FUNCTION__, get_called_class());
}
}
interface iGeometry {
/*
* #return string
*/
public function toGeoJSON();
/*
* #return string
*/
public function toKML();
/*
* #return string
*/
public function toWKT();
/*
* #param mode: trkseg, rte or wpt
* #return string
*/
public function toGPX($mode = null);
/*
* #param Geometry $geom
* #return boolean
*/
public function equals(Geometry $geom);
}
abstract class Geometry implements iGeometry {
const name = "";
public function toGeoJSON() {
throw new UnimplementedMethod(__FUNCTION__, get_called_class());
}
public function toKML() {
throw new UnimplementedMethod(__FUNCTION__, get_called_class());
}
public function toGPX($mode = null) {
throw new UnimplementedMethod(__FUNCTION__, get_called_class());
}
public function toWKT() {
throw new UnimplementedMethod(__FUNCTION__, get_called_class());
}
public function equals(Geometry $geom) {
throw new UnimplementedMethod(__FUNCTION__, get_called_class());
}
public function __toString() {
return $this->toWKT();
}
}
class GeoJSON extends Decoder {
static public function geomFromText($text) {
$ltext = strtolower($text);
$obj = json_decode($ltext);
if (is_null ($obj)) {
throw new InvalidText(__CLASS__, $text);
}
try {
$geom = static::_geomFromJson($obj);
} catch(InvalidText $e) {
throw new InvalidText(__CLASS__, $text);
} catch(\Exception $e) {
throw $e;
}
return $geom;
}
static protected function _geomFromJson($json) {
if (property_exists ($json, "geometry") and is_object($json->geometry)) {
return static::_geomFromJson($json->geometry);
}
if (!property_exists ($json, "type") or !is_string($json->type)) {
throw new InvalidText(__CLASS__);
}
foreach (array("Point", "MultiPoint", "LineString", "MultiLinestring", "LinearRing",
"Polygon", "MultiPolygon", "GeometryCollection") as $json_type) {
if (strtolower($json_type) == $json->type) {
$type = $json_type;
break;
}
}
if (!isset($type)) {
throw new InvalidText(__CLASS__);
}
try {
$components = call_user_func(array('static', 'parse'.$type), $json);
} catch(InvalidText $e) {
throw new InvalidText(__CLASS__);
} catch(\Exception $e) {
throw $e;
}
$constructor = __NAMESPACE__ . '\\' . $type;
return new $constructor($components);
}
static protected function parsePoint($json) {
if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) {
throw new InvalidText(__CLASS__);
}
return $json->coordinates;
}
static protected function parseMultiPoint($json) {
if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) {
throw new InvalidText(__CLASS__);
}
return array_map(function($coords) {
return new Point($coords);
}, $json->coordinates);
}
static protected function parseLineString($json) {
return static::parseMultiPoint($json);
}
static protected function parseMultiLineString($json) {
$components = array();
if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) {
throw new InvalidText(__CLASS__);
}
foreach ($json->coordinates as $coordinates) {
$linecomp = array();
foreach ($coordinates as $coordinates) {
$linecomp[] = new Point($coordinates);
}
$components[] = new LineString($linecomp);
}
return $components;
}
static protected function parseLinearRing($json) {
return static::parseMultiPoint($json);
}
static protected function parsePolygon($json) {
$components = array();
if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) {
throw new InvalidText(__CLASS__);
}
foreach ($json->coordinates as $coordinates) {
$ringcomp = array();
foreach ($coordinates as $coordinates) {
$ringcomp[] = new Point($coordinates);
}
$components[] = new LinearRing($ringcomp);
}
return $components;
}
static protected function parseMultiPolygon($json) {
$components = array();
if (!property_exists ($json, "coordinates") or !is_array($json->coordinates)) {
throw new InvalidText(__CLASS__);
}
foreach ($json->coordinates as $coordinates) {
$polycomp = array();
foreach ($coordinates as $coordinates) {
$ringcomp = array();
foreach ($coordinates as $coordinates) {
$ringcomp[] = new Point($coordinates);
}
$polycomp[] = new LinearRing($ringcomp);
}
$components[] = new Polygon($polycomp);
}
return $components;
}
static protected function parseGeometryCollection($json) {
if (!property_exists ($json, "geometries") or !is_array($json->geometries)) {
throw new InvalidText(__CLASS__);
}
$components = array();
foreach ($json->geometries as $geometry) {
$components[] = static::_geomFromJson($geometry);
}
return $components;
}
}}
I have placed it in App\Gislib\Gislib.php
and in my controller I have added its using as use App\Gislib\GeoJSON; but when I try to load its class $decoder =new \App\Gislib\GeoJSON(); it says Class 'App\Gislib\GeoJSON' not found where is my mistake?Is it related to extended types or namespaces? I know there are some other methods to call these classes but I just can load them using namespaces
thanks
Each class or interface needs to be in its own file, with the file name matching the class name.
For example, in app/Gislib/Unimplemented.php:
<?php
namespace App\Gislib;
class Unimplemented extends CustomException {
public function __construct($message) {
$this->message = "unimplemented $message";
}
}
and then in app/Gislib/iDecoder.php:
<?php
namespace App\Gislib;
interface iDecoder {
/*
* #param string $text
* #return Geometry
*/
static public function geomFromText($text);
}
This is due to Laravel following PSR-4 standards.
If you still get the error after splitting the file up, try running composer dump.
With PSR-4 autoloading it is required that the class name matches the file name. See https://stackoverflow.com/a/29033779 .
But what you can do is to modify your composer.json file like this:
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"files" : [
"app/Gislib/Gislib.php"
],
"psr-4": {
"App\\": "app/"
}
},
Add as section "files" and provide the path to your lib. ( I think you have to do composer dump-autoload after that. Now it should work.

PHP: Warning: array_map(): Argument #2 should be an array

I have a script that works perfect in ubuntu, recently I copied the exact same script to centos6 server
and I'm getting the following error:
PHP Warning: array_map(): Argument #2 should be an array in /copy_scripts/classes/Vserver.class.php on line 245
the code
/**
* Get an array of open files
* #param array $aExt
* #return array of arrays ['views','size','path']
*/
public function getOpenFiles($aExt=array()) {
$aOpenFiles = self::GetList(self::$sLogOpenFiles);
$aOpenFiles = array_map( create_function('$v', '
$v = trim($v);
$aFile = preg_split("#[\t\s]+#",$v);
$oFile = new VserverFile($aFile[2]);
$oFile->setViews($aFile[0]);
return $oFile;'),
$aOpenFiles
);
//echo "<pre>";
//print_r($aOpenFiles);
return $aOpenFiles;
}
Can it be the difference between php versions?
centos:
PHP 5.3.3 (cli) (built: Jul 3 2012 16:53:21)
ubuntu
PHP 5.3.10-2 (cli) (built: Feb 20 2012 19:39:00)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies
what I've tried so far:
changing setting in php.ini a restarting php.
the complete code:
<?php
/**
*
* Global Video Server class
* Singleton class
* Has methods to manipulate files
* #author kpoxas
*
*/
class Vserver {
static protected $oInstance=null;
static protected $iTimeStart, $iTimeEnd;
const FILE_NOEXIST = -2;
const FILE_ZEROSIZE = -4;
const FILE_DIFF = -8;
/*
* Path /var/www/HDD_PATH/
*/
static public $sHDDPath = null;
/*
* Path /var/www/SSD_PATH/
*/
static public $sSSDPath = null;
/*
* Folder same on every RAID.../FOLDER/...
*/
static public $sHTTPFolder = null;
/*
* Open Files Log
*/
static public $sLogOpenFiles = null;
/*
* Limit of Free space on SSD in percents
*/
static public $iSSDUsage = 90;
/*
* Limit of Free space on SSD in percents (not delete below)
*/
static public $iSSDDeleteUsage = 80;
/*
* Limit of Free space on SSD in bytes (not delete below)
*/
static public $iSSDDeleteUsageAbsolute = 5368709120;
/*
* Min views count of file to copy
*/
static public $iMinViews = 2;
/*
* Search within files older than $iDaysToDelete days
*/
static public $iDaysToDelete = 1;
/*
* Test Mode
* File manipulations aren't executed
* Only log mode
*/
static public $bTest = false;
/*
* Store openfiles.txt log
*/
static protected $sOpenFiles = null;
/**
* Äåñêðèïòîð áëîêèðóþùåãî ôàéëà
*
* #var string
*/
protected $oLockFile=null;
/**
* Singleton Object implementation
*
* #return VSERVER
*/
static public function getInstance() {
if (isset(self::$oInstance) and (self::$oInstance instanceof self)) {
return self::$oInstance;
} else {
self::$oInstance= new self();
return self::$oInstance;
}
}
/**
* Get Log content
* #param string $sLogPath
* #return string
*/
static public function GetLog($sLogPath) {
if ($s = #file_get_contents($sLogPath)) {
$s = trim($s);
if (!empty($s)) {
return $s;
}
}
return false;
}
/**
* Get Log content in list mode divided by EOL
* #param string $sLogPath
* #return array
*/
static public function GetList($sLogPath) {
if ($sList = self::GetLog($sLogPath)) {
return explode(PHP_EOL,trim($sList));
}
}
/**
* Check if directory exists and chmod 775
* #param $directory
*/
static public function CheckDirectory($directory) {
$directory = rtrim($directory, '/\\');
if (is_dir($directory)) #chmod($directory, 0775);
else {
if (!mkdir($directory, 0755, true)) {
//self::AddError ('Directory does not exist', $directory);
return false;
}
}
}
/**
* Check if file exist
* Check if file has zero size
* #param string $filename
*/
static public function CheckFile($filename) {
if (file_exists($filename) && !is_file($filename)) {
self::log("NO VALID FILEPATH: {$filename}");
return false;
} else if(!file_exists($filename)) {
self::log("FILE DOESN'T EXIST: {$filename}");
return self::FILE_NOEXIST;
} else if(!filesize($filename)) {
self::log("FILE ZEROSIZE: {$filename}");
return self::FILE_ZEROSIZE;
}
else return true;
}
/**
* Check if file1 is identical to file2
* #param string $filename1
* #param string $filename2
*/
static public function CheckIdentity($filename1, $filename2) {
if (self::CheckFile($filename1)>0 && self::CheckFile($filename2)>0) {
if (filesize($filename1)===filesize($filename2)) {
self::log("FILES: {$filename1} AND {$filename2} ARE IDENTICAL");
return true;
}
self::log("FILES: {$filename1} AND {$filename2} ARE DIFFERENT");
return false;
}
}
/**
* Copy file from $source to $dest
* Make www-data owner
* Make perms 755
*/
static public function Copy($source, $dest = null) {
self::log("COPY {$source} TO {$dest}");
if (self::$bTest) return true;
self::CheckDirectory(dirname($dest));
// copy
$sCmd = "cp -f '{$source}' '{$dest}'";
self::exec($sCmd);
// chown
$sCmdChown = "chown www-data:www-data '{$dest}'";
self::exec($sCmdChown);
// chmod
$sCmdChmod = "chmod 775 '{$dest}'";
self::exec($sCmdChmod);
return true;
}
/**
* Delete file
*/
static public function Delete($source) {
self::log("DELETE {$source}");
if (self::$bTest) return true;
// chmod
$sCmdChmod = "rm '{$source}'";
self::exec($sCmdChmod);
}
/**
* Get free space of catalog or storage in bytes
* #param string $sDir
*/
static public function GetFreeSpace($sDir = '') {
return disk_free_space($sDir);
}
/**
* Get total space of catalog or storage in bytes
* #param string $sDir
*/
static public function GetTotalSpace($sDir = '') {
return disk_total_space($sDir);
}
/**
* Get used space of catalog or storage in percents
* #param string $sDir
*/
static public function GetUsage($sDir = '') {
//$sCmd = "df -k {$sDir} | grep -Eo '[0-9]+%'";
//return intval(self::exec($sCmd));
return round(1-self::GetFreeSpace($sDir)/self::GetTotalSpace($sDir),4)*100;
}
/**
* Exec command wrapper
*/
static public function exec($sCmd = null, &$aVar=null) {
if (empty($sCmd)) {
return;
}
self::log($sCmd);
return exec($sCmd, $aVar);
}
static public function log($sStr) {
echo "{$sStr}<br>\n";
}
/**
* Get open files log
* #return string
*/
static public function getOpenFilesContent() {
if (self::$sOpenFiles === null) {
self::$sOpenFiles = self::GetLog(self::$sLogOpenFiles);
}
return self::$sOpenFiles;
}
/**
* Get an array of open files
* #param array $aExt
* #return array of arrays ['views','size','path']
*/
public function getOpenFiles($aExt=array()) {
$aOpenFiles = self::GetList(self::$sLogOpenFiles);
$aOpenFiles = array_map( create_function('$v', '
$v = trim($v);
$aFile = preg_split("#[\t\s]+#",$v);
$oFile = new VserverFile($aFile[2]);
$oFile->setViews($aFile[0]);
return $oFile;'),
$aOpenFiles
);
//echo "<pre>";
//print_r($aOpenFiles);
return $aOpenFiles;
}
public function getFileList($sDir=null, $aExt=array('flv','mp4'), $sGrep='') {
if (!$sDir || empty($aExt)) return;
$aCmdExt = "\( -name '*."
.implode("' -o -name '*.",$aExt)
."' \)";
$sCmd = "find {$sDir} {$aCmdExt} -mmin +".self::$iDaysToDelete."";
// sort by date
//$sCmd = "find {$sDir} {$aCmdExt} -printf '%T# %p\n'| sort -k 1n | cut -d' ' -f2-";
if (!empty($sGrep)) {
$sCmd.= " | {$sGrep}";
}
self::exec($sCmd,$aFiles);
return $aFiles;
}
/**
*
* Get list of storages
*/
public function getStorages() {
$sCmd = "df -k | grep -Eo 'storage[0-9]+$'";
self::exec($sCmd,$aStorages);
return $aStorages;
}
/**
*
* Get list of megastorages
*/
public function getMegaStorages() {
$sCmd = "df -k | sort -k5 -r | grep -Eo 'megastorage[0-9]+$'";
self::exec($sCmd,$aStorages);
return $aStorages;
}
/**
* Copy opened files to SSD
*/
public function copyToSSD() {
$this->setLock(__FUNCTION__.'.lock');
if($this->isLock()) {
self::log("Process has already started.");
return;
}
$iCopied = 0;
$iIgnored = 0;
$aOpenedFiles = $this->GetOpenFiles();
foreach ($aOpenedFiles as $oFile) {
if (self::GetUsage(self::$sSSDPath) < self::$iSSDUsage) {
if ($oFile->getViews() >=self::$iMinViews && $oFile->synchronize()) $iCopied++;
else $iIgnored++;
} else {
break;
}
}
self::log("COPIED: {$iCopied}");
self::log("IGNORED: {$iIgnored}");
self::log("SSD USAGE: ".self::GetUsage(self::$sSSDPath)."%");
}
/**
* DELETE FROM SSD
*/
public function deleteFromSSD() {
$this->setLock(__FUNCTION__.'.lock');
if($this->isLock()) {
self::log("Process has already started.");
return;
}
$iDeleted = 0;
$iIgnored = 0;
/*
* Get megastorages sorted by usage desc,
*/
$aMegaStorages = $this->getMegaStorages();
foreach ($aMegaStorages as &$sMegaStorage) {
/*
* Get files in current megastorage
*/
$aFileList = $this->getFileList('/'.$sMegaStorage);
if (empty($aFileList)) {
self::log("NO FILES FOUND in {$sMegaStorage}");
continue;
}
$aFileList = array_map(
create_function('$v', '
$oFile = new VserverFile(trim($v));
return $oFile;
'),
$aFileList
);
/*
* Delete files until appropriate usage
*/
$iFreeSpace = self::GetFreeSpace('/'.$sMegaStorage);
$iTotalSpace = self::GetTotalSpace('/'.$sMegaStorage);
foreach ($aFileList as &$oFile) {
$iUsageCurrent = round(1-$iFreeSpace/$iTotalSpace,4)*100;
self::Log($sMegaStorage." ".$iUsageCurrent."%");
if ($iUsageCurrent > self::$iSSDDeleteUsage) {
if (!$oFile->isOpened()) {
$iFreeSpace += $oFile->getSize();
$oFile->synchronize();
$oFile->deleteFromSSD();
$iDeleted++;
} else $iIgnored++;
} else {
break;
}
}
}
/*
$aFileList = $this->getFileList(self::$sSSDPath);
if (empty($aFileList)) {
self::log("NO FILES FOUND");
return;
}
$aFileList = array_map(
create_function('$v', '
$oFile = new VserverFile(trim($v));
return $oFile;
'),
$aFileList
);
foreach ($aFileList as $oFile) {
if (self::GetUsage(self::$sSSDPath) > self::$iSSDDeleteUsage) {
if (!$oFile->isOpened()) {
$oFile->synchronize();
$oFile->deleteFromSSD();
$iDeleted++;
} else $iIgnored++;
} else {
break;
}
}
*/
self::log("DELETED: {$iDeleted}");
self::log("IGNORED: {$iIgnored}");
self::log("SSD USAGE: ".self::GetUsage(self::$sSSDPath)."%");
}
/**
* DELETE FROM SSD
* USING ABSOLUTE VALUE of $iSSDDeleteUsageAbsolute
*/
public function deleteFromSSDAbsolute() {
$this->setLock(__FUNCTION__.'.lock');
if($this->isLock()) {
self::log("Process has already started.");
return;
}
$iDeleted = 0;
$iIgnored = 0;
/*
* Get megastorages sorted by usage desc,
*/
$aMegaStorages = $this->getMegaStorages();
foreach ($aMegaStorages as &$sMegaStorage) {
/*
* Get files in current megastorage
*/
$aFileList = $this->getFileList('/'.$sMegaStorage);
if (empty($aFileList)) {
self::log("NO FILES FOUND in {$sMegaStorage}");
continue;
}
$aFileList = array_map(
create_function('$v', '
$oFile = new VserverFile(trim($v));
return $oFile;
'),
$aFileList
);
/*
* Delete files until appropriate usage
*/
$iFreeSpace = self::GetFreeSpace('/'.$sMegaStorage);
foreach ($aFileList as &$oFile) {
self::Log($sMegaStorage." ".(round($iFreeSpace/1024/1024/1024,2))." G");
if ($iFreeSpace < self::$iSSDDeleteUsageAbsolute) {
if (!$oFile->isOpened()) {
$iFreeSpace += $oFile->getSize();
$oFile->synchronize();
$oFile->deleteFromSSD();
$iDeleted++;
} else $iIgnored++;
} else {
break;
}
}
}
self::log("DELETED: {$iDeleted}");
self::log("IGNORED: {$iIgnored}");
self::log("FREE SPACE: ".(round($iFreeSpace/1024/1024/1024,2))." G");
}
/**
*
* Get Identical files on storages
*/
public function getIdentical() {
$this->setLock(__FUNCTION__.'.lock');
if($this->isLock()) {
self::log("Process has already started.");
return;
}
$aStorages = $this->getStorages();
foreach ($aStorages as &$sStorage) {
$sDir = "/{$sStorage}/".self::$sHTTPFolder;
$aList[$sStorage] = $this->getFileList($sDir,array('flv'), "grep -Eo '".self::$sHTTPFolder.".*$'");
}
$iStorages = sizeof($aList);
for ($i=0; $i<$iStorages; $i++) {
reset($aList);
$sPrimaryStorage = key($aList);
$aPrimaryFiles = array_shift($aList);
foreach($aList as $sSecondaryStorage=>&$aSecondaryFiles) {
self::log("{$sPrimaryStorage} <-- {$sSecondaryStorage}");
//array of identical files
$aIdentical = array_intersect($aPrimaryFiles, $aSecondaryFiles);
if (!empty($aIdentical)) {
// get sizes
foreach ($aIdentical as $sFile) {
$iPrimaryPath = "/{$sPrimaryStorage}/".$sFile;
$iPrimarySize = filesize($iPrimaryPath);
$iSecondaryPath = "/{$sSecondaryStorage}/".$sFile;
$iSecondarySize = filesize($iSecondaryPath);
self::log("| {$iPrimaryPath} <-- {$iPrimarySize}");
self::log("| {$iSecondaryPath} <-- {$iSecondarySize}");
// delete if sizes are identical
if ($iPrimarySize == $iSecondarySize) self::Delete($iSecondaryPath);
// if any have null size
else if ($iPrimarySize * $iSecondarySize) continue;
else if (filectime($iPrimarySize)>filectime($iSecondaryPath)) self::Delete($iSecondaryPath);
else if (filectime($iSecondaryPath)>filectime($iPrimarySize)) self::Delete($iPrimarySize);
}
}
}
}
//echo "<pre>";
//print_r(array_intersect($aPrimaryFiles, $aSecondaryFiles));
//echo "</pre>";
}
/**
* Ñîçäàåò áëîêèðîâêó íà ïðîöåññ
*/
public function setLock($sLockFile=null) {
if(!empty($sLockFile)) {
$this->oLockFile=fopen($sLockFile,'a');
}
}
/**
* Ïðîâåðÿåò óíèêàëüíîñòü ñîçäàâàåìîãî ïðîöåññà
*/
public function isLock() {
return ($this->oLockFile && !flock($this->oLockFile, LOCK_EX|LOCK_NB));
}
/**
* Ñíèìàåò áëîêèðîâêó íà ïîâòîðíûé ïðîöåññ
*/
public function unsetLock() {
return ($this->oLockFile && #flock($this->oLockFile, LOCK_UN));
}
public function __construct() {
self::$iTimeStart = microtime(true);
}
public function __destruct() {
self::$iTimeEnd = microtime(true);
$iTimeExecution = round(self::$iTimeEnd - self::$iTimeStart,3);
$iUsageMem = memory_get_usage(true)/1024/1024; //Mb
$iUsageMemPeak = memory_get_peak_usage(true)/1024/1024; //Mb
self::log("Execution time: {$iTimeExecution} s");
self::log("Memory Usage: {$iUsageMem} Mb");
self::log("Memory Peak Usage: {$iUsageMemPeak} Mb");
$this->unsetLock();
}
/**
* Class autoloader
*
* #param unknown_type $sClassName
*/
public static function autoload($sClassName) {
require_once("{$sClassName}.class.php");
}
}
spl_autoload_register(array('Vserver','autoload'));
?>
Since array_map complains about $aOpenFiles not being an array you might want to take a look at that variable.
public function getOpenFiles($aExt=array()) {
$aOpenFiles = self::GetList(self::$sLogOpenFiles);
if ( !is_array($aOpenFiles) ) {
var_dump($aOpenFiles);
die('not an array');
}
$aOpenFiles = array_map( ...
quote:
static public function GetList($sLogPath) {
if ($sList = self::GetLog($sLogPath)) {
return explode(PHP_EOL,trim($sList));
}
}
... and if $sList evaluates to false (which may very well happen, since GetLog returns false on some conditions) this function returns nothing.
You have to check for that condition somewhere. Where depends on how you want the script to react on what condition. e.g.
static public function GetList($sLogPath) {
if ($sList = self::GetLog($sLogPath)) {
return explode(PHP_EOL,trim($sList));
}
return array();
}
would "fix" the error, but if that's feasible is another question; it may only "hide" a symptom - not the underlying problem. Must there be a log file? Must GetList(GetLog()) return a (non-empty) array? and so on and on....

Categories