Completely remove objects from runtime - php

I'm running a job where I import stock information from loads of suppliers. Some of them have many products, so when I store them in memory it quickly gets pretty heavy.
For that reason, I'd like to - after doing whatever-i-do-with-them - completely unset each supplier.
Each "supplier" is a Reader and a Processor which gets injected into a Manager which will return all data to N writers. The manager stores the Reader and Processor (which is the classes where the big amount of data will be) in a simple array.
Here's a quick demonstration:
$first = new stdClass();
$first->name = "I'm an object";
$first->age = 12;
$second = new stdClass();
$first->name = "I'm also an object!";
$first->age = 9;
$arr = array($first, $second);
while ($data = array_shift($arr)) {
echo "Emptying array one by one... \n";
//doesn't matter if you unset($data) or do $data=null;
}
//I want this to be empty, but's not
var_dump($first);
For now, I do a foreach() on the array and makes the value be passed by reference, which I then passes (by reference) to a cleanup() method. This feels clumsy.
Am I missing something obvious? How can I achieve this?

Related

Structuring OOP Php methods and objects when using a JSON API call

I am making an application that pulls data through JSON with CURL. I'm using PHP and my script currently creates 2 different objects to contain the curl requests. I've got the data in a readable format, but I need to divide an average from the first call by an average from the second call.
This average is of equal relevance to each of the calls - and I'm new to working with objects and classes so am struggling to understand how to structure this in the right way. The following line of code doesn't work - but, if you read on it will hopefully help to explain what I'm trying to do.
$yield = object1->calculate_average($list)->average['price']/object2->calculate_average($list)->average['price'];
I want to code the $yield variable in to the listing class as a further method that will display when I call $anobject->yield. Should I access this via a) object1, b) object2, or c) it's own object (which seems excessive..as it will only be used occasionally). Is there a right way to do this, or does it not really matter?
My query is general (what is the best approach to using objects when an object appears to be as relevant to all options, and is it possible to reference arrays like this: calculate_average($list)->average['price']) as much as it is specific (how can I fix this script)
When this has come up before I've tended to just regrab the data from the source and fudge it, but because it's a JSON call and therefore takes a bit longer I've been prompted to find a better solution...
A simplified version of my best effort so far is below.
I've assumed that I need to use foreach on the json outputs:
class listing {
//This takes a stdClass object from the JSON and prepares it to be served
public function output_list($data) {
$price = "0";
$vals = "0";
$list = array();
foreach ($data->listing as $item) {
//Calculate average price
$price = $price + $item->price;
$vals = $vals + 1;
//Display information about each item
$list[$vals][] = "Price: £" . $item->price . " and various data";
$list[$vals][] = ...;
}
return $list;
}
// This function calculates equations on the data which I read above
public function calculate_average($list) {
$average = array();
$total_price = array_column($list, '1'); //Find the total sum of list[?][1] which = price
$vals = count($list); //count the number of values in the list
$average['denominator'] = $vals;
$average['week'] = $total_price / $average['denominator'];
//And a number of other calculations within the array...
return $average;
}
}
I then want to call these by using the following code:
$object1 = new listing();
$list1 = $object1->output_list($response); ////Output formatted data
print_r($list1);
$average = $object1->calculate_average($list);
$object2 = new listing();
$list2 = $object2->output_list($response); ////Output formatted data
print_r($list2);
$average = $object2->calculate_average($list);
The other thing is that $total_price = array_column($list,'1') needs me to have php 5.5 - which I don't have, so I need to find an alternative - and that makes me question whether my array approach is the right one in the first place. [I need to sum every [?][1] reference in the multi-dimensional array I've made].
If you're able to help I'd be so grateful - I've been trying to get my head around objects and classes for weeks!

Running an intensive batch process in PHP, and avoiding memory exhaustion

