PHP out of memory with CDataProviderIterator (Yii) - php

I am having an out of memory issue with my PHP script on the Yii framework. I've tried to do quite a bit of debugging. I'm using a CDataProviderIterator because the Yii documentation says this about it:
For example, the following code will iterate over all registered users (active record class User) without running out of memory, even if there are millions of users in the database.
This code iterates over around 1.5 million records and it runs out of memory in its attempt. I'm looking for any kind of help of why it might be doing this. Thanks!
public function foo($model, $relations) {
$dataProvider = new CActiveDataProvider($model, array('criteria' => $model->dbCriteria));
$iterator = new CDataProviderIterator($dataProvider, 200);
$this->modelsToArray($iterator, $relations, $model_as_array = array());
}
public function modelsToArray($model, $relations, $model_as_array = array()) {
$preparedRelations = $this->prepareRelations($relations);
if (is_null($model))
{
return array();
}
$model_as_array = array();
if (get_class($model) === 'CDataProviderIterator') {
foreach ($model as $row) {
$model_as_array[] = $this->modelsToArrayHelper($preparedRelations, $row);
}
}
else {
$model_as_array[] = $this->modelsToArrayHelper($preparedRelations, $model);
}
return $model_as_array;
}
private function modelsToArrayHelper($relations, $listOfModels) {
$listOfArrayModels = array();
if(!is_array($listOfModels)){
return $this->modelToArrayHelper($listOfModels, $relations);
}
foreach ($listOfModels as $index => $model)
{
$listOfArrayModels[$index] = $this->modelToArrayHelper($model, $relations);
}
return $listOfArrayModels;
}
private function modelToArrayHelper($model, $relations){
$model_as_array = $this->processAttributes($model);
foreach ($relations as $relationIndex => $relation)
{
$relationName = is_string($relationIndex) ? $relationIndex : $relation;
if(empty($model->$relationName))
continue;
if ($model->relations()[$relationName][0] != CActiveRecord::STAT)
{
$subRelations = is_array($relation) ? $relation : array();
$model_as_array[$relationName] = $this->modelsToArrayHelper($subRelations, $model->$relationName);
}
else
{
$model_as_array[$relationName] = $model->$relationName;
}
}
return $model_as_array;
}

i believe you are trying to convert the CDataProviderIterator to a Php array.
so for placing the array you need more memory
ini_set('memory_limit', '-1');
this sets the use of ram to max usage
ini_set('memory_limit', '512M');
this sets the ram usage to 512 Mb
Can you make your code like this just to make sure you are not using too much ram
public function foo($model, $relations) {
$dataProvider = new CActiveDataProvider($model, array('criteria' => $model->dbCriteria));
$iterator = new CDataProviderIterator($dataProvider, 200);
$preparedRelations = $this->prepareRelations($relations);
foreach ($iterator as $row) {
print_r ($this->modelsToArrayHelper($preparedRelations, $row));
}
}
and

Related

Request too heavy

I try to retrieve for each region the number of cities with current events.
So I started doing this:
$regionCities = [];
foreach ($regions as $region) {
$regionCities[$region->getId()] = $cityRepository->getCitiesByRegion($region);
}
dump($regionCities);
$regionCitiesNumber = [];
foreach ($regionCities as $index => $region) {
foreach ($region as $city) {
$regionCitiesNumber[$index] = count($city->getCurrentEvents());
}
}
My dump returns me this:
dump
The problem is, it crashes my script, and I suddenly get a blank page when I dump regionCitiesNumber.
getCurrentEvents is a method of my City entity that will retrieve all current events.
public static function createCurrentEventsCriteria(): Criteria
{
return Criteria::create()
->where(Criteria::expr()->gte('endDate', new DateTime('00:00:00')))
->orderBy(['id' => 'DESC'])
;
}
public function getCurrentEvents(): Collection
{
$criteria = EventRepository::createCurrentEventsCriteria();
return $this->events->matching($criteria);
}
Use error logs or try using "Try Catch" wrapper
$regionCities = [];
foreach ($regions as $region) {
$regionCities[$region->getId()] = $cityRepository->getCitiesByRegion($region);
}
//dump($regionCities);
try {
$regionCitiesNumber = [];
foreach ($regionCities as $index => $region) {
foreach ($region as $city) {
$regionCitiesNumber[$index] = count($city->getCurrentEvents());
}
}
} catch (\Throwable $t) {
dd($t);
}
So I tried:
return $this->events->matching($criteria)->count();
And I dumped on regionCitiesNumber, it gave me a totally wrong result:
For example for the ARA region, I end up with 0 as a result while there are 1895 events distributed over all the departments ...
As a result, most regions result in 0, sometimes 1, sometimes 2 ...
In addition, it takes a long time to load my page, isn't there a softer solution?

