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;
}
}
Related
I am trying to do a very simple but numerous iterations task. I choose 7 random serial numbers from an array of 324000 serial numbers and place them in another array and then search that array to see if a particular number is within it, execute another script and fwrite out how many times the looked for number is in the array.
This goes fairly fast in single thread. But when I put it in pthreads, even one single pthread running is 100x slower than single thread. The workers are not sharing any resources (i.e. the grab all info from their own folders and write info to their own folders)..fwrite bottlenecks is not the problem. The problem is with the arrays which I note below. Am I running into a cache line problem, where the arrays although they have separate variables are still sharing the same cache line? Sigh...much appreciate your help, in figuring out why the arrays are slowing it to a crawl.
<?php
class WorkerThreads extends Thread
{
private $workerId;
private $linesId;
private $linesId2;
private $c2_result;
private $traceId;
public function __construct($id,$newlines,$newlines2,$xxtrace)
{
$this->workerId = $id;
$this->linesId = (array) $newlines;
$this->linesId2 = (array) $newlines2;
$this->traceId = $xxtrace;
$this->c2_result= (array) array();
}
public function run()
{
for($h=0; $h<90; $h++) {
$fp42=fopen("/folder/".$this->workerId."/count.txt","w");
for($master=0; $master <200; $master++) {
// *******PROBLEM IS IN THE <3000 loop -very slow***********
$b=0;
for($a=0; $a<3000; $a++) {
$zex=0;
while($zex != 1) {
$this->c2_result[0]=$this->linesId[rand(0,324631)];
$this->c2_result[1]=$this->linesId[rand(0,324631)];
$this->c2_result[2]=$this->linesId[rand(0,324631)];
$this->c2_result[3]=$this->linesId[rand(0,324631)];
$this->c2_result[4]=$this->linesId[rand(0,324631)];
$this->c2_result[5]=$this->linesId[rand(0,324631)];
$this->c2_result[6]=$this->linesId[rand(0,324631)];
if(count(array_flip($this->c2_result)) != count($this->c2_result)) { //echo "duplicates\n";
$zex=0;
} else { //echo "no duplicates\n";
$zex=1;
//exit;
}
}
// *********PROBLEM here too !in_array statement, slowing down******
if(!in_array($this->linesId2[$this->traceId],$this->c2_result)) {
//fwrite($fp4,"nothere\n");
$b++;
}
}
fwrite($fp42,$b."\n");
}
fclose($fp42);
$mainfile3="/folder/".$this->workerId."/count_pthread.php";
$command="php $mainfile3 $this->workerId";
exec($command);
}
}
}
$xxTrack=0;
$lines = range(0, 324631);
for($x=0; $x<56; $x++) {
$workers = [];
// Initialize and start the threads
foreach (range(0, 8) as $i) {
$workers[$i] = new WorkerThreads($i,$lines,$lines2,$xxTrack);
$workers[$i]->start();
$xxTrack++;
}
// Let the threads come back
foreach (range(0, 8) as $i) {
$workers[$i]->join();
}
unset($workers);
}
UPDATED CODE
I was able to speed up the original code by 6x times with help from #tpunt suggestions. Most importantly what I learned is that the code is being slowed down by the calls to rand(). If I could get rid of that, then speed time would be 100x faster. array_rand,mt_rand() and shuffle() are even slower. Here is the new code:
class WorkerThreads extends Thread
{
private $workerId;
private $c2_result;
private $traceId;
private $myArray;
private $myArray2;
public function __construct($id,$xxtrace)
{
$this->workerId = $id;
$this->traceId = $xxtrace;
$c2_result=array();
}
public function run()
{
////////////////////THE WORK TO BE DONE/////////////////////////
$lines = file("/fold/considers.txt",FILE_IGNORE_NEW_LINES);
$lines2= file("/fold/considers.txt",FILE_IGNORE_NEW_LINES);
shuffle($lines2);
$fp42=fopen("/fold/".$this->workerId."/count.txt","w");
for($h=0; $h<90; $h++) {
fseek($fp42, 0);
for($master=0; $master <200; $master++) {
$b=0;
for($a=0; $a<3000; $a++) {
$zex=0;
$myArray = [];
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
while (count($myArray) !== 7) {
$myArray[rand(0,324631)] = true;
}
if (!isset($myArray[$lines2[$this->traceId]])) {
$b++;
}
}
fwrite($fp42,$b."\n");
}
$mainfile3="/newfolder/".$this->workerId."/pthread.php";
$command="php $mainfile3 $this->workerId";
exec($command);
}//END OF H LOOP
fclose($fp42);
}
}
$xxTrack=0;
$p = new Pool(5);
for($b=0; $b<56; $b++) {
$tasks[$b]= new WorkerThreads($b,$xxTrack);
$xxTrack++;
}
// Add tasks to pool queue
foreach ($tasks as $task) {
$p->submit($task);
}
// shutdown will wait for current queue to be completed
$p->shutdown();
Your code is just incredibly inefficient. There are also a number of problems with it - I've made a quick breakdown of some of these things below.
Firstly, you are spinning up over 500 threads (9 * 56 = 504). This is going to be very slow because threading in PHP requires a shared-nothing architecture. This means that a new instance of PHP's interpreter will need to be created for each thread you create, where all classes, interfaces, traits, functions, etc, will need to be copied over to the new interpreter instance.
Perhaps more to the point, though, is that your 3 nested for loops are performing 54 million iterations (90 * 200 * 3000). Multiply this by the 504 threads being created, and you can soon see why things are becoming sluggish. Instead, use a thread pool (see pthreads' Pool class) with a more modest amount of threads (try 8, and go from there), and cut down on the iterations being performed per thread.
Secondly, you are opening up a file 90 times per thread (so a total of 90 * 504 = 45360). You only need one file handler per thread.
Thirdly, utilising actual PHP arrays inside of Threaded objects makes them read-only. So with respect to the $this->c2_result property, the code inside of your nested while loop should not even work. Not to mention that the following check does not look for duplicates:
if(count(array_flip($this->c2_result)) != count($this->c2_result))
If you avoid casting the $this->c2_result property to an array (therefore making it a Volatile object), then the following code could instead replace your while loop:
$keys = array_rand($this->linesId, 7);
for ($i = 0; $i < 7; ++$i) {
$this->c2_result[$this->linesId[$keys[$i]]] = true;
}
By setting the values as the keys in $this->c2_result we can remove the subsequent in_array function call to search through the $this->c2_result. This is done by utilising a PHP array as a hash table, where the lookup time for a key is constant time (O(1)), rather than linear time required when searching for values (with in_array). This enables us to replace the following slow check:
if(!in_array($this->linesId2[$this->traceId],$this->c2_result))
with the following fast check:
if (!isset($this->c2_result[$this->linesId2[$this->traceId]]))
But with that said, you don't seem to be using the $this->c2_result property anywhere else. So (assuming you haven't purposefully redacted code that uses it), you could remove it altogether and simply replace the while loop at check after it with the following:
$found = false;
foreach (array_rand($this->linesId, 7) as $key) {
if ($this->linesId[$key] === $this->linesId2[$this->traceId]) {
$found = true;
break;
}
}
if (!$found) {
++$b;
}
Beyond the above, you could also look at storing the data you're collecting in-memory (as some property on the Threaded object), to prevent expensive disk writes. The results could be aggregated at the end, before shutting down the pool.
Update based up your update
You've said that the rand function is causing major slowdown. Whilst it may be part of the problem, I believe it is actually all of the code inside of your third nested for loop. The code inside there is very hot code, because it gets executed 54 million times. I suggested above that you replace the following code:
$zex=0;
while($zex != 1) {
$c2_result[0]=$lines[rand(0,324631)];
$c2_result[1]=$lines[rand(0,324631)];
$c2_result[2]=$lines[rand(0,324631)];
$c2_result[3]=$lines[rand(0,324631)];
$c2_result[4]=$lines[rand(0,324631)];
$c2_result[5]=$lines[rand(0,324631)];
$c2_result[6]=$lines[rand(0,324631)];
$myArray = (array) $c2_result;
$myArray2 = (array) $c2_result;
$myArray=array_flip($myArray);
if(count($myArray) != count($c2_result)) {//echo "duplicates\n";
$zex=0;
} else {//echo "no duplicates\n";
$zex=1;
//exit;
}
}
if(!in_array($lines2[$this->traceId],$myArray2)) {
$b++;
}
with a combination of array_rand and foreach. Upon some initial tests, it turns out that array_rand really is outstandingly slow. But my hash table solution to replace the in_array invocation still holds true. By leveraging a PHP array as a hash table (basically, store values as keys), we get a constant time lookup performance (O(1)), as opposed to a linear time lookup (O(n)).
Try replacing the above code with the following:
$myArray = [];
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
$myArray[rand(0,324631)] = true;
while (count($myArray) !== 7) {
$myArray[rand(0,324631)] = true;
}
if (!isset($myArray[$lines2[$this->traceId]])) {
$b++;
}
For me, this resulted in a 120% speedup.
As for further performance, you can (as mentioned above, again) store the results in-memory (as a simple property) and perform a write of all results at the end of the run method.
Also, the garbage collector for pthreads is not deterministic. It should therefore not be used to retrieve data. Instead, a Threaded object should be injected into the worker thread, where data to be collected should be saved to this object. Lastly, you should shutdown the pool after garbage collection (which, again, should not be used in your case).
despite it is unclear about your code and what $newlines and $newlines2 are, so, I am just guessing here...
something like this ?
The idea is to avoid as much as possible fopen and fwrite in your loop.
1 - open it only once in the construct.
2 - concat your chain in your loop.
3 - write it only once after the loop.
class WorkerThreads extends Thread {
private $workerId;
private $linesId;
private $linesId2;
private $c2_result;
private $traceId;
private $fp42;
private $mainfile3;
public function __construct($id, $newlines, $newlines2, $xxtrace) {
$this->workerId = $id;
$this->linesId = (array) $newlines;
$this->linesId2 = (array) $newlines2;
$this->traceId = $xxtrace;
$this->c2_result = array();
$this->fp42 = fopen("/folder/" . $id . "/count.txt", "w");
$this->mainfile3 = "/folder/" . $id . "/count_pthread.php";
}
public function run() {
for ($h = 0; $h < 90; $h++) {
$globalf42='';
for ($master = 0; $master < 200; $master++) {//<200
$b = 0;
for ($a = 0; $a < 3000; $a++) {
$zex = 0;
if ($zex != 1) {
for ($ii = 0; $ii < 6; $ii++) {
$this->c2_result[$ii] = $this->linesId[rand(0, 324631)];
}
$zex = (count(array_flip($this->c2_result)) != count($this->c2_result)) ? 0 : 1;
}
if (!in_array($this->linesId2[$this->traceId], $this->c2_result)) {
$b++;
}
}
$globalf42 .= $b . "\n";
}
fwrite($this->fp42, $globalf42);
fclose($this->fp42);
$command = "php $this->mainfile3 $this->workerId";
exec($command);
}
}
}
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
I am getting "Fatal error: Allowed memory size of XXXX bytes exhausted...". I need to iterate through big amount of records, and execute a function to verify of the record fit the criteria which declare many class variables.
foreach ($results as $row)
{
$location = Location::parseDatabaseRow($row);
if ($location->contains($lat, $lon))
{
$found = true;
$locations[] = $location;
break;
}
}
Implementation of the Location class:
public function contains($lat, $lon)
{
$polygon =& new polygon();
.... //Add points to polygons base on location polygons
$vertex =& new vertex($lat, $lon);
$isContain = $polygon->isInside($vertex);
$polygon->res(); //Reset all variable inside polygons
$polygon = null; //Let Garbage Collector clear it whenever
return ($isContain);
}
Shouldn't the $polygon be clear when contain() method is return? What can I do to reduce memory usage?
I am a Java developer, and started to learn PHP. Please help me understand on how to manage stack size and memory allocation and delocation. Thanks in advance.
I am a Java developer, and started to learn PHP.
Here are some corrections which may allow your code to not exhaust memory limit.
use a while. Since your result comes from a database query, you should have the possibility to use fetch() instead of fetchAll() which I assume you are using since you are applying a foreach() on it.
while ($row = $result->fetch()) { // here $result is supposed to be a PDOStatatement.
$location = Location::parseDatabaseRow($row);
if ($location->contains($lat, $lon)) {
$found = true; // where is this used?
$locations[] = $location;
break;
}
}
While uses less memory because not all results are fetched at the same time.
use the ampersand the right way. You are doing a new in each loop. The ampersand is used when you want to pass a value by reference to a function, so that it is affected outside that function scope without the need to return it.
Here, you are using objects, which are somewhat passed by reference by design.
public function contains($lat, $lon) {
$polygon = new polygon();
$vertex = new vertex($lat, $lon);
return $polygon->isInside($vertex);
// no need to reset the values of your polygon, you will be creating a new one on the next loop.
}
for completeness sake here a version using the same polygon object. Notice how I do not use an ampersand because we are passing an object.
$polygon = new polygon();
while ($row = $result->fetch()) { // here $result is supposed to be a PDOStatatement.
$location = Location::parseDatabaseRow($row);
if ($location->contains($lat, $lon, $polygon)) {
$found = true; // where is this used?
$locations[] = $location;
break;
}
}
public function contains($lat, $lon, $polygon) {
//Add points to the passed polygon
$vertex = new vertex($lat, $lon);
$isContain = $polygon->isInside($vertex);
$polygon->res();
// since we eill be using the same $polygon, now we need to reset it
return $isContain;
}
I'm new to PHP and I'm having trouble accessing some class variables.
I have this class:
class emptyClass
{
private $ladderWinsNoCoin = 0;
private $ladderWinsCoin = 0;
private $arenaWinsNoCoin = 0;
private $arenaWinsCoin = 0;
private $ladderLossesNoCoin = 0;
private $ladderLossesCoin = 0;
private $arenaLossesNoCoin = 0;
private $arenaLossesCoin = 0;
public function getProperty($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function upOne($property) {
if (property_exists($this, $property)) {
$this->$property=($property+1);
}
}
public function setProperty($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
}
}
I then create 9 instances of it in an array
/* Create classes in an array so we can call
them with strings from the database*/
$classes = array(
'Druid' => new emptyClass,
'Hunter' => new emptyClass,
'Mage' => new emptyClass,
'Paladin'=> new emptyClass,
'Priest' => new emptyClass,
'Rogue' => new emptyClass,
'Shaman' => new emptyClass,
'Warlock'=> new emptyClass,
'Warrior'=> new emptyClass
);
Next I want to increment the values of the class variables to match data got from a database and this is what I've come up with
foreach ($games as $game){ // Go through the games array from the database
$gameID = current($game); // Unused (for now)
$heroClass = (string)next($game);
$villainClass = (string)next($game); // Unused (for now)
$win = (int)next($game);
$coin = (int)next($game);
$ladder = (int)next($game);
//Add up the values in the class objects
if ($ladder==1&&$coin==1&&$win==1){ // Ladder win with coin
$classes[$heroClass] -> {upOne($ladderWinsCoin)};
} else if ($ladder==1&&$coin==1&&$win==0){ // Ladder loss with coin
$classes[$heroClass] -> {upOne($ladderLossesCoin)};
} else if ($ladder==1&&$coin==0&&$win==1){ // Ladder win without coin
$classes[$heroClass] -> {upOne($ladderWinsNoCoin)};
} else if ($ladder==1&&$coin==0&&$win==0){ // Ladder loss without coin
$classes[$heroClass] -> {upOne($ladderLossesNoCoin)};
} else if ($ladder==0&&$coin==1&&$win==1){ // Arena win with coin
$classes[$heroClass] -> {upOne($arenaLossesCoin)};
} else if ($ladder==0&&$coin==1&&$win==0){ // Arena loss with coin
$classes[$heroClass] -> {upOne($arenaLossesCoin)};
} else if ($ladder==0&&$coin==0&&$win==1){ // Arena win without coin
$classes[$heroClass] -> {upOne($arenaWinsNoCoin)};
} else if ($ladder==0&&$coin==0&&$win==0){ // Arena loss without coin
$classes[$heroClass] -> {upOne($arenaLossesNoCoin)};
}
Where $game is an array from the database that looks something like this
[1, 'Mage', 'Druid', 1, 0, 1]
When it runs I get a fatal error
PHP Fatal error: Call to undefined function setProperty() in /home/vooders/public_html/gameReader.php on line 48
Edit:
So after trying renaming the getter/setter I'm still getting the fatal error, so now I'm sure its how I'm calling the objects.
I'll try to talk you through my thinking
$classes[$heroClass] -> {upOne($ladderWinsCoin)};
If we take this line above, $heroClass will be a string from the database in this example 'Mage'.
Now I want to use this string to call the right object from the $classes array then increment the appropriate variable by 1.
Categorically, I would advise you not to "write code external to a class that knows the class's business."
"I am a class. Therefore, I am alive. Tell me what has happened and I shall respond accordingly. Ask me what you want to know and I shall provide you with the answer. But: Do Not Meddle in the Affairs of Classes, for you are crunchy and taste good with worcestershire sauce!!"
(1) Don't put your sticky fingers on the class's variables "from outside." Tell the Class what has happened, that it may increment or decrement its own properties. (You did say they were private, didn't you? As they should be.)
(2) If you want to know "an answer," which may be based on the value of one or many properties, according to simple or complex logic, then the Class should contain that logic, as it pertains "to itself."
//it should work now
<?
class emptyClass {
private $ladderWinsNoCoin = 5; private $ladderWinsCoin = 0; private $arenaWinsNoCoin = 0; private $arenaWinsCoin = 0;
private $ladderLossesNoCoin = 0; private $ladderLossesCoin = 0; private $arenaLossesNoCoin = 0; private $arenaLossesCoin = 0;
public function getProperty($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function setProperty($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
}
public function upOne($property) {
if (property_exists($this, $property)) {
$this->$property++;
}
}
}
$classes = array(
'Druids' => new emptyClass,
'Elfos' => new emptyClass
);
$classes["Druids"]->setProperty("ladderWinsNoCoin",50);
echo $classes["Druids"]->getProperty("ladderWinsNoCoin") . "<br>";
$classes["Druids"]->upOne("ladderWinsNoCoin");
echo $classes["Druids"]->getProperty("ladderWinsNoCoin"). "<br>";
$classes["Elfos"]->setProperty("ladderLossesCoin",25);
echo $classes["Elfos"]->getProperty("ladderLossesCoin"). "<br>";
$classes["Elfos"]->upOne("ladderLossesCoin");
echo $classes["Elfos"]->getProperty("ladderLossesCoin"). "<br>";
//50
//51
//25
//26
?>
I need to return family data (parents, siblings and partners) for 'x' number of generations (passed as $generations parameter) starting from a single person (passed as $id parameter). I can't assume two parents, this particular genealogy model has to allow for a dynamic number of parents (to allow for biological and adoptive relationships). I think my recursion is backwards, but I can't figure out how.
The code below is triggering my base clause 5 times, once for each generation, because $generation is being reduced by 1 not for every SET of parents but for every parent. What I want is for the base clause ($generations == 0) to only be triggered once, when 'x' number of generations for all parents of the initial person are fetched.
public function fetchRelationships($id = 1, $generations = 5, $relationships = array())
{
$perId = $id;
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
$relationships[$perId]['partners'] = $this->fetchPartners($perId);
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$parent = $parentRel->getPer2();
$pid = $parent->getId();
$relationships[$perId]['siblings'][$pid] = $this->fetchSiblings($perId, $pid);
$perId = $pid;
$relationships[$perId] = $this->fetchRelationships($perId, $generations, $relationships);
}
}
return $relationships;
}
}
The methods fetchPartners, fetchParents and fetchSiblings just fetch the matching entities. So I am not pasting them here. Assuming that there are 2 parents, 5 generations and each generation has 2 parents then the return array should contain 62 elements, and should only trigger the base clause once those 62 elements are filled.
Thanks, in advance, for any help.
-----------Edit--------
Have rewritten with fetchSiblings and fetchPartners code removed to make it easier to read:
public function fetchRelationships($id = 1, $generations = 5, $relationships = array())
{
$perId = $id;
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$perId = $parentRel->getPer2()->getId();
$relationships[$perId] = $this->fetchRelationships($perId, $generations, $relationships);
}
}
return $relationships;
}
}
Garr Godfrey got it right. $generations will equal zero when it reaches the end of each branch. So you'll hit the "base clause" as many times as there are branches. in the foreach ($parents as $parentRel) loop, you call fetchRelationships for each parent. That's two branches, so you'll have two calls to the "base clause". Then for each of their parents, you'll have another two calls to the "base clause", and so on...
Also, you're passing back and forth the relationships, making elements of it refer back to itself. I realize you're just trying to retain information as you go, but you're actually creating lots of needless self-references.
Try this
public function fetchRelationships($id = 1, $generations = 5)
{
$perId = $id;
$relationships = array();
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$perId = $parentRel->getPer2()->getId();
$relationships[$perId] = $this->fetchRelationships($perId, $generations);
}
}
return $relationships;
}
}
you'll still hit the base clause multiple times, but that shouldn't matter.
you might be thinking "but then i will lose some of the data in $relationships", but you won't. It's all there from the recursive returns.
If you're pulling this out of a database, have you considered having the query do all of the leg work for you?
Not sure how you need the data stacked or excluded, but here's one way to do it:
<?php
class TreeMember {
public $id;
// All three should return something like:
// array( $id1 => $obj1, $id2 => $obj2 )
// and would be based on $this->$id
public function fetchParents(){ return array(); }
public function fetchPartners(){ return array(); };
public function fetchSiblings(){ return array(); };
public function fetchRelationships($generations = 5)
{
// If no more to go
if ($generations == 0) { return; }
$branch = array();
$branch['parents'] = $this->fetchParents();
$branch['partners'] = $this->fetchPartners();
$branch['partners'] = $this->fetchSiblings();
// Logic
$generations--;
foreach($branch as $tmType, $tmArr)
{
foreach($tmArr as $tmId => $tmObj)
{
$branch[$tmType][$tmId] =
$mObj->fetchRelationships
(
$generations
)
);
});
return array($this->id => $branch);
}
}