Can't access $this on PHP Closure passed to an object method - php

I'm trying to pass a closure that uses object data without having to assign the object to a variable and passing it as a parameter to the closure, but I can't seem to figure out the right way to do it.
Here is my arbitrary class code:
class Person
{
var $__data = [];
function setData($key, $value)
{
$this->__data[$key] = $value;
return $this;
}
function setAutoData($map)
{
$this->__data = array_merge($this->__data, $map());
return $this;
}
function getData($key)
{
return $this->__data[$key];
}
}
This piece of code here will work and add the first_name to the data array:
$p = (new Person())->setData('full_name', 'Valerie Maddison Bricks');
print_r($p->setAutoData(function () use($p) {
return [
'first_name' => array_shift(explode(' ', $p->getData('full_name')))
];
}));
/*
Output:
Person Object
(
[__data] => Array
(
[full_name] => Valerie Maddison Bricks
[first_name] => Valerie
)
)
*/
This one doesn't work.
print_r((new Person())
->setData('full_name', 'Valerie Maddison Bricks')
->setAutoData(function () {
return [
'first_name' => array_shift(explode(' ', $this->getData('full_name'))),
];
}));
/*
Output:
Fatal error: Uncaught Error: Using $this when not in object context in C:\Damian\xampp\web\dbo-dev\teste.php:44
Stack trace:
#0 C:\Damian\xampp\web\dbo-dev\teste.php(22): {closure}()
#1 C:\Damian\xampp\web\dbo-dev\teste.php(46): Person->setAutoData(Object(Closure))
#2 {main}
thrown in C:\Damian\xampp\web\dbo-dev\teste.php on line 44
*/
Is there a way to achieve that in a similar way without relying on a variable?

To avoid the use of $p, you can pass $this in the call of the closure. But you can't use $this in an anonymous function.
$this->__data = array_merge($this->__data, $map($this));
Then,
->setAutoData(function ($object) {
$array = explode(' ', $object->getData('full_name'));
return [
'first_name' => array_shift($array),
];
})
Note that array_shift requires a reference. You should create a variable for that.
Code:
class Person
{
private $__data = [];
public function setData($key, $value)
{
$this->__data[$key] = $value;
return $this;
}
public function setAutoData($map)
{
$this->__data = array_merge($this->__data, $map($this));
return $this;
}
public function getData($key)
{
return $this->__data[$key];
}
}
print_r(
(new Person())
->setData('full_name', 'Valerie Maddison Bricks')
->setAutoData(function ($object) {
$array = explode(' ', $object->getData('full_name'));
return [
'first_name' => array_shift($array),
];
})
);
Output:
Person Object
(
[__data:Person:private] => Array
(
[full_name] => Valerie Maddison Bricks
[first_name] => Valerie
)
)

Related

Recursive function return issue

I'm writing a recursive function like below:
private function getManager($employee)
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
return;
}
This function receive an employee and find his manage. If find a manage, then push manager id into an array ($this->managers on line 5). Then call this function recursively and pass manager as an employee. If no manager found on line 3, then this function just return (line 8).
So my question is, is their any problem if i'm not return the recursive call at line 6 ($this->getManager($manager);)
Not sure if this is what you think, but it works.
function getManagers($employee)
{
$managers = [];
if (isset($employee->manager)) {
array_push($managers, $employee->manager->id);
array_push($managers, ...getManagers($employee->manager));
}
return $managers;
}
No, there is absolutely no benefit in writing the empty return. The method will halt regardless of the existence of the return.
Please observe the two methods below which show identical, error-less outcomes regardless of the return.
Code: (Demo)
class Management
{
private $managers = [];
function __construct($managerTree)
{
$this->getManager($managerTree);
var_export($this->managers);
echo "\n---\n";
$this->managers = [];
var_export($this->managers);
echo "\n---\n";
$this->getManager2($managerTree);
var_export($this->managers);
}
private function getManager(?object $employee): void
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
return;
}
private function getManager2(?object $employee): void
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
}
}
new Management(
(object) [
'id' => 3,
'manager' => (object) [
'id' => 2,
'manager' => (object) [
'id' => 1,
'manager' => null
]
]
]
);
Output:
array (
0 => 2,
1 => 1,
)
---
array (
)
---
array (
0 => 2,
1 => 1,
)

Transform array using a declarative approach