read laravel object and get data

I am trying to read laravel object and assign that into a new variable I have no idea to do that please someone help
THIS IS MY FUNCTION
public function nextToJumpGrs(CompetitionService $competitionService, CachedEventResourceService $cachedEventResourceService, ConfigurationRepositoryInterface $configurationRepository,MarketRepository $marketTypeRepository)
{
$nextToJump = $cachedEventResourceService->nextToJump();
$markets=array();
$config = $configurationRepository->getConfigByName('sports_settings', true);
$competitions = $competitionService->getCompetitionsByN2JEventResourcesGrs($nextToJump->slice(0, array_get($config, 'next_to_jump_events')));
return $competitions;
}
}
this is what $competitions return
I want to read those markets[] and products[] and assign to another variable please someone help
Just typecast it
$array = (array) $competitions;
Then loop through it
$markets = [];
$products = [];
foreach($array as $item) {
dump($item['id']);
dump($item['name']);
//...
foreach($item['events'] as $event) {
dump($event['id']);
dump($event['name']);
//...
$markets[$item['id']][] = $event['markets'];
foreach($event['markets'] as $market) {
dump($market['id']);
dump($market['name']);
dump($market['line']);
//...
}
$products[$item['id']][] = $event['products'];
foreach($event['products'] as $product) {
dump($product['product_id']);
dump($product['bet_type']);
dump($product['product_code']);
//...
}
}
}

Array Filter limit amount of results PHP