I have several thousand records (stored in in a table in a MYSQL table) that I need to "batch process." All of the records contain a large JSON. In some cases, the JSON is over 1MB (yes, my DB is well over 1GB).
I have a function that grabs a record, decodes the JSON, changes some data, re-encodes the PHP array back to a JSON, and saves it back to the db. Pretty simple. FWIW, this is within the context of a CakePHP app.
Given an array of ID's, I'm attempting to do something like this (very simple mock code):
foreach ($ids as $id) {
$this->Model->id = $id;
$data = $this->Model->read();
$newData = processData($data);
$this->Model->save($newData);
}
The issue is that, very quickly, PHP runs out of memory. When running a foreach like this, it's almost as if PHP moves from one record to the next, without releasing the memory required for the preceding operations.
Is there anyway to run a loop in such a way that memory is freed before moving on to the next iteration of the loop, so that I can actually process the massive amount of data?
Edit: Adding more code. This function takes my JSON, converts it to a PHP array, does some manipulation (namely, reconfiguring data based on what's present in another array), and replacing values in the the original array. The JSON is many layers deep, hence the extremely long foreach loops.
function processData($theData) {
$toConvert = json_decode($theData['Program']['data'], $assoc = true);
foreach($toConvert['cycles'] as $cycle => $val) {
foreach($toConvert['cycles'][$cycle]['days'] as $day => $val) {
foreach($toConvert['cycles'][$cycle]['days'][$day]['sections'] as $section => $val) {
foreach($toConvert['cycles'][$cycle]['days'][$day]['sections'] as $section => $val) {
foreach($toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'] as $exercise => $val) {
if (isset($toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'][$exercise]['selectedFolder'])) {
$folderName = $toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'][$exercise]['selectedFolder']['folderName'];
if ( isset($newFolderList['Folders'][$folderName]) ) {
$toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'][$exercise]['selectedFolder'] = $newFolderList['Folders'][$folderName]['id'];
}
}
if (isset($toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'][$exercise]['selectedFile'])) {
$fileName = basename($toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'][$exercise]['selectedFile']['fileURL']);
if ( isset($newFolderList['Exercises'][$fileName]) ) {
$toConvert['cycles'][$cycle]['days'][$day]['sections'][$section]['exercises'][$exercise]['selectedFile'] = $newFolderList['Exercises'][$fileName]['id'];
}
}
}
}
}
}
}
return $toConvert;
}
Model->read() essentially just tells Cake to pull a record from the db, and returns it in an array. There's plenty of stuff that's happening behind the scenes, someone more knowledgable would have to explain that.
The first step I would do is make sure everything is passed by reference.
Eg,
foreach ($ids as $id) {
processData($data);
}
function processData(&$d){}
http://php.net/manual/en/language.references.pass.php

Creating an array in PHP

If I wanted to create an array in PHP with the following format, what would be the best way to approach it? Basically I want to declare the array beforehand, and then be able to add the values to person_id, name and age.
'person_id' => array('name'=>'jonah', 'age'=> 35)
To the first answer #Joe, first of all, your class doesn't support encapsulation since your attributes are public. Also, for someone who doesn't know how to create an array, this concept may be a bit complicated.
For the answer, changing a bit the class provided by #Joe, PHP provide a simple way of using arrays:
If you need many rows for this array, each row has a number, if not, remove the [0] for only one entry.
$persons = array();
$persons[0]['person_id'] = 1;
$persons[0]['name'] = 'John';
$persons[0]['age'] = '27';
Depending on how you want to use it, you could make a simple object:
class Person
{
public $person_id;
public $name;
public $age;
}
$person = new Person; // make the object beforehand
$person->person_id = 123; // their id
$person->name = 'Joe'; // my name
$person->age = 21; // yeah, right
This is virtually identical to an array (in terms of how it's used) except for:
Instead of new array() you use new Person (no brackets)
Instead of accessing items with ['item'] you use ->item
You can start with an empty array:
$arr = Array();
Then add stuff to that array:
$arr['person_id'] = Array();
$arr['person_id']['name'] = "jonah";
$arr['person_id']['age'] = 35;

Creating an array of objects in PHP

I would like to know what is the right of creating objects arrays in php.
My goal here is to be able to get data like this:
$obj = new MyClass();
echo $obj[0]->parameter; //value1
echo $obj[1]->parameter; //value2
Thanks for your time.
EDIT:
And if I want to do it in class it should look like this?
class MyClass{
public $property;
public function __construct() {
$this->property[] = new ProjectsList();
}
}
Any of the following are valid:
$myArray = array();
$myArray[] = new Object();
$myArray[1] = new Object();
array_push($myArray, new Object);
Try this,
$obj = array(new stdClass(), new stdClass())
or
$obj = array()
$obj[] = new stdClass()
$obj[] = new stdClass()
EDIT:
Class to stdClass
An important hint ...
The memory allocation for PHP arrays is exponential. A simple example: All array entries fit into an array, that allocates 2MB of memory. If this memory is no longer sufficient for all array entries, the memory allocation is expanded exponentially until the PHP memory limit is reached. If the array needs more than the 2 MB in this example, it will expand to 4MB, 16MB, etc.
When dealing with objects, better use collections. PHP provides the SplObjectStorage. This kind of collection allocates the exactly needed memory for its contents. Iteration over this collections behaves like using a yield. Only the memory for the current object in iteration is allocated. Beside that the SplObjectStorage class takes objects only. Should work perfectly for your purposes.
<?php
declare('strict_types=1');
namespace Marcel;
$collection = new SplObjectStorage();
$object1 = new stdClass();
$object2 = new stdClass();
$collection->attach($object1);
$collection->attach($object2);
The above shown code allows some stricter searching.
$collection->contains($object1); // returns true
Detaching an item works like a charme.
$collection->detach($object1);
$collection->contains($object1); // returns false
Iterations works with a pretty fast foreach loop or a while loop.
$collection->rewind();
foreach ($collection as $object) {
// yielding works here
}
$collection->rewind();
while ($collection->valid()) {
// yielding also works here automatically
$collection->next();
}
hope this will be helpful
$newArray[] = (object) array();
Honestly, I think you are on the right path. from what it sounds like you are not just trying to add to arrays, but convert arrays to objects.
<?php
$obj = (object) 'ciao';
echo $obj->scalar; // outputs 'ciao'
?>
PHP Objects
EDIT: I don't think you could add an object like this:
$this->property[] = new ProjectsList();
the "new ProjectsList()" would be how you would create an object from a class. ProjectsList would need to be a class. it would look more like this:
$obj = new ProjectsList;
$this->property[] = $obj;
you would need to make sure the ProjectsList existed first though.

Duplicate array but maintain pointer links

Suppose I have an array of nodes (objects). I need to create a duplicate of this array that I can modify without affecting the source array. But changing the nodes will affect the source nodes. Basically maintaining pointers to the objects instead of duplicating their values.
// node(x, y)
$array[0] = new node(15, 10);
$array[1] = new node(30, -10);
$array[2] = new node(-2, 49);
// Some sort of copy system
$array2 = $array;
// Just to show modification to the array doesn't affect the source array
array_pop($array2);
if (count($array) == count($array2))
echo "Fail";
// Changing the node value should affect the source array
$array2[0]->x = 30;
if ($array2[0]->x == $array[0]->x)
echo "Goal";
What would be the best way to do this?
If you use PHP 5:
Have you run your code? It is already working, no need to change anything. I get:
Goal
when I run it.
Most likely this is because the values of $array are already references.
Read also this question. Although he OP wanted to achieve the opposite, it could be helpful to understand how array copying works in PHP.
Update:
This behaviour, when copying arrays with objects, the reference to the object is copied instead the object itself, was reported as a bug. But no new information on this yet.
If you use PHP 4:
(Why do you still use it?)
You have to do something like:
$array2 = array();
for($i = 0; $i<count($array); $i++) {
$array2[$i] = &$array[$i];
}
it is some time that I don' write PHP code, but does the code
// Some sort of copy system
$array2 = $array;
actually work?
Don't you have to copy each element of the array in a new one?

Categories