Given the following code:
$flat = [
[ '10', 'hoho'],
[ '10', null],
[ '13', null],
[ '10', 'ahha']
];
//imperative, procedural approach
$hierarchical = [];
foreach ($flat as $entry) {
$id = $entry[0];
$hierarchical[$id]['id'] = $id;
$hierarchical[$id]['microtags'] = $hierarchical[$id]['microtags'] ?? [];
if ($entry[1] != null)
array_push($hierarchical[$id]['microtags'], $entry[1]);
}
And its result ($hierarchical):
array (
10 =>
array (
'id' => '10',
'microtags' =>
array (
0 => 'hoho',
1 => 'ahha',
),
),
13 =>
array (
'id' => '13',
'microtags' =>
array (
),
),
)
Is it possible to refactor it to a reasonably efficient declarative/functional approach? Like using array transformation functions (map,reduce,filter,etc)? Also without changing references or altering the same variable. If so, how?
Creating and traversing trees of different shape is best accomplished by using functions. Below, we create functions node_create and node_add_child which encode our intention. Finally, we use array_reduce to complete the transformation. $flat remains untouched; our reducing operation only reads from the input data.
function node_create ($id, $children = []) {
return [ "id" => $id, "children" => $children ];
}
function node_add_child ($node, $child) {
return node_create ($node['id'], array_merge ($node['children'], [ $child ]));
}
$flat =
[ [ '10', 'hoho' ]
, [ '10', null ]
, [ '13', null ]
, [ '10', 'ahha' ]
];
$result =
array_reduce ($flat, function ($acc, $item) {
list ($id, $value) = $item;
if (! array_key_exists ($id, $acc))
$acc [$id] = node_create ($id);
if (! is_null ($value))
$acc [$id] = node_add_child ($acc [$id], $value);
return $acc;
}, []);
And the result
print_r ($result);
// Array
// (
// [10] => Array
// (
// [id] => 10
// [children] => Array
// (
// [0] => hoho
// [1] => ahha
// )
// )
// [13] => Array
// (
// [id] => 13
// [children] => Array
// (
// )
// )
// )
Above, we use an associative array for $acc which means we have to use PHP's built-in functions for interaction with associative arrays. We can abstract away PHP's ugly, non-functional interfaces for more favourable ones.
function has ($map, $key) {
return array_key_exists ($key, $map);
}
function get ($map, $key) {
return $map [$key];
}
function set ($map, $key, $value = null) {
$map [$key] = $value;
return $map;
}
We move the logic for adding null children to node_add_child
function node_create ($id, $children = []) {
return [ "id" => $id, "children" => $children ];
}
function node_add_child ($node, $child = null) {
if (is_null ($child))
return $node;
else
return node_create ($node['id'], array_merge ($node['children'], [ $child ]));
}
Now we can see a much more declarative reduce
function make_tree ($flat = []) {
return
array_reduce ($flat, function ($acc, $item) {
list ($id, $value) = $item;
return
set ( $acc
, $id
, has ($acc, $id)
? node_add_child (get ($acc, $id), $value)
: node_add_child (node_create ($id), $value)
);
}, []);
}
print_r (make_tree ($flat));
// same output as above
Above, we see how has, get, and set can simplify our reduce operation. However, this kind of approach can lead to lots of small, separated functions. Another approach involves inventing your own data type that satisfies your needs. Below, we scrap the separated functions we created above and trade them for a class, MutableMap
class MutableMap {
public function __construct ($data = []) {
$this->data = $data;
}
public function has ($key) {
return array_key_exists ($key, $this->data);
}
public function get ($key) {
return $this->has ($key)
? $this->data [$key]
: null
;
}
public function set ($key, $value = null) {
$this->data [$key] = $value;
return $this;
}
public function to_assoc () {
return $this->data;
}
}
Now instead of having to pass $acc, to each function, we swap it out for $map which is an instance of our new type
function make_tree ($flat = []) {
return
array_reduce ($flat, function ($map, $item) {
list ($id, $value) = $item;
return
$map -> set ( $id
, $map -> has ($id)
? node_add_child ($map -> get ($id), $value)
: node_add_child (node_create ($id), $value)
);
}, new MutableMap ())
-> to_assoc ();
}
Of course you could swap node_create and node_add_child out for a class-based implementation, class Node { ... }. This exercise is left for the reader.
function make_tree ($flat = []) {
return
array_reduce ($flat, function ($map, $item) {
list ($id, $value) = $item;
return
$map -> set ( $id
, $map -> has ($id)
? $map -> get ($id) -> add_child ($value)
: (new Node ($id)) -> add_child ($value)
);
}, new MutableMap ())
-> to_assoc ();
}

How to access data in nested Std Object array in PHP

