configure own iterator for class php? - php

I have a class Foo, I need to do :
$foo = new Foo();
foreach($foo as $value)
{
echo $value;
}
and define my own method to iterate with this object, exemple :
class Foo
{
private $bar = [1, 2, 3];
private $baz = [4, 5, 6];
function create_iterator()
{
//callback to the first creation of iterator for this object
$this->do_something_one_time();
}
function iterate()
{
//callback for each iteration in foreach
return $this->bar + $this->baz;
}
}
Can we do that? How?

You need to implement the \Iterator or \IteratorAggregate interface to achieve that.
A simple example of what you're trying to achieve using the \IteratorAggregate and \Iterator interfaces (I've left out the \Iterator implementation details, but you can use the PHP doc to see how they work) :
class FooIterator implements \Iterator
{
private $source = [];
public function __construct(array $source)
{
$this->source = $source;
// Do whatever else you need
}
public function current() { ... }
public function key() { ... }
public function next()
{
// This function is invoked on each step of the iteration
}
public function rewind() { ... }
public function valid() { ... }
}
class Foo implements \IteratorAggregate
{
private $bar = [1, 2, 3];
private $baz = [4, 5, 6];
public function getIterator()
{
return new FooIterator(array_merge($this->bar, $this->baz));
}
}
$foo = new Foo();
foreach ($foo as $value) {
echo $value;
}

You need to implement the Iterator interface.
class Foo implements Iterator {
You should review the built in interfaces:
http://php.net/manual/en/reserved.interfaces.php

Related

How do I set PHP class properties with construct() arguments automatically?

Does anyone know of an efficient technique in PHP to auto assign class parameters with identically named __construct() method arguments?
For instance, I've always thought it was highly inefficient to do something like the following:
<?php
class Foo
{
protected $bar;
protected $baz;
public function __construct($bar, $baz)
{
$this->bar = $bar;
$this->baz = $baz;
}
}
I'm wondering if there's a better/more efficient way/magic method to auto-assign class properties with identically named method parameters.
Thanks,
Steve
PHP 8
Constructor Promotion
function __construct(public $bar, public $baz) {}
PHP 5
function _promote(&$o) {
$m = debug_backtrace(0, 2)[1];
$ref = new ReflectionMethod($m['class'], $m['function']);
foreach($ref->getParameters() as $i=>$p) {
$o->{$p->name} = $m['args'][$i] ?? $p->getDefaultValue();
}
}
class Foo {
function __construct($bar, $baz) {
_promote($this);
}
}
I think this way is a pretty generally accepted way to do it, although you could make getters and setters. Or, if you're looking for magic methods in php: http://php.net/manual/en/language.oop5.magic.php
Not in a constructor. You can always wrap your properties into an array, instead, and only have a single property that needs to be set:
<?php
class SomeClass
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
}
$params = ['bar' => 'bar', 'baz' => 'baz'];
$someClass = new SomeClass($params);
echo $someClass->getData()['bar'] . PHP_EOL;
There is the magic method __set, but it is only called when attempting to write data to inaccessible properties:
<?php
class SomeClass
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __get($name)
{
if(isset($this->data[$name])) {
return $this->data[$name];
}
return null;
}
}
$class = new SomeClass;
$class->bar = 'bar';
$class->baz = 'baz';
echo $class->bar . PHP_EOL . $class->baz . PHP_EOL;
If your class is starting to have a lot of parameters being passed in to the constructor, it may be a sign that your class is getting too big and trying to do too much. A refactoring may be in order.

How to return special value when objected is casted to an array?

There is a magic method __toString, which would be triggered if an object is used in a string context or casted to such, e.g.
<?php
class Foo {
public function __toString() {
return 'bar';
}
}
echo (string) new Foo(); // return 'bar';
Is there a similar function that would be triggered when an object is castend into an (array)?
No, but there is the ArrayAccess interface, which allows you to use a class as an array. To get looping functionality a la foreach you will need to interface IteratorAggregate or Iterator. The former is easier to use if you have an internal array that you are using because you only need to override one method (which provides an instance of ArrayIterator), but the latter allows you more fine-grain control over iterating.
Example:
class Messages extends ArrayAccess, IteratorAggregate {
private $messages = array();
public function offsetExists($offset) {
return array_key_exists($offset, $this->messages);
}
public function offsetGet($offset) {
return $this->messages[$offset];
}
public function offsetSet($offset, $value) {
$this->messages[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->messages[$offset]);
}
public function getIterator() {
return new ArrayIterator($this->messages);
}
}
$messages = new Messages();
$messages[0] = 'abc';
echo $messages[0]; // 'abc'
foreach($messages as $message) { echo $message; } // 'abc'
This may not being exactly what you may have expected, because what you expect isn't available as a language feature of PHP (unfortunately) but here comes a well known workaround:
Use get_object_vars() for this:
$f = new Foo();
var_dump(get_object_vars($f));
It will return an associative array with property names as indexes and theirs values. Check this example:
class Foo {
public $bar = 'hello world';
// even protected and private members will get exported:
protected $test = 'I\'m protected';
private $test2 = 'I\'m private';
public function toArray() {
return get_object_vars($this);
}
}
$f = new Foo();
var_dump($f->toArray());
Output:
array(2) {
'bar' =>
string(11) "hello world"
'test' =>
string(13) "I'm protected"
'test2' =>
string(13) "I'm private"
}

Copying an instance of a PHP class while preserving the data in a base class?