I have the following method
public function getNextAvailableHousesToAttack(\DeadStreet\ValueObject\House\Collection $collection, $hordeSize)
{
$houses = $collection->getHouses();
$housesThatCanBeAttacked = array_filter($houses, function($house) use (&$hordeSize) {
if(!isset($house)) {
return false;
}
$house = $this->houseModel->applyMaxAttackCapacity($house, $hordeSize);
if($this->houseModel->isAttackable($house)) {
return $house;
}
return false;
});
return $housesThatCanBeAttacked;
However, this array can be huge.
I want to limit $housesThatCanBeAttacked to whatever the size of $hordeSize is set to, as I only need as many houses as there are zombies in the horde to attack this round.
However, this array $housesThatCanBeAttacked could end up containing 1 million houses, where there are only 100 in the zombie horde.
Is there a way to limit the size of this array built from the callback?
You could simply use a loop, and stop processing the array when you have enough houses.
$houses = $collection->getHouses();
housesThatCanBeAttacked[];
$i = 0;
foreach ($houses as $house) {
$house = $this->houseModel->applyMaxAttackCapacity($house, $hordeSize);
if ($this->houseModel->isAttackable($house)) {
housesThatCanBeAttacked[] = $house;
if (++$i == $hordeSize) {
break;
}
}
}
I would add a counter of houses outside of callback and use it inside callback to skip all excessive houses. Here is how a solution can look like:
public function getNextAvailableHousesToAttack(\DeadStreet\ValueObject\House\Collection $collection, $hordeSize)
{
$houses = $collection->getHouses();
$counter = $hordeSize;
$housesThatCanBeAttacked = array_filter($houses, function($house) use (&$hordeSize, &$counter) {
if($counter == 0 && !isset($house)) {
return false;
}
$house = $this->houseModel->applyMaxAttackCapacity($house, $hordeSize);
if($this->houseModel->isAttackable($house)) {
$counter--;
return $house;
}
return false;
});
return $housesThatCanBeAttacked;
This way you array_filter won't return more then $counter values.

Why not all threads are completed?

I've tried example from this Joe answer https://stackoverflow.com/a/32187103/2229367 and it works great, but when i tried to edit this code a little:
$pool = new Pool(4);
while (#$i++<10) {
$pool->submit(new class($i) extends Collectable {
public function __construct($id) {
$this->id = $id;
}
public function run() {
printf(
"Hello World from %d\n", $this->id);
$this->html = file_get_contents('http://google.fr?q=' . $this->query);
$this->setGarbage();
}
public $id;
public $html;
});
}
while ($pool->collect(function(Collectable $work){
printf(
"Collecting %d\n", $work->id);
var_dump($work->html);
return $work->isGarbage();
})) continue;
$pool->shutdown();
Count of "Hello world" differs from count of "Collecting".
Docs are out of date.
What about this problem?
Worker::collect is not intended to enable you to reap results; It is non-deterministic.
Worker::collect is only intended to run garbage collection on objects referenced in the stack of Worker objects.
If the intention is to process each result as it becomes available, the code might look something like this:
<?php
$pool = new Pool(4);
$results = new Volatile();
$expected = 10;
$found = 0;
while (#$i++ < $expected) {
$pool->submit(new class($i, $results) extends Threaded {
public function __construct($id, Volatile $results) {
$this->id = $id;
$this->results = $results;
}
public function run() {
$result = file_get_contents('http://google.fr?q=' . $this->id);
$this->results->synchronized(function($results, $result){
$results[$this->id] = $result;
$results->notify();
}, $this->results, $result);
}
private $id;
private $results;
});
}
do {
$next = $results->synchronized(function() use(&$found, $results) {
while (!count($results)) {
$results->wait();
}
$found++;
return $results->shift();
});
var_dump($next);
} while ($found < $expected);
while ($pool->collect()) continue;
$pool->shutdown();
?>
This is obviously not very tolerant of errors, but the main difference is that I use a shared Volatile collection of results, and I synchronize properly to fetch results in the main context as they become available.
If you wanted to wait for all results to become available, and possibly avoid some contention for locks - which you should always try to avoid if you can - then the code would look simpler, something like:
<?php
$pool = new Pool(4);
$results = new Volatile();
$expected = 10;
while (#$i++ < $expected) {
$pool->submit(new class($i, $results) extends Threaded {
public function __construct($id, Volatile $results) {
$this->id = $id;
$this->results = $results;
}
public function run() {
$result = file_get_contents('http://google.fr?q=' . $this->id);
$this->results->synchronized(function($results, $result){
$results[$this->id] = $result;
$results->notify();
}, $this->results, $result);
}
private $id;
private $results;
});
}
$results->synchronized(function() use($expected, $results) {
while (count($results) != $expected) {
$results->wait();
}
});
var_dump(count($results));
while ($pool->collect()) continue;
$pool->shutdown();
?>
Noteworthy that the Collectable interface is already implemented by Threaded in the most recent versions of pthreads - which is the one you should be using ... always ...
The docs are out of date, sorry about that ... one human ...
Pthreads V3 is much less forgiven than V2.
collect is a no go in V3.
Rule n°1: I do all my queries inside the threads, avoiding to pass too large amount of datas inside them. This was ok with V2, not anymore with V3. I keep passed arguments to workers as neat as possible. This also allows faster process.
Rule n°2: I do not go over the number of CPU threads available for each pool and chunck them accordingly with a loop. This way I make sure there are no memory overhead with a ton of pools and each time a loop is done, I force a garbage collection. This turned out to be necessary for me due to very high Ram needs across threads, might not be your case but make sure your consumed ram is not going over your php limit. More you passed arguments to the threads are big, more the ram will go up fast.
Rule n°3: Properly declare your object arrays in workers with (array) to make sure all results are returned.
Here is a basic rewritten working example , following the 3 rules as close as I can do per your example:
uses an array of queries to be multithreaded.
a collectable implement to grab the results in place of collect.
batches of pools according to the CPU nb of threads to avoid ram overheads.
threaded queries, each one having his connection, not passed across workers.
pushing all the results inside an array at the end.
code:
define("SQLHOST", "127.0.0.1");
define("SQLUSER", "root");
define("SQLPASS", "password");
define("SQLDBTA", "mydatabase");
$Nb_of_th=12; // (6 cpu cores in this example)
$queries = array_chunk($queries, ($Nb_of_th));// whatever list of queries you want to pass to the workers
$global_data=array();// all results from all pool cycles
// first we set the main loops
foreach ($queries as $key => $chunks) {
$pool = new Pool($Nb_of_th, Worker::class);// 12 pools max
$workCount = count($chunks);
// second we launch the submits
foreach (range(1, $workCount) as $i) {
$chunck = $chunks[$i - 1];
$pool->submit(new MyWorkers($chunck));
}
$data = [];// pool cycle result array
$collector = function (\Collectable $work) use (&$data) {
$isGarbage = $work->isGarbage();
if ($isGarbage) {
$data[] = $work->result; // thread result
}
return $isGarbage;
};
do {
$count = $pool->collect($collector);
$isComplete = count($data) === $workCount;
} while (!$isComplete);
array_push($global_data, $data);// push pool results into main
//complete purge
unset($data);
$pool->shutdown();
unset($pool);
gc_collect_cycles();// force garbage collector before new pool cycle
}
Var_dump($global_data); // results for all pool cycles
class MyWorkers extends \Threaded implements \Collectable {
private $isGarbage;
public $result;
private $process;
public function __construct($process) {
$this->process = $process;
}
public function run() {
$con = new PDO('mysql:host=' . SQLHOST . ';dbname=' . SQLDBTA . ';charset=UTF8', SQLUSER, SQLPASS);
$proc = (array) $this->process; // important ! avoid volatile destruction in V3
$stmt = $con->prepare($proc);
$stmt->execute();
$obj = $stmt1->fetchall(PDO::FETCH_ASSOC);
/* do whatever you want to do here */
$this->result = (array) $obj; // important ! avoid volatile destruction in V3
$this->isGarbage = true;
}
public function isGarbage() : bool
{
return $this->isGarbage;
}
}

PHP: Modifying array with unknown structure at runtime; what is the most elegant solution?

PROBLEM
I have a function that takes in a nested array where the structure and nesting of the array is unknown at run-time. All that is known is some of the target fieldnames and desired values of some of the leafs.
QUESTIONS
1) I am hoping to modify this unknown structure and still have the code be readable and easily understood by fellow programmers. What (if any) solution will allow me to do things like this in PHP?
// Pseudo-code for things I would like to be able to do
// this is kinda like the same thing as XPATH, but for native PHP array
// find *every* fname with value of "Brad" and change it to "Brian"
$mydata->find_all('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
// find *the first* fave_color and set it to "Green"
$mydata->find('*:fave_color')->get(0)->set_equal_to('Green');
2) If there is nothing out there that will let me do this, is there something, anything, that at least comes close to the spirit of what I am hoping to accomplish here?
SAMPLE ARRAY
$mydata = array(
'people' => array(
array('fname'=>'Alice'),
array('fname'=>'Brad'),
array('fname'=>'Chris'),
),
'animals' => array(
array('fname'=>'Dino'),
array('fname'=>'Lassie'),
array('fname'=>'Brad'),
),
'settings' => array(
'user_prefs'=>array(
'localhost'=>array(
'fave_color'=>'blue',
),
),
),
'places' => array(
array('state'=>'New york',
'cities'=>array(
'name'=>'Albany',
'name'=>'Buffalo',
'name'=>'Corning',
),
'state'=>'California',
'cities'=>array(
'name'=>'Anaheim',
'name'=>'Bakersfield',
'name'=>'Carlsbad',
),
),
),
);
Although I maintain that you should stick with explicit manipulation as in my previous answer, boredom and intrigue got the better of me ;)
It probably has holes (and clearly lacks docs) but if you insist on this route, it should get you started:
class Finder {
protected $data;
public function __construct(&$data) {
if (!is_array($data)) {
throw new InvalidArgumentException;
}
$this->data = &$data;
}
public function all() {
return $this->find();
}
public function find($expression = null) {
if (!isset($expression)) {
return new Results($this->data);
}
$results = array();
$this->_find(explode(':', $expression), $this->data, $results);
return new Results($results);
}
protected function _find($parts, &$data, &$results) {
if (!$parts) {
return;
}
$currentParts = $parts;
$search = array_shift($currentParts);
if ($wildcard = $search == '*') {
$search = array_shift($currentParts);
}
foreach ($data as $key => &$value) {
if ($key === $search) {
if ($currentParts) {
$this->_find($currentParts, $value, $results);
} else {
$results[] = &$value;
}
} else if ($wildcard && is_array($value)) {
$this->_find($parts, $value, $results);
}
}
}
}
class Results {
protected $data;
public function __construct(&$data) {
$this->data = $data;
}
public function get($index, $limit = 1) {
$this->data = array_slice($this->data, $index, $limit);
return $this;
}
public function set_equal_to($value) {
foreach ($this->data as &$datum) {
$datum = $value;
}
}
public function __call($method, $args) {
if (!preg_match('/^where_?(key|value)_?(eq|contains)$/i', $method, $m)) {
throw new BadFunctionCallException;
}
if (!isset($args[0])) {
throw new InvalidArgumentException;
}
$operand = $args[0];
$isKey = strtolower($m[1]) == 'key';
$method = array('Results', '_compare' . (strtolower($m[2]) == 'eq' ? 'EqualTo' : 'Contains'));
$ret = array();
foreach ($this->data as $key => &$datum) {
if (call_user_func($method, $isKey ? $key : $datum, $operand)) {
$ret[] = &$datum;
}
}
$this->data = $ret;
return $this;
}
protected function _compareEqualTo($value, $test) {
return $value == $test;
}
protected function _compareContains($value, $test) {
return strpos($value, $test) !== false;
}
}
$finder = new Finder($mydata);
$finder->find('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
$finder->find('*:fave_color')->get(0)->set_equal_to('Green');
$finder->find('places:*:cities:*:name')->where_value_contains('ba')->set_equal_to('Stackoton');
print_r($mydata);
There's certainly no native solution for this and the syntax is rather strange. If you want the code to "be readable and easily understood by fellow programmers" please stick to methods that we're used to working with ;)
foreach ($mydata as $type => &$data) {
foreach ($data as &$member) {
if (isset($member['fname']) && $member['fname'] == 'Brad') {
$member['fname'] = 'Brian';
}
}
}
It's admittedly more verbose, but there's much less chance of confusion.

Categories