PHP functions such as 'array_map' take a callback, which can be a simple function or a class or object method:
$array2 = array_map('myFunc', $array);
or
$array2 = array_map(array($object, 'myMethod'), $array);
But is there a syntax to pass a method which is bound within the iteration to the current object (like 'invoke' in Prototype.js)? So that the following could be used:
$array2 = array_map('myMethod', $array);
with the effect of
foreach($array as $obj) $array2[] = $obj->myMethod();
Obviously I can use this form, or I can write a wrapper function to make the call, and even do that inline. But since 'myMethod' is already a method it seems to be going round the houses to have to do one of these.
Not currently. When php 5.3 comes out, you could use the following syntax:
$array2 = array_map(function($obj) { return $obj->myMethod(); }, $array);
function obj_array_map($method, $arr_of_objects) {
$out = array();
$args = array_slice(func_get_args(), 2);
foreach ($arr_of_objects as $key => $obj) {
$out[$key] = call_user_func_array(Array($obj, $method), $args);
}
return $out;
}
// this code
$a = Array($obj1, $obj2);
obj_array_map('method', $a, 1, 2, 3);
// results in the calls:
$obj1->method(1, 2, 3);
$obj2->method(1, 2, 3);
Basically, no. There is no special syntax to make this any easier.
I can think of a fancier way of doing this in PHP 5.3, seeing as there's always more than one way to do things in PHP, but I'd say it wouldn't necessarily be better than your foreach example:
$x = array_reduce(
$array_of_objects,
function($val, $obj) { $val = array_merge($val, $obj->myMethod()); return $val; },
array()
);
Just go with your foreach :)
<?php
// $obj->$method(); works, where $method is a string containing the name of the
// real method
function array_map_obj($method, $array) {
$out = array();
foreach ($array as $key => $obj)
$out[$key] = $obj->$method();
return $out;
}
// seems to work ...
class Foo {
private $y = 0;
public function __construct($x) {
$this->y = $x;
}
public function bar() {
return $this->y*2;
}
}
$objs = array();
for ($i=0; $i<20; $i++)
$objs[] = new Foo($i);
$res = array_map_obj('bar', $objs);
var_dump($res);
?>
voila!
This is a bit of a silly answer, but you could subclass ArrayObject and use that instead of a normal array:
<?php
class ArrayTest extends ArrayObject {
public function invokeMethod() {
$result = array();
$args = func_get_args();
$method = array_shift($args);
foreach ($this as $obj) {
$result[] = call_user_func_array(array($obj, $method), $args);
}
return $result;
}
}
//example class to use
class a {
private $a;
public function __construct($a) {
$this->a = $a;
}
public function multiply($n) {
return $this->a * $n;
}
}
//use ArrayTest instance instead of array
$array = new ArrayTest();
$array[] = new a(1);
$array[] = new a(2);
$array[] = new a(3);
print_r($array->invokeMethod('multiply', 2));
Outputs this:
Array
(
[0] => 2
[1] => 4
[2] => 6
)
I would use create_function() to ... well ... create a temporary function for array_map while waiting for PHP 5.3
$func = create_function('$o', '$o->myMethod();');
array_map($func, $objects);
Related
I'm making a array class and want a value to be able to be returned by a higher order function. The idea is that its a instance constant or method returned value such that I can skip the value in a map.
In other languages making an array or some compound value, like ['skip'] will make it pointer equal such that I can then use the operator for pointer equal and it will not be equal to other arrays with the exact same content, but my problem is that ['skip'] === ['skip'] is true so even with === the two values are the same.
Here is an example of usage of my code where I accedentally have the same value as I used to skip:
namespace Test;
use Common\Domain\Collection;
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? Collection::SKIP : ["skip"];
});
echo count($arr2); // prints 0, but should be 2
Is there a way to get a unique value or work around this somehow?
Here is code that implements Collection:
namespace Common\Domain;;
class Collection implements \Iterator, \Countable, \ArrayAccess
{
const SKIP = ["skip"];
private $arr = [];
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result !== self::SKIP) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
// implementation of interfaces \Iterator, \Countable, \ArrayAccess
public function current()
{
return current($this->arr);
}
public function next()
{
next($this->arr);
}
public function key()
{
return key($this->arr);
}
public function valid()
{
return isset($this->arr[$this->key()]);
}
public function rewind()
{
reset($this->arr);
}
public function count()
{
return count($this->arr);
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset)
{
return $this->arr[$offset];
}
public function offsetSet($offset, $value)
{
$this->arr[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->arr[$offset]);
}
}
I guess you are looking for Java-type enumerations, which doesn't exist in PHP. My best guess on your problem would be to use an object instead of a constant, that you would instantiate statically for a convenient use. Then, in the loop of your map function, you check the value with an instanceof instead of the basic equality operator, against the class you defined.
So, here :
class UniqueValue
{
public static function get()
{
return new self();
}
}
Then :
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? UniqueValue::get() : ["skip"];
});
And inside your collection :
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result ! instanceof UniqueValue) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
This is the quickest approach I can think of. If your array contains data from "outside" I don't think it's possible in any way that it matches against a class check from your own code.
I would solve this by implementing another method for this. The method delete would map a function over the collection and remove any elements where the function returns false.
e.g.
class Collection
{
// ...
public function delete($func)
{
$result = new static();
foreach($this->arr as $item)
{
if($func($item) !== false) $result[] = $item;
}
}
}
// example
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->delete(function ($v) {
return $v % 2 ? true : false;
});
var_dump($arr2); // prints [2, 4]
I need a way to iterate any array or object ($this->_data).
The current draft is following:
class values implements \Iterator{
private $_data = null; // array or object
private $_keys = [];
private $_key = false;
// ------------ \Iterator implementation
public function current(){
return $this->get($this->_key);
}
public function key(){
return $this->_key;
}
public function next(){
$this->_key = \next($this->_keys);
}
public function rewind(){
$this->_keys = [];
foreach ($this->_data as $k => $v){
$this->_keys[] = $k;
}
$this->_key = \reset($this->_keys);
}
public function valid(){
if (false === $this->_key){
return false;
}
return $this->has($this->_key);
}
}
The problem is that i do not want to hold additional array for keys.
May be there are some better way to iterate keys of an object avoiding additional abject/array creation for this purpose?
(External iterator is not an option because i do not want extra object creation in a foreach loops)
Example of mixed usage with native myClass::methods and wrapper's values::methods
class myClass{
var $x = 'x';
var $y = 'y';
public function hello(){
echo 'Hello, '.$this->x;
}
}
$a = new myClass();
$values = new values($a);
foreach ($values as $k => $v){
$values[$k] = $v.' modified';
}
$a->hello();
Additional notes:
values instance can be reused (by replacing $this->_data)
usage of this class can be very intensive (from 100 to over 100000 different objects/arrays passed)
values can be used and/or changed outside of values wrapper (see example above)
While it is possible to implement Iterator on your own in a way that you don't need to store $keys separately (I can show if you want) you can just use or extend the class ArrayObject. It looks like it perfectly fits your needs.
Check this example:
$a = new ArrayObject(array(
'a' => 'foo',
'b' => 'bar'
));
foreach($a as $k => $v) {
var_dump($k, $v);
}
Output:
string(1) "a"
string(3) "foo"
string(1) "b"
string(3) "bar"
For PHP 5.5.0+
Well, it actually fails, because it will create Generator instance at every call
class values implements IteratorAggregate{
private $_data = null; // array or object
public function __construct($data = null){
$this->_data = $data;
}
private static function g($data){
foreach ($data as $k => $v){
yield $k => $v;
}
}
public function getIterator(){
return self::g($this->_data);
}
}
Wish some alternative for php 5.4
Can you do something crazy like this
function cool_function($pizza, $ice_cream) {
make the arguments in the array into an array
return $array_of_paramaters // my new array
} // end function
print_r(cool_function); // outputs array(0 => pizza, 1=> ice cream)
Actually, this is pretty easy (and read the manual: func_get_args — Returns an array containing a function's argument list, see as well: Variable-length argument lists):
function cool_function($pizza, $ice_cream) {
return func_get_args();
}
but as asked in comments, why do you need this?
Or do you need the variable names? Reflection Docs is your friend:
function cool_named($neutron, $electron)
{
$f = new ReflectionFunction(__FUNCTION__);
$p = array();
foreach($f->getParameters() as $p1)
$p[] = '$'.$p1->name;
return $p;
}
var_dump(cool_named());
Or just both? (Edit: taking under-length, optional and over-length parameters into account):
function overall($neutron, $electron, $quark = 'xiaro')
{
$f = new ReflectionFunction(__FUNCTION__);
$defined = $f->getParameters();
$passed = func_get_args() + array_fill(0, count($defined), NULL);
foreach($defined as &$param)
$param = '$'.$param->name;
return array_combine($defined + array_keys($passed), $passed);
}
var_dump(overall('clara', 'peter', 'moon', 'jupiter'));
I'm not sure if you're looking for func_get_args which return all arguments passed to a function, or the ReflectionFunction class.
A basic example of func_get_args:
function cool_function($pizza, $ice_cream) {
return func_get_args();
}
But you don't need the arguments to make this work:
function cool_function() {
return func_get_args();
}
// cool_function(1,2,3) will return array(1,2,3)
The reflection option:
/**
* Returns an array of the names of this function
*/
function getMyArgNames($a,$b,$c)
{
$args = array();
$refFunc = new ReflectionFunction(__FUNCTION__);
foreach( $refFunc->getParameters() as $param ){
$args[] = $param->name;
}
return $args;
}
Or, for the really crazy:
/**
* Returns an associative array of the names of this function mapped
* to their values
*/
function getMyArgNames($a,$b,$c)
{
$received = func_get_args();
$i = 0;
$args = array();
$refFunc = new ReflectionFunction(__FUNCTION__);
foreach( $refFunc->getParameters() as $param ){
$args[$param->name] = $received[$i++];
}
// include all of those random, optional arguments.
while($i < count($received)) $args[] = $received[$i++];
return $args;
}
do you mean
function valuesToArray($value1,$value2){return array($value1,$value2);}
If I got you right, you so it like this:
function cool_function($pizza, $ice_cream) {
return array($pizza, $ice_cream);
}
but you could simply do that:
$new_var = array($pizza, $ice_cream);
I'm taking array output from a command-line program and parsing it into a PHP object. Consider this example of a very simple way to do this:
$output = explode("\n", shell_exec(myProg));
$obj = new MyObject();
$offset_field1 = 0;
$offset_field2 = 1;
$obj->Field1 = $output[$offset_field1];
$obj->Field2 = $output[$offset_field2];
This is a bit cumbersome, especially when the number of fields increases. Is there a better design pattern or method to accomplish the same feat in a less heavy-handed manner?
Why not put the assignment code in the object?
class MyObject
{
public function __construct(array $data)
{
$this->Field1 = $data['keyname1'];
$this->Field2 = $data['keyname2'];
...
}
}
or use the get magic method.
class MyObject
{
protected $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function __get($key)
{
$map = array('Field1' => 1, 'Feild2' => 2, ...);
if (isset($map[$key])) {
return $this->data[$map[$key]];
}
}
}
I guess this should work:
$output = explode("\n", shell_exec(myProg));
$obj = new MyObject();
foreach ($output as $key => $value)
{
$obj->{'Field' . ($key + 1)} = $value;
}
As it seems you cannot guess the field name from the outpout of your programm, you will have to define it somewhere.
$key_map = array('field_name1', 'field_name2', 'etc');
$obj = new MyObject();
foreach(explode("\n", shell_exec(myProg)) as $k => $v)
{
if(isset($key_map($k))
$obj->$key_map[$k] = $v;
}
This question already exists:
Closed 11 years ago.
Possible Duplicate:
Any way to access array directly after method call?
In C# and other languages, I can do something like this
$value = $obj->getArray()[0];
But not in PHP. Any workarounds or am I doomed to do this all the time?
$array = $obj->getArray();
$value = $array[0];
No, you can't do it without using array_shift (Which only gets the first element). If you want to access the third or fourth, most likely you'd want to do a function like this:
function elemnt($array, $element)
{
return $array[$element];
}
$value = element($obj->getArray(), 4);
Also, see this question, as it is an exact duplicate: Any way to access array directly after method call?
I think you are doomed to do it that way :(
You can do this:
$res = array_pop(array_slice(somefunc(1), $i, 1));
If this is a one-off or occasional thing where the situation in your example holds true, and you're retrieving the first element of the return array, you can use:
$value = array_shift($obj->getArray());
If this is a pervasive need and you often need to retrieve elements other than the first (or last, for which you can use array_pop()), then I'd arrange to have a utility function available like so:
function elementOf($array, $index = 0) {
return $array[$index];
}
$value = elementOf($obj->getArray());
$otherValue = elementOf($obj->getArray(), 2);
Well, maybe this could help you, in php spl, pretty usefull, you can make you a Special Array for you:
<?php
class OArray extends ArrayObject{
public function __set($name, $val) {
$this[$name] = $val;
}
public function __get($name) {
return $this[$name];
}
}
class O{
function __construct(){
$this->array = new OArray();
$this->array[] = 1;
$this->array[] = 2;
$this->array[] = 3;
$this->array[] = 4;
}
function getArray(){
return $this->array;
}
}
$o = new O();
var_dump( $o->getArray()->{1} );
<?php
class MyArray implements Iterator ,ArrayAccess
{
private $var = array();
//-- ArrayAccess
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->var[] = $value;
} else {
$this->var[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->var[$offset]);
}
public function offsetUnset($offset) {
unset($this->var[$offset]);
}
public function offsetGet($offset) {
return isset($this->var[$offset]) ? $this->var[$offset] : null;
}//-- Iterator
public function __construct($array){
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind() {
reset($this->var);
}
public function current() {
return current($this->var);
}
public function key() {
return key($this->var);
}
public function next() {
return next($this->var);
}
public function valid() {
return ($this->current() !== false);
}
}
$values = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
$it = new MyArray($values);
foreach ($it as $a => $b) {
print "$a: $b<br>";
}
echo $it["one"]."<br>";?>