I have a class which contains an array of objects and has methods to return an object from that array by reference. There is also a method to unset an object from the array.
However if I have a variable that references an object from the array, and that element is unset, the variable still has a reference to it. What do I need to do in the remove method that will destroy that object for good, including references to it.
class myClass
{
public $objectList = array();
public function __construct()
{
$objectList[] = new myObject();
}
public function &getObjectByReference()
{
return $this->objectList[0];
}
public function removeObject()
{
unset($this->objectList[0]);
}
}
$myClass = new myClass();
$referencedObject = $myClass->getObjectByReference();
// this can now use the methods from myObject
$myClass-> removeObject();
// supposed to delete the object from memory. However $referencedObject is still
// able to use all the methods from myObject.
So thats the problem I am having, I need to be able to remove from the array and delete the object from memory so variables that reference that object are no longer usable.
Have you tried doing:
$referencedObject = &$myClass->getObjectByReference();
Is $referencedObject still there after putting in that &?
To create a reference from a return value both the function must return by reference (you already do so) and the assignment has to by by reference. So you should write:
$referencedObject =& $myClass->getObjectByReference();
If you do this the reference really will be destroyed.
But if you want to do destroy all variables having this object as value (and which are not references) then this is impossible. You can only remove the real references, not variables having the same value ;)
Php is working with garbage collector : if there is still a reference to the object then the object is not deleted.
unset($this->objectList[0])
Does not delete the object but the value in $this->objectList, the object still exists since he is referenced by $referencedObject.
One solution : when you delete the object, tell him he is being deleted and in that object you have a boolean "isDeleted". Then for every method of that object, check if isDeleted is true and in that case, just do nothing.
This is the nature of PHP's garbage collector. To make sure a caller doesn't maintain a reference to your object you have to ensure they can never touch the original object. Here is an idea:
class myClass
{
public $objectList = array();
public function __construct()
{
$objectList[] = new Wrapper(new myObject());
}
public function getObject()
{
return $this->objectList[0];
}
public function removeObject()
{
$this->objectList[0]->emptyWrapper();
unset($this->objectList[0]);
}
}
class Wrapper {
private $object;
public function __construct($object) {
$this->object = $object;
}
public function __call($method, $args) {
return call_user_func_array(array($this->object, $method), $args);
}
public function __get($attr) {
return $this->object->$attr;
}
public function __set($attr, $value) {
$this->object->$attr = $value;
}
public function emptyWrapper() {
$this->object = null;
}
}
You can use this wrapper idea or you can use forced indirection and handles. I might prefer using forced indirection instead; otherwise, a caller can still keep the wrapper object alive - though it is fairly cheap.
class myClass
{
public $objectList = array();
public function __construct()
{
$objectList[] = new myObject();
}
public function getObjectHandle() {
return 0;
}
public function removeObject($h)
{
unset($this->objectList[$h]);
}
public function call($h, $method, $args) {
call_user_func(array($this->objectList[$h], $method), $args);
}
public function get($h, $attr) {
return $this->objectList[$h]->$attr;
}
public function set($h, $attr, $value) {
$this->objectList[$h]->$attr = $value;
}
}
$myClass = new myClass();
$objectHandle = $myClass->getObjectHandle();
// example method call
$myClass->call($objectHandle, 'some_method', array('arg1', 'arg2'));
$myClass->removeObject($objectHandle);
Related
Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);
In PHP using method chaining how would one go about supplying a functional call after the last method being called in the chain?
Also while using the same instance (see below). This would kill the idea of implementing a destructor.
The end result is a return value and functional call of private "insert()" from the defined chain properties (of course) without having to call it publicly, no matter of the order.
Note, if I echo (__toString) the methods together it would retrieve the final generated unique code which is normal behavior of casting a string.
Example below:
class object
{
private $data;
function __construct($name) {
// ... some other code stuff
}
private function fc($num) {
// some wicked code here
}
public function green($num) {
$this->data .= fc($num*10);
return $this;
}
public function red($num) {
$this->data .= fc($num*25);
return $this;
}
public function blue($num) {
$this->data .= fc($num*1);
return $this;
}
// how to get this baby to fire ?
private function insert() {
// inserting
file_put_content('test_code.txt', $this->data);
}
}
$tss = new object('index_elements');
$tss->blue(100)->green(200)->red(100); // chain 1
$tss->green(0)->red(100)->blue(0); // chain 2
$tss->blue(10)->red(80)->blue(10)->green(0); // chain 3
Chain 1, 2, and 3 would generated an unique code given all the values from the methods and supply an action, e.g. automatically inserting in DB or creating a file (used in this example).
As you can see no string setting or casting or echoing is taking place.
You could keep a list of things that needs to be initialised and whether they
have been so in this instance or not. Then check the list each time you use
one of the initialisation methods. Something like:
class O {
private $init = array
( 'red' => false
, 'green' => false
, 'blue' => false
);
private function isInit() {
$fin = true;
foreach($this->init as $in) {
$fin = $fin && $in;
}
return $fin;
}
public function green($n) {
$this->init['green'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function red($n) {
$this->init['red'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function blue($n) {
$this->init['blue'] = true;
if($this->isInit()) {
$this->insert();
}
}
private function insert() {
echo "whee\n";
}
}
But personally I think this would be more hassle then it's worth. Better imo
to expose your insert method and let the user of you code tell when the
initialisation is finished. So something that should be used like:
$o->red(1)->green(2)->blue(0)->insert();
-update-
If it's the case that it's impossible to predict what functions need to be called
you really do need to be explicit about it. I can't see a way around that. The reason
is that php really can't tell the difference between
$o1 = new A();
$o2 = $o1->stuff();
and
$o2 = (new A())->stuff();
In a language that allows overloading = I guess it would be possible but really
really confusing and generally not a good idea.
It is possible to move the explicit part so that it's not at the end of the call
chain, but I'm not sure if that would make you happier? It would also go against
your desire to not use another instance. It could look something like this:
class O {
public function __construct(InitO $ini) {
// Do stuff
echo "Whee\n";
}
}
class InitO {
public function red($n) {
return $this;
}
public function green($n) {
return $this;
}
public function blue($n) {
return $this;
}
}
$o = new O((new InitO())->red(10)->red(9)->green(7));
You can of course use just one instance by using some other way of wrapping
but the only ways I can think of right now would look a lot uglier.
Im with PeeHaa, this makes no sense! :)
Only chance to have something magically happen after the last chain was used (without being able to look into the future) is a Destructor/Shutdown function OR a manually cast/call to insert()
You can also decide to implement this statically without using objects.
<?php
class Object
{
private static $data;
public static function set($name)
{
// ... some other code stuff
}
private static function fc($num)
{
// some wicked code here
}
public static function green($num)
{
self::$data .= self::fc($num*10);
return new static;
}
public static function red($num)
{
self::$data .= self::fc($num*25);
return new static;
}
public static function blue($num) {
self::$data .= self::fc($num*1);
return new static;
}
// how to get this baby to fire ?
public static function insert()
{
// inserting
file_put_content('test_code.txt', self::$data);
}
}
//$tss = new object('index_elements');
$Object::set('index_elements')->blue(100)->green(200)->red(100)->insert(); // chain 1
$Object::set('index_elements')->green(0)->red(100)->blue(0)->insert(); // chain 2
$Object::set('index_elements')->blue(10)->red(80)->blue(10)->green(0)->insert(); // chain 3
?>
Ok let's see a code example
<?php
// map dummy class
class map
{
// __call magic method
public function __call($name, $args)
{
return $this;
}
}
// now we chain
$map = new map;
// let's find me
$map->start('here')
->go('right')
->then()
->turn('left')
->and('get water')
->dontEat()
->keep('going')
->youShouldSeeMe('smiling');
here we don't know what the last method would be and we need to trigger a kinda operation or event once we hit the end.
According to data structure we can call this the LIFO stack. (Last in first out)
so how did i solve this on PHP?
// i did some back tracing
... back to the __call function
function __call($name, $args)
{
$trace = debug_backtrace()[0];
$line = $trace['line'];
$file = $trace['file'];
$trace = null;
$getFile = file($file);
$file = null;
$getLine = trim($getFile[$line-1]);
$line = null;
$getFile = null;
$split = preg_split("/(->)($name)/", $getLine);
$getLine = null;
if (!preg_match('/[)](->)(\S)/', $split[1]) && preg_match('/[;]$/', $split[1]))
{
// last method called.
var_dump($name); // outputs: youShouldSeeMe
}
$split = null;
return $this;
}
And whoolla we can call anything once we hit the bottom.
*(Notice i use null once i am done with a variable, i come from C family where we manage memory ourselves)
Hope it helps you one way or the other.
So there are two things that I am stuck on now. First
class DisplayTaxonomy {
public $MyArray[]= new DisplayTaxonomy(); //the compiler says syntax error
//things I have tried
//public $ss[]= new Object();
}
Second! in a function like this:
public function whatever()
{
$Temp = new DisplayTaxonomy();
$Temp->setADTitle("1");
$MyArray[]= $Temp;//works just fine.
//then i tried to return the array
return $MyArray[];
}
I get the following
//Cannot use [] for reading in C:\xampp\htdocs\wordpress\wp-//content\themes\twentyeleven\page.php on line 52
then in the client side
$y=new DisplayTaxonomy();
$myArray[]=new DisplayTaxonomy();//works fine dont know why I cant do this in theclass.
$myArray[]=$y->getArrayOfDisplayTaxonomyObjects();
echo $myArray[0]->getADTitle();
It seems you want to create a class that handles a collection of Taxonomy objects. In that case you should have two classes, instead of making a class store instances of itself.
class TaxonomyContainer
{
private $collection = array();
public function addElement(DisplayTaxonomy $element)
{
$this->collection[] = $element;
}
public function getElements()
{
return $this->collection;
}
}
class DisplayTaxonomy
{
private $adTitle;
public function setAdTitle($adTitle)
{
$this->adTitle = $adTitle;
}
//and other functionality the Taxonomy object should have
}
Then you can avoid the ugly self replicating behaviour and separate your concerns.
$container = new TaxonomyContainer();
$element = new DisplayTaxonomy();
$container->addElement($element);
On the next level, it might be worth considering the use of one of PHP's predefined interfaces for the Container class.
You declare objects in the function body and initiate them in the constructor (or a member function). You don't use [] when returning an array, $array[] has the same functionality as array_push, nothing more.
To clarify,
class myClass {
public $obj = array();
public function __construct() {
$this->obj[] = new OtherObject();
}
public function getObj() {
return $this->obj;
}
}
You cannot do this :
class DisplayTaxonomy {
public $MyArray[]= new DisplayTaxonomy();
}
because it's like an infinite loop :) So you have to use __contruct() function.
After change the :
return $MyArray[];
to :
return $MyArray;
Your first issue is due to trying to call the class you're declaring.
class DisplayTaxonomy {
public $MyArray[]= new DisplayTaxonomy();
You should initialize your object outside of the class, in the portion of code that you need to reference the class.
In addition, the object is already an array so you can omit attaching [] to the end return $MyArray:
public function whatever(){
$Temp = new DisplayTaxonomy();
$Temp->setADTitle("1");
$MyArray[] = $Temp;
return $MyArray;
}
You're declaring the array object here:
$MyArray[]= $Temp;//works just fine
You can't call code (new DisplayTaxonomy()) when definining class properties. You'll have to assign the value in the constructor of the class:
class Foo
{
public $MyArray;
public function __construct()
{
$this->MyArray = new DisplayTaxonomy();
}
}
The other issue is that the $array[] shortcut is for appending to an array. If you want to just return the array (and not write to the array which you're asking about with []), simply skip []:
return $MyArray;
Expanded:
As Vincent Joigƞie pointed out below; you're trying to create a class with the same name as the class you're already creating. This doesn't make any sense, except for static properties. In that case you can't use __construct(), but would rather create / set the object in the static method you're calling to retrieve the object the first time:
static public function getInstance()
{
if (self::$MyArray === null)
{
self::$MyArray = new DisplayTaxonomy();
}
return self::$MyArray;
}
This is however probably not what you want, and it seems you've confused something in your logic in your class definition. Guessing freely you might just want:
class Foo
{
public $MyArray = array();
}
As array() is a static assignment (and not a function call), it's allowed in the class definition.
Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);
I would like to get all the instances of an object of a certain class.
For example:
class Foo {
}
$a = new Foo();
$b = new Foo();
$instances = get_instances_of_class('Foo');
$instances should be either array($a, $b) or array($b, $a) (order does not matter).
A plus is if the function would return instances which have a superclass of the requested class, though this isn't necessary.
One method I can think of is using a static class member variable which holds an array of instances. In the class's constructor and destructor, I would add or remove $this from the array. This is rather troublesome and error-prone if I have to do it on many classes.
If you derive all your objects from a TrackableObject class, this class could be set up to handle such things (just be sure you call parent::__construct() and parent::__destruct() when overloading those in subclasses.
class TrackableObject
{
protected static $_instances = array();
public function __construct()
{
self::$_instances[] = $this;
}
public function __destruct()
{
unset(self::$_instances[array_search($this, self::$_instances, true)]);
}
/**
* #param $includeSubclasses Optionally include subclasses in returned set
* #returns array array of objects
*/
public static function getInstances($includeSubclasses = false)
{
$return = array();
foreach(self::$_instances as $instance) {
if ($instance instanceof get_class($this)) {
if ($includeSubclasses || (get_class($instance) === get_class($this)) {
$return[] = $instance;
}
}
}
return $return;
}
}
The major issue with this is that no object would be automatically picked up by garbage collection (as a reference to it still exists within TrackableObject::$_instances), so __destruct() would need to be called manually to destroy said object. (Circular Reference Garbage Collection was added in PHP 5.3 and may present additional garbage collection opportunities)
Here's a possible solution:
function get_instances_of_class($class) {
$instances = array();
foreach ($GLOBALS as $value) {
if (is_a($value, $class) || is_subclass_of($value, $class)) {
array_push($instances, $value);
}
}
return $instances;
}
Edit: Updated the code to check if the $class is a superclass.
Edit 2: Made a slightly messier recursive function that checks each object's variables instead of just the top-level objects:
function get_instances_of_class($class, $vars=null) {
if ($vars == null) {
$vars = $GLOBALS;
}
$instances = array();
foreach ($vars as $value) {
if (is_a($value, $class)) {
array_push($instances, $value);
}
$object_vars = get_object_vars($value);
if ($object_vars) {
$instances = array_merge($instances, get_instances_of_class($class, $object_vars));
}
}
return $instances;
}
I'm not sure if it can go into infinite recursion with certain objects, so beware...
I need this because I am making an event system and need to be able to sent events to all objects of a certain class (a global notification, if you will, which is dynamically bound).
I would suggest having a separate object where you register objects with (An observer pattern). PHP has built-in support for this, through spl; See: SplObserver and SplSubject.
As far as I know, the PHP runtime does not expose the underlying object space, so it would not be possible to query it for instances of an object.