How to assign parameter value to anonymous function in PHP - php

How $item variable has a value from $products
$products = $products->map(function($item){
$item->getCustomPrices($this->user->client);
$item->getMarkUpPrices($this->user->client);
return $item;
});

A simple example to show you how to implement it
https://implode.io/Ce1azD
class Products
{
protected $items = [];
public function __construct($items)
{
$this->items = $items;
}
public function map(callable $callback)
{
return array_map($callback, $this->items);
}
}
$products = new Products([1, 2, 3, 4]);
return $products->map(function ($item) {
return $item * 2;
});

If you deep dive into laravel framework, the type of $products is Illuminate\Database\Eloquent\Collection. It implements the map method and the underlying implementation is:
public function map(callable $callback)
{
$result = parent::map($callback);
return $result->contains(function ($item) {
return ! $item instanceof Model;
}) ? $result->toBase() : $result;
}
The parent class of Illuminate\Database\Eloquent\Collection is Illuminate\Support\Collection and the underlying implementation is:
public function map(callable $callback)
{
$keys = array_keys($this->items);
$items = array_map($callback, $this->items, $keys);
return new static(array_combine($keys, $items));
}
It uses array_map method and it will apply the callback function to every value.
array array_map ( callable $callback , array $array1 [, array $... ] )

Related

Is it possible to make a unique value in PHP?

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]

PHP: Functionally obtaining array of properties from object

I have a simple class like this:
class MyClass {
public $color = 'red';
public $width = 200;
public $height = 100;
public function getValues(array $properties) {
return array_map(function($property) {
return $this->$property;
}, $properties);
}
}
I would like to do the following:
$values = (new MyClass)->getValues(['width', 'height']);
Where $values would end up containing this array: [200, 100].
The following example above works perfectly fine, so my question is:
How can we simplify the getValues() method? Surely there is a simpler way to achieve this?
Requirements:
It should be functional (no for/while loops). <- this criteria is met in my example
It should not use a callback function. <- this criteria is not met in my example
You could store your class-variables in an array and then use array_intersect_key() and array_flip() to achieve that. Here is an example.
class MyClass {
public $values = [
'color' => 'red',
'width' => '200',
'heigth' => '100'
];
public function getValues (array $properties) {
return array_intersect_key ($this -> values, array_flip ($properties));
}
//EDIT 2
public function set_value ($key, $value) {
$this -> values[$key] = $value;
}
}
//EDIT (thanks to mae)
It is also possible to generate an array of the class properties with ReflectionClass() and getProperties().
//EDIT (thanks to Nigel Ren)
Instead of Reflection it is possible to use get_object_vars() to achieve the same output.
class MyClass {
public $color = 'red';
public $width = '200';
public $heigth = '100';
public function getValues (array $properties) {
return array_intersect_key (get_object_vars ($this), array_flip ($properties));
}
}
Consider a new approach:
class MyClass
{
public $prop = array();
public function set($key, $value)
{
$this->prop[$key] = $value;
}
public function get($key)
{
return $this->prop[$key];
}
}
$o = new MyClass();
$o->set('color', 'blue');
$o->set('pi', 3.14);
$o->get('color'); // blue
$o->get('pi'); // 3.14

Use list() with an object in PHP