I need to access the data: 'hotelID', 'name', 'address1','city' etc. I have the following Std Object array ($the_obj) in PHP that contains the following data:
object(stdClass)[1]
public 'HotelListResponse' =>
object(stdClass)[2]
public 'customerSessionId' => string '0ABAAA87-6BDD-6F91-4292-7F90AF49146E' (length=36)
public 'numberOfRoomsRequested' => int 0
public 'moreResultsAvailable' => boolean false
public 'HotelList' =>
object(stdClass)[3]
public '#size' => string '227' (length=3)
public '#activePropertyCount' => string '227' (length=3)
public 'HotelSummary' =>
array (size=227)
0 =>
object(stdClass)[4]
public 'hotelId' => 112304
public 'name' => La Quinta Inn and Suites Seattle Downtown
public 'address1' => 2224 8th Ave
public 'city' => Seattle
public 'stateProvinceCode' => WA
public 'postalCode' => 98121
public 'countryCode' => US
public 'airportCode' => SEA
public 'propertyCategory' => 1
public 'hotelRating' => 2.5
I have tried the following for lets say to access the 'name':
echo $the_obj->HotelListResponse->HotelList->HotelSummary[0]->name;
Also I have tried to print each key and value pairs by using foreach loop but I keep on getting errors. Here is what I tried:
foreach ($the_obj->HotelListResponse->HotelList->HotelSummary[0] as $key => $value){
echo $key.' : '.$value.'<br />';
}
Here are the errors that I get:
Trying to get property of non-object
Warning: Invalid argument supplied for foreach()
Thank you everyone for answering, I have figured out the way to access the 'hotelID', 'name' and all other keys and value pairs in the deepest nest of the array.
I converted the Std Object array to an associative array, then I accessed each of the value by using the foreach loop:
foreach ($the_obj["HotelListResponse"]["HotelList"]["HotelSummary"] as $value){
echo $value["hotelId"];
echo $value["name"];
//and all other values can be accessed
}
To access both (Keys as well as values):
foreach ($the_obj["HotelListResponse"]["HotelList"]["HotelSummary"] as $key=>$value){
echo $key.'=>'.$value["hotelId"];
echo $key.'=>'.$value["name"];
//and all other keys as well as values can be accessed
}
Regarding to #Satya's answer I'd like to show simpler way for Object to array conversion, by using json functions:
$obj = ...
$tmp = json_encode($obj);
$objToArray = json_decode($tmp,true);
This way you can easily access array items. First you can dump structure...
try something like this :
$p=objectToArray($result);
recurse($p);
}
function objectToArray( $object )
{
if( !is_object( $object ) && !is_array( $object ) )
{
return $object;
}
if( is_object( $object ) )
{
$object = get_object_vars( $object );
}
return array_map( 'objectToArray', $object );
}
function recurse ($array)
{
//statements
foreach ($array as $key => $value)
{
# code...
if( is_array( $value ) )
{
recurse( $value );
}
else
{ $v=$value;
$v=str_replace("’",'\'',strip_tags($v));
$v=str_replace("–",'-',$v);
$v=str_replace("‘",'\'',strip_tags($v));
$v=str_replace("“",'"',strip_tags($v));
$v=str_replace("”",'"',strip_tags($v));
$v=str_replace("–",'-',strip_tags($v));
$v=str_replace("’",'\'',strip_tags($v));
$v=str_replace("'",'\'',strip_tags($v));
$v=str_replace(" ",'',strip_tags($v));
$v=html_entity_decode($v);
$v=str_replace("&",' and ',$v);
$v = preg_replace('/\s+/', ' ', $v);
if($key=="image")
{
if(strlen($v)==0)
{
echo '<'.$key .'>NA</'.$key.'>';
}
else
{
echo '<'.$key .'>'. trim($v) .'</'.$key.'>';
}
}
}
}
}

How to give object a name

When I do the following:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = (object) $arUserStuff;
print_r($object);
The print function returns me the following:
stdClass Object ( [name] => username [email] => test#test.com )
How can I change std class object in let's say's User Object?
Create that class, then create an object of it:
class User {
public $name, $email; // public for this example, or set these by constructor
public function __construct( array $fields) {
foreach( $fields as $field => $value)
$this->$field = $value;
}
}
$object = new User;
$object->name = 'username';
$object->email = 'test#test.com';
Or, you can do:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = new User( $arUserStuff);
Now, from print_r( $object);, you'll get something like this:
User Object ( [name] => username [email] => test#test.com )
actually to do what you want, you should make it like:
$arUserStuff = new ArrayObject(
array (
'name' => 'username', 'email' => 'test#test.com'
)
);
to change the class name you need to create a new class.
It's a rather complex process but you can learn about it here:
http://php.net/manual/en/language.oop5.php
Here's a generic function that converts an array into any type of object, assuming the fields are public
class User { public $name, $email; }
class Dog { public $name, $breed; }
function objFromArray($className, $arr) {
$obj = new $className;
foreach(array_keys(get_class_vars($className)) as $key) {
if (array_key_exists($key, $arr)) {
$obj->$key = $arr[$key];
}
}
return $obj;
}
print_r(objFromArray('User',
array ('name' => 'username', 'email' => 'test#test.com')));
echo "<br/>";
print_r(objFromArray('Dog',
array ('name' => 'Bailey', 'breed' => 'Poodle')));
Output
User Object ( [name] => username [email] => test#test.com )
Dog Object ( [name] => Bailey [breed] => Poodle )
I wanted to make a trait out of it but don't have PHP 5.4 installed to test it. This wouldn't require the fields to be public
trait ConvertibleFromArray {
public static function fromArray($arr) {
var $cls = get_called_class();
var $obj = new $cls;
foreach($arr as $key=>$value) {
if (property_exists($obj, $arr)) {
$obj->$key = $value;
}
}
return $obj;
}
}
class User {
use ConvertibleFromArray;
public $name, $email;
}
class Dog {
use ConvertibleFromArray;
public $name, $breed;
}
print_r(User::fromArray(array ('name' => 'username', 'email' => 'test#test.com')));
print_r(Dog::fromArray(array('name' => 'Bailey', 'breed' => 'Poodle')));
?>

What is the right way to create nested classes in PHP?

I need to create a nested structure class of a multidimensional array, this is my array:
Array
(
[days] => Array
(
[0] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 1
[name] => Activity 2
[city] => London
[info] => fsdsdshgsfd
)
[1] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
)
)
[1] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
...
)
)
)
...
)
)
I have been trying to rewrite my code to make it class-driven, but I am struggling with that, what is the right way to build a class structure Itinerary->Days->Rows to replace an array? I tried something like this, I am not sure if it makes sense, I don't really understand the way how it has to be done:
class Itinerary
{
private $days = array();
public static function addDay($day) {
$this->$days[] = new ItineraryDay($day);
}
}
class ItineraryDay implements Countable
{
private $rows = array();
public static function addRow($row) {
$this->$rows[] = new ItineraryRow($row);
}
public function count()
{
return count($this->rows);
}
}
class ItineraryRow implements Countable
{
private $name;
private $city;
...
function __get($key)
{
...
}
function __set($key, $value)
{
...
}
public function count()
{
return count($this->rows);
}
}
$itinerary1 = new Itinerary();
$day1 = new ItineraryDay();
$itinerary1->addDay($day1);
$row1 = new ItineraryRow();
$day1->addRow($row1);
Can someone guide me?
It really depends what you ultimately want to do with said structure, but for a general idea I typically do something like this:
class Itinerary implements Countable
{
private $days;
public function __construct( array $days = array() )
{
$this->setDays( $days );
}
public function addDay( ItineraryDay $day )
{
$this->days[] = $day;
}
public function setDays( array $days )
{
$this->days = array();
foreach( $days as $day )
{
$this->addDay( $day );
}
}
public function count()
{
return count( $this->days );
}
}
class ItineraryDay implements Countable
{
private $rows;
public function __construct( array $rows = array() )
{
$this->setRows( $rows );
}
public function addRow( ItineraryRow $row )
{
$this->rows[] = $row;
}
public function setRows( array $rows )
{
$this->rows = array();
foreach( $rows as $row )
{
$this->addRow( $row );
}
}
public function count()
{
return count( $this->rows );
}
}
class ItineraryRow
{
private $id;
private $name;
private $city;
private $info;
public function __construct( $id, $name, $city, $info )
{
$this->id = $id;
$this->name = $name;
$this->city = $city;
$this->info = $info;
}
/* ... */
}
Then using it with the structure of your current array of data:
$days = array();
foreach( $originalData[ 'days' ] as $days )
{
$rows = array();
foreach( $days[ 'rows' ] as $row )
{
$rows[] = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] );
}
$days[] = new ItineraryDay( $rows );
}
$itinerary = new Itinerary( $days );
Or:
$itinerary = new Itinerary;
foreach( $originalData[ 'days' ] as $days )
{
$day = new ItineraryDay;
foreach( $days[ 'rows' ] as $row )
{
$row = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] )
$day->addRow( $row );
}
$itinerary->addDay( $day );
}
So, you can either pass "child" objects to the constructor (the method that constructs a new object), or add them with methods after construction. If you want the objects to be immutable, meaning you don't want to allow the objects to accept any more rows / days after construction, just make the addDay, setDays, addRow and setRows methods protected or private thereby only allowing passing "child" object through the constructors.
Be aware that, as PeeHaa already mentioned, you don't want static methods, because they operate class wide, not on individual instances of classes (objects). As a matter of fact, you cannot even use static methods the way you intended, because $this is only available in object context, not in class wide context.
But, to be honest, the question is a little bit to vague to be answered properly. We'd have to have a little more details about how you are going to construct the objects, and how you are going to use them later on.

Categories