Set anonymous class as parameter of a function - php

I am writing an small ODM for Mongodb in PHP by PhpStorm. At the aggregate query:
$value = $this->creditRepo->execute(AggregateQueryBuilder::create()->project('name', 'ts')->take(10)->toQuery(),
new class{
public ?string $name;
});
the second parameter is an anonymous class. And inside of execute() function:
function execute(AggregateQuery $query, $class): ArrayObject
{
$result = $this->_collection->aggregate($query->getPipeline(), $query->getOptions());
$returnArray = new ArrayObject();
foreach ($result as $item){
$itemx = App::jsonMapper()->map($item, new $class); // $itemx is instance of $class
$returnArray->append($itemx);
}
return $returnArray;
}
The result in caller script:
$value = $this->creditRepo->execute(AggregateQueryBuilder::create()
->project('name', 'ts')->take(10)->toQuery(), new class{
public ?string $name;
});
$d = $value->getArrayCopy();
$d[0]-> // not type hint, I want it hint to me the <name>
Can PhpStorm know the output of the execute() that I can type hint the output of it?
[Edit]
I have try with function array_map
$value = $this->creditRepo->execute(AggregateQueryBuilder::create()
->project('name', 'ts')->take(10)->toQuery());
$temp = new class{
public ?string $name;
};
$output = array_map(static function ($item) use ($temp) {
$temp->name = $item['name'];
return $temp;
}, (array)$value);
$output[0]->name // type hinted
and I want to write a function that PhpStorm know that return like array_map how to do it?

Related

PHP - passing anonymous function as a parameter