I would like to use the list() statement in combination with an object.
$tuple = new Tuple();
// ..
list ($guestbook, $entry, $author) = $tuple;
This would work if $tuple was an array with three elements. But its an object.
Is there any way without using a method of Tuple (returning that kind of array) like implementing a fancy native interface I yet don't know?
You can implement the interface ArrayAccess to do so:
class Tuple implements ArrayAccess {
private $arr;
public function __construct($arr) {
$this->arr = $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) {
return $this->arr[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->arr[$offset]);
}
}
$tuple = new Tuple([1, 2, 3]);
list($am, $stram, $gram) = $tuple;
echo $am;
echo $stram;
echo $gram;
// outputs: 123
See this previous post:
Convert PHP object to associative array
I am assuming (I haven't tested it) you could then do:
$tuple = new Tuple();
list ($guestbook, $entry, $author) = (array) $tuple;
You can do this:
$tumple = new Tumple();
$properties = get_object_vars($tumple);// ["guestbook" => "Feel", "entry" => "Good", "author" => "Inc"];

Passing array to a function inside class PHP

I have a question on how to pass an array to a function in PHP. I have a class called "MyClass" and inside it has functions called rankVal($arr1, $arr2) and processResponse($data, $db, $id, $lat, $lng).
processResponse() will call rankVal() and here is my problem is.
class MyClass{
private function cmpVal($a, $b){
/*do sorting stuff*/
}
function rankVal($arr1, $arr2){
$arrIdx=[];
foreach ($arr1 as $key => $value) {
$n=array_search($value, $arr2);
$newPos = ($key+$n)/2;
$arrNewIdx [$n]=round($newPos,0, PHP_ROUND_HALF_DOWN);
}
}
function processResponse($data, $db, $id, $lat, $lng){
//Do some stuffs here...
$someArr1 = [];
foreach($results as $key => $value){
$newVal = new stdClass();
$newVal->key1 = $value->key1;
$newVal->key2 = $value->key2;
$newVal->key3 = $value->key3;
$newVal->key4 = $value->key4;
$newVal->key5 = $value->key5;
$someArr1 []= $newVal;
}
$someArr2 = $someArr1;
usort($someArr2, array($this, "cmpVal"));
$rankedVal = $this->rankVal($someArr1, $someArr2);
}
}
When I called the processResponse() function I got this error:
array_search() expects parameter 2 to be array, object given
So, I var_dump($arr2) in rankVal(), and the output clearly says that $arr2 is an array. Here's the sample output of the var_dump($arr2):
array(30) {
[0]=>
object(stdClass)#385 (7) {
["key1"]=>
string(24) "something"
["key2"]=>
string(20) "something"
["key3"]=>
string(41) "something"
["key4"]=>
float(1.23455)
["key5"]=>
float(1.19128371983198)
}
What did I do wrong? I tried to pass the array by reference by adding "&" in rankVal(&$arr1, &$arr2), but the error is still there.
To add to the other answer here (which now seems to have gone), if you want your class to behave as an array where appropriate, you need to make it iterable.
All you need to do is implement Iterable. This means that you need to create the necessary methods, but this is all you need in order to have your class behave that way.
Its useful for classes which are designed to hold an array of data, but you want to encapsulate additional tools along with that data.
Heres an example:
class Row implements \Iterator {
protected $data;
protected $position = 0;
public function __construct( array $data = [ ]) {
$this->data = $data;
}
public function addData( $value ) {
$this->data[] = $value;
}
public function replaceData( $index, $value ) {
$this->data[ $index ] = $value;
}
public function getData() {
return $this->data;
}
public function setData( array $data ) {
$this->data = $data;
return $this;
}
/** Required by Iterator */
public function current() {
return $this->data[ $this->position ];
}
/** Required by Iterator */
public function next() {
++$this->position;
}
public function __toArray() {
return $this->data;
}
/** Required by Iterator */
public function key() {
return $this->position;
}
/** Required by Iterator */
public function valid( $index = null ) {
return isset( $this->data[ $index ? $index : $this->position ] );
}
/** Required by Iterator */
public function rewind() {
$this->position = 0;
}
public function count() {
return count( $this->data );
}
}
Once you have this, it can be used anywhere you can use an array.
So after checking my code again, I finally found the problem that causing this weird bug which was not supposed to be there.
The culprit is in the rankVal() function where I called usort() which used rankVal() as the callback function for the sorting process. Then, I changed this callback function to the right one, and voila problem's solved.
Thanks for everyone who had answered and given me some suggestions on how to fix it.

Filter ArrayObject (PHP)

I have my data in ArrayObject, simply representing an array. I need to filter the data, function array_filter() would work great. However, it does not work with ArrayObject as argument. What's the best way to treat with this? Is there any standard function which handles filtering for me?
Example:
$my_data = ArrayObject(array(1,2,3));
$result = array_object_filter($my_data, function($item) { return $item !== 2; });
Is there any array_object_filter function?
How about you export it to an actual array, and then create a new Array Object?
$my_data = new ArrayObject(array(1,2,3));
$result = new ArrayObject(
array_filter( (array) $my_data, function($item) {
return $item !== 2;
})
);
How about subclassing the ArrayObject and adding a new method to it:
/**
* Filters elements using a callback function.
*
* #param callable $callback The callback function to use
*
* #return self
*/
public function filter(/* callable */ $callback = null)
{
$this->exchangeArray(array_filter($this->getArrayCopy(), $callback));
return $this;
}
How about this:
$my_data = new ArrayObject(array(1,2,3));
$callback = function($item) { return $item !== 2; };
$result = new ArrayObject;
foreach ($my_data as $k => $item) if ($callback($item)) $result[$k] = $item;
Alternately, you can define an array_object_filter() function yourself:
function array_object_filter($array, $callback) {
$result = new ArrayObject;
foreach ($array as $k => $item) if ($callback($item)) $result[$k] = $item;
return $result;
}

Categories