I have the following three classes:
class a
{ public $test; }
class b extends a { }
class c extends a
{
function return_instance_of_b() { }
}
As you can see, both classes b and c derive from a. In the return_instance_of_b() function in c, I want to return an instance of the class b. Basically return new b(); with one additional restriction:
I need the data from the base class (a) to be copied into the instance of b that is returned. How would I go about doing that? Perhaps some variant of the clone keyword?
You can use the get_class_vars function to retrieve the names of the variables you want to copy, and just loop to copy them.
The variables that are defined are protected so they are visible to get_class_vars in its scope (since c extends a), but not directly accessible outside the class. You can change them to public, but private will hide those variables from get_class_vars.
<?php
class a
{
protected $var1;
protected $var2;
}
class b extends a
{
}
class c extends a
{
function __construct()
{
$this->var1 = "Test";
$this->var2 = "Data";
}
function return_instance_of_b()
{
$b = new b();
// Note: get_class_vars is scope-dependant - It will not return variables not visible in the current scope
foreach( get_class_vars( 'a') as $name => $value) {
$b->$name = $this->$name;
}
return $b;
}
}
$c = new c();
$b = $c->return_instance_of_b();
var_dump( $b); // $b->var1 = "Test", $b->var2 = "Data
I believe you can achieve this with some reflection. Not very pretty code, I'm sure there is a much more succinct method to achieve this but here you go.
class a
{
public $foo;
public $bar;
function set($key, $value) {
$this->$key = $value;
}
function get($key) {
return $this->$key;
}
}
class b extends a
{
function hello() {
printf('%s | %s', $this->foo, $this->bar);
}
}
class c extends a
{
public $ignored;
function return_instance_of_b() {
$b = new b();
$reflection = new ReflectionClass($this);
$parent = $reflection->getParentClass();
foreach($parent->getProperties() as $property) {
$key = $property->getName();
$value = $property->getValue($this);
$b->$key = $value;
}
return $b;
}
}
$c = new c();
$c->set('foo', 'bar');
$c->set('bar', 'bar2');
$c->set('ignored', 'should be!');
$b = $c->return_instance_of_b();
$b->hello();
// outputs bar | bar2
Additionally you could use nickb's answer but instead of hard coding the class you could use get_parent_class
function return_instance_of_b()
{
$b = new b();
foreach(get_class_vars(get_parent_class(__CLASS__)) as $name => $value) {
$b->$name = $this->$name;
}
return $b;
}

Working with __get() by reference

With an example class such as this:
class Test{
public function &__get($name){
print_r($name);
}
}
An instance of Test will kick back output as such:
$myTest = new Test;
$myTest->foo['bar']['hello'] = 'world';
//outputs only foo
Is there a way I can get more information about what dimension of the array is being accessed, showing me (from the previous example) that the bar element of foo, and the hello element of bar are being targeted?
You can't with the current implementation. In order for this to work, you will have to create an array object (i.e.: an object that implements ArrayAccess). Something like:
class SuperArray implements ArrayAccess {
protected $_data = array();
protected $_parents = array();
public function __construct(array $data, array $parents = array()) {
$this->_parents = $parents;
foreach ($data as $key => $value) {
if (is_array($value)) {
$value = new SuperArray($value, array_merge($this->_parents, array($key)));
}
$this[$key] = $value;
}
}
public function offsetGet($offset) {
if (!empty($this->_parents)) echo "['".implode("']['", $this->_parents)."']";
echo "['$offset'] is being accessed\n";
return $this->_data[$offset];
}
public function offsetSet($offset, $value) {
if ($offset === '') $this->_data[] = $value;
else $this->_data[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->_data[$offset]);
}
public function offsetExists($offset) {
return isset($this->_data[$offset]);
}
}
class Test{
protected $foo;
public function __construct() {
$array['bar']['hello'] = 'world';
$this->foo = new SuperArray($array);
}
public function __get($name){
echo $name.' is being accessed.'.PHP_EOL;
return $this->$name;
}
}
$test = new Test;
echo $test->foo['bar']['hello'];
Should output:
foo is being accessed.
['bar'] is being accessed
['bar']['hello'] is being accessed
world
No you can't.
$myTest->foo['bar']['hello'] = 'world'; goes through the following translation
$myTest->__get('foo')['bar']['hello'] = 'world'; breaking them in parts become
$tmp = $myTest->__get('foo')
$tmp['bar']['hello'] = 'world';
What you can do is to create an ArrayAccess Derived Object. Where you define your own offsetSet() and return that from __get()
Instead of returning an array, you could return an object that implements ArrayAccess. Objects are always returned and passed by reference. This pushes the problem at least on level down.

How to call the construct of a ReflectionObject and how to get this from a ReflectionClass

I have an array of reflectionClasses.
I need to get a reflectionObject from one of these and then call its constructor with some parameters.
The point is to instantiate an object without knowing the class name (i'll know it at runtime).
Example, just to render the idea:
foreach (Conf::get_array() as $reflection_class) {
//it's not right, just to render the idea
$reflectionObject = new ReflectionObject ($reflection_class);
$objects[] = $reflectionObject->construct($param_1, $param_2);
}
Another example:
foreach (Conf::get_array() as $reflection_class) {
$objects[] = new $reflection_class($param_1, $param_2); //not right. maybe from php 5.3?
}
You don't need an instance of ReflectionObject for that. ReflectionClass has the two methods
public stdclass newInstance(mixed args)
public stdclass newInstanceArgs(array args)
example:
<?php
class Foo {
public function __construct($a, $b) { echo "Foo($a,$b) "; }
}
class Bar {
public function __construct($a, $b) { echo "Bar($a,$b) "; }
}
class Conf {
public static function get_array() {
return array(new ReflectionClass('Foo'), new ReflectionClass('Bar'));
}
}
$args = array('A', 'B');
$object = array();
foreach (Conf::get_array() as $reflection_class) {
$objects[] = $reflection_class->newInstanceArgs($args);
}
var_dump($objects);

Categories