Is it possible to pass an anonymous function as a parameter in PHP? And if yes - how?
I am trying to pass an anonymous function to a setter which will fill an array with values returned from that function.
class MyClass
{
private $arr = array();
public function __construct()
{
$this->setArrElm('New', function(){return 123;});
}
private function setArrElm($name, $val)
{
// here: gettype($val) == object
$this->arr[$name] = $val;
}
}
Please note the comment - the type of val is object and I expect an int.
In PHP 7 you can self execute the closure
class MyClass
{
private $arr = array();
public function __construct()
{
$this->setArrElm('New', (function(){return 123;})()); //<-- self execute
}
private function setArrElm($name, int $val) //<-- added typehint
{
// here: gettype($val) == object
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Output
123
Sandbox
This takes a form similar to JS (probably other languages too):
(function(){return 123;})()
It's important to know that it's executing the function, then passing the result. You can pass the closure (which is an object) and then execute it, too. But if you have strict types and need an int, you can self execute the closure too.
It really only makes sense to do this if you need an int as the argument. Even in that case you can execute it beforehand and then pass the result. This just saves you a local variable.
For < PHP7 or just because
Alt1
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var()); //<-- execute
}
private function setArrElm($name, $val) //<-- added typehint
{
// here: gettype($val) == object
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Alt2
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var);
}
private function setArrElm($name, $val) //<-- mixed
{
if(gettype($val) == 'object' && is_a($val, '\Closure')){
//is a closure, you could use is_callable etc. too. see __invoke()
$val = $val();
}
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Alt3
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var);
}
private function setArrElm($name, $val) //<-- mixed
{
if(is_callable($val)){
//pass functions (as a string) or arrays or closures(executable classes with __invoke)
$val = call_user_func($val);
}
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Cheers

The Scoping of Static Variables in PHP Anonymous Functions

I have some trouble,When I define a static variable in a method and call it multiple times, the code is as follows:
function test()
{
static $object;
if (is_null($object)) {
$object = new stdClass();
}
return $object;
}
var_dump(test());
echo '<hr>';
var_dump(test());
The output is as follows:
object(stdClass)[1]
object(stdClass)[1]
Yes, they return the same object.
However, when I define a closure structure, it returns not the same object.
function test($global)
{
return function ($param) use ($global) {
//echo $param;
//exit;
static $object;
if (is_null($object)) {
$object = new stdClass();
}
return $object;
};
}
$global = '';
$closure = test($global);
$firstCall = $closure(1);
$closure = test($global);
$secondCall = $closure(2);
var_dump($firstCall);
echo '<hr>';
var_dump($secondCall);
The output is as follows:
object(stdClass)[2]
object(stdClass)[4]
which is why, I did not understand.
By calling test(...) twice in your sample code, you have generated two distinct (but similar) closures. They are not the same closure.
This becomes more obvious some some subtle improvements to your variable names
$closureA = test($global);
$firstCall = $closureA(1);
$closureB = test($global);
$secondCall = $closureB(2);
var_dump($firstCall, $secondCall);
Now consider this code alternative:
$closureA = test($global);
$firstCall = $closureA(1);
$secondCall = $closureA(2);
var_dump($firstCall, $secondCall);
Does that help you understand?

PHP : Get array from customs Objects functions

I search for an php function which would do the following process:
class Item{
private $id;
public function __construct($id){
$this->id = $id
}
public function foo(){
return 'Item_'.$this->id;
}
}
my_array = array();
$my_array[] = new Item(1);
$my_array[] = new Item(2);
$results = thefunctionimlookingfor($my_array, 'foo');
//which give :
//$results = {'Item_1', 'Item_2'}
Does anyone knows this function name?
$results = array_map(function(Item $item) { return $item->foo(); }, $my_array);
And if you really need to have that function:
function applyMethod($data, $method) {
return array_map(function($item) use ($method) {
return $item->$method();
}, $data);
}
$result = applyMethod($my_array, 'foo');

Readonly multidimensional array property, PHP

I've been fooling with ArrayAccess and PHP's magic (__get, __set) for awhile now, and I'm stuck.
I'm trying to implement a class in which some properties, which are arrays, are read only. They will be set initially by the constructor, but should not be modifiable thereafter.
Using __get magic by reference, I can access array elements arbitrarily deep in the properties, and I was thinking I can throw exceptions when those properties are targeted via __set.
The problem is though, when I'm accessing the value of an array element, PHP is calling __get to return that part of the array by reference, and I have no knowledge of whether or not its a read or write action.
(The worst part is I knew this going in, but have been fooling with ArrayAccess as a possible workaround solution, given the properties were instances of an implemented object)
Simple example:
class Test{
public function &__get($key){
echo "[READ:{$key}]\n";
}
public function __set($key, $value){
echo "[WRITE:{$key}={$value}]\n";
}
}
$test = new Test;
$test->foo;
$test->foo = 'bar';
$test->foo['bar'];
$test->foo['bar'] = 'zip';
And the output:
[READ:foo]
[WRITE:foo=bar]
[READ:foo]
[READ:foo] // here's the problem
Realistically, I only need the value foo (as per my example) anyways, but I need to know it's a write action, not read.
I've already half accepted that this cannot be achieved, but I'm still hopeful. Does anyone have any idea how what I'm looking to accomplish can be done?
I was considering some possible workarounds with ArrayAccess, but so far as I can tell, I'll end up back at this spot, given I'm going to use the property notation that invokes __get.
Update: Another fun day with ArrayAccess.
(This is a different issue, but I suppose it works in. Posting just for kicks.)
class Mf_Params implements ArrayAccess{
private $_key = null;
private $_parent = null;
private $_data = array();
private $_temp = array();
public function __construct(Array $data = array(), $key = null, self $parent = null){
$this->_parent = $parent;
$this->_key = $key;
foreach($data as $key => $value){
$this->_data[$key] = is_array($value)
? new self($value, $key, $this)
: $value;
}
}
public function toArray(){
$array = array();
foreach($this->_data as $key => $value){
$array[$key] = $value instanceof self
? $value->toArray()
: $value;
}
return $array;
}
public function offsetGet($offset){
if(isset($this->_data[$offset])){
return $this->_data[$offset];
}
// if offset not exist return temp instance
return $this->_temp[$offset] = new self(array(), $offset, $this);
}
public function offsetSet($offset, $value){
$child = $this;
// copy temp instances to data after array reference chain
while(!is_null($parent = $child->_parent) && $parent->_temp[$child->_key] === $child){
$parent->_data[$child->_key] = $parent->_temp[$child->_key];
$child = $parent;
}
// drop temp
foreach($child->_temp as &$temp){
unset($temp);
}
if(is_null($offset)){
$this->_data[] = is_array($value)
? new self($value, null, $this)
: $value;
}else{
$this->_data[$offset] = is_array($value)
? new self($value, $offset, $this)
: $value;
}
}
public function offsetExists($offset){
return isset($this->_data[$offset]);
}
public function offsetUnset($offset){
unset($this->_data[$offset]);
}
}
You need to use a second class, implementing ArrayAccess, to use instead of your arrays. Then you will be able to control what is added to the array with the offsetSet() method:
class ReadOnlyArray implements ArrayAccess {
private $container = array();
public function __construct(array $array) {
$this->container = $array;
}
public function offsetSet($offset, $value) {
throw new Exception('Read-only');
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
if (! array_key_exists($offset, $this->container)) {
throw new Exception('Undefined offset');
}
return $this->container[$offset];
}
}
You can then initialize your ReadOnlyArray with your original array:
$readOnlyArray = new ReadOnlyArray(array('foo', 'bar'));
You could not return by ref, which would solve the problem of changability, but would not allow changing of some values that are allowed to be changed.
Alternatively you need to wrap every returned array in ArrayAccess, too - and forbid write access there.

php bind variable to function's scope in older PHP

I would like to bind a variable to a function's scope, I can do this in php use the 'use' keyword after PHP 5.3, however how do I do the equivalent in versions < PHP 5.3?
test_use_keyword();
function test_use_keyword(){
$test =2;
$res=array_map(
function($el) use ($test){
return $el * $test;
},
array(3)
);
print_r($res);
}
You can use a global variable, but you should always avoid globals variables whereever possible. As a suggestion, without knowing, what you are trying to solve with this
class Xy ( {
private $test;
public function __construct ($test) {
$this->test = $test;
}
public function call ($el) {
return $el * $this->test;
}
}
print_r(array_map(array(new Xy(2), 'call'), array(3));
Also possible are the good old lambdas
$test = 2;
$a = create_function ('$el', 'return $el * ' . $test . ';');
print_r (array_map($a, array(3)));
Normally through globals, seriously. Although hacks could be used to mimic the functionality, like partial functions in php. Extracted from article:
function partial()
{
if(!class_exists('partial'))
{
class partial{
var $values = array();
var $func;
function partial($func, $args)
{
$this->values = $args;
$this->func = $func;
}
function method()
{
$args = func_get_args();
return call_user_func_array($this->func, array_merge($args, $this->values));
}
}
}
//assume $0 is funcname, $1-$x is partial values
$args = func_get_args();
$func = $args[0];
$p = new partial($func, array_slice($args,1));
return array($p, 'method');
}
And only after that could you have something like.
function multiply_by($base, $value) {
return $base * $value;
}
// ...
$res = array_map(partial("multiply_by", $test), array(3));
Not... worth... it.

Categories