PHP: Looping over an object loops over member array - php

foreach ($pElements as $pElement) {
var_dump($pElement);
}
If I execute the above, I notice it dumps each value in the 'nodes' array even though $pElement is an object. Can anyone explain this behavior?
At first I thought running foreach on an object automatically searches for a member array, but the first listed array namespaces seems to be ignored.
Here is the full object $pElements:
/var/www/html/phpTestArea/index.php:35:
object(Symfony\Component\DomCrawler\Crawler)[38]
protected 'uri' => null
private 'defaultNamespacePrefix' => string 'default' (length=7)
private 'namespaces' =>
array (size=0)
empty
private 'baseHref' => null
private 'document' =>
object(DOMDocument)[2]
public 'doctype' => string '(object value omitted)' (length=22)
public 'implementation' => string '(object value omitted)' (length=22)
<public properties removed>
private 'nodes' =>
array (size=2)
0 =>
object(DOMElement)[36]
<public properties removed>
1 =>
object(DOMElement)[35]
<public properties removed>
private 'isHtml' => boolean true

If you take a look at the source code of that class here, you can see that it implements two interfaces, defined as follow:
Countable: this define how the instances of the class should behave when passed to the native php count function. The counting depends on the implementation.
interface Countable {
abstract public count(void): int
}
IteratorAggregate: this is the one that return an iterator (which extends Traversable) that defines how and what should be traversed.
interface IteratorAggregate extends Traversable {
abstract public getIterator(void): Traversable
}
So if you look closely into the source code for these two functions, you will see how these are implemented:
/**
* #return int
*/
public function count()
{
return \count($this->nodes);
}
/**
* #return \ArrayIterator|\DOMElement[]
*/
public function getIterator()
{
return new \ArrayIterator($this->nodes);
}
So if you call count($pElements), the object's internal count function will be executed, therefore you would get the count of the nodes property.
In the same way, if you iterate with a foreach over $pElements, you are iterating over the nodes property, as this is the behaviour defined by getIterator().

Related

Why query statement results on eloquent orm is returning null values

I'm new to this questions thing so please bear with me.
I'm using Eloquent as my PHP database library. So I created a class that extends from Illuminate\Database\Eloquent\Model and tried to query one single record. When I print the results I know it is fetching the information, as you can see by the protected attributes, but somehow the public attributes of the record are NULL.
Am I missing some previous configuration, or is there another reason for that?
Here's my structure:
The Model, Plantilla.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Plantilla extends Model
{
/**
* #var string
*/
protected $primaryKey = 'cod_plantilla';
/**
* #var string
*/
protected $table = 'plantilla';
protected $connection = 'mysql';
public function __construct()
{
#attributes
parent::__construct();
Database2::init();
}
}
Database.php
<?php
namespace App\Models;
use Illuminate\Database\Capsule\Manager as Capsule;
class Database2
{
private static $db;
static public function init()
{
if (is_null(self::$db)) {
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => getenv('DB_HOST'),
'database' => getenv('DB_NAME'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASS'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
], 'mysql');
// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();
// Setup the Eloquent ORM... (optional; unless you've used setEventDispatcher())
$capsule->bootEloquent();
}
}
}
index.php
$p = Plantilla::where('cod_plantilla', 35)->first();
var_dump($p);
Result
object(App\Models\Plantilla)[251]
protected 'primaryKey' => string 'cod_plantilla' (length=13)
protected 'table' => string 'plantilla' (length=9)
protected 'connection' => string 'mysql' (length=5)
# Values I need
public 'cod_area_interna' => null
public 'cod_tipo_plantilla' => null
public 'nombre' => null
public 'detalle' => null
public 'personalizada' => null
public 'fecha' => null
# Values I need
protected 'keyType' => string 'int' (length=3)
public 'incrementing' => boolean true
protected 'with' =>
array (size=0)
empty
protected 'withCount' =>
array (size=0)
empty
protected 'perPage' => int 15
public 'exists' => boolean true
public 'wasRecentlyCreated' => boolean false
# Same values I need but they're protected
protected 'attributes' =>
array (size=7)
'cod_plantilla' => int 35
'cod_area_interna' => int 2
'cod_tipo_plantilla' => int 1
'nombre' => string 'Some' (length=32)
'detalle' => string 'Some' (length=142)
'personalizada' => null
'fecha' => string '2020-06-25 12:15:13' (length=19)
protected 'original' =>
array (size=7)
'cod_plantilla' => int 35
'cod_area_interna' => int 2
'cod_tipo_plantilla' => int 1
'nombre' => string 'Some' (length=32)
'detalle' => string 'Some' (length=142)
'personalizada' => null
'fecha' => string '2020-06-25 12:15:13' (length=19)
protected 'changes' =>
...
As the documentation states, you can do something like this
<?php
$flights = App\Models\Flight::all();
foreach ($flights as $flight) {
echo $flight->name;
}
So you can access the attributes aka table columns values.
In my case those are:
cod_plantilla
cod_area_interna
cod_tipo_plantilla
nombre
detalle
personalizada
fecha
I am not sure what you are actually asking about here.
The attributes are not 'properties' of the class. They are held in a protected array named $attributes. If you want to access them you can do that in the way the documentation says you can.
You could access them via the dynamic property:
$p = Plantilla::find(35);
echo $p->nombre;
Via array access:
echo $p['nombre'];
You can get the array of attributes themselves:
dump($p->getAttributes());
Serialize the model's attributes (and loaded relationships) to an array:
dump($p->toArray());
Or even get the serialized model as JSON:
echo $p->toJson();
It looks to me like Eloquent is actually performing exactly as it should be!
Eloquent does a lot of automagic with PHP magic methods. What looks like regular PHP object properties are actually dynamically accessed via __get() and __set() from the $attribute property.
In your example you have this:
$p = Plantilla::where('cod_plantilla', 35)->first();
Using Tinker (if you're new to Laravel, it's easiest to use php artisan tinker to figure out this sort of stuff), you should be able to just try accessing your database columns like this:
> $p->cod_plantilla;
35
> $p->fecha;
'2020-06-25 12:15:13'
You should find your values are returned, even though var_dump() shows nothing!

PHP display nested Object with array of other Object as 1 nested array

I have big Object with protected properties and a property can be an array of other Objects. My goal is to print this entire Object as a single nested array. So I need to convert the object to an array.
I've tried doing:
$result = (array) $object;
But this converts only the highest lever object to an array and it messes up my protected properties names with weird question mark signs.
I've also tried something like this but this simply returns an empty array:
$result= json_decode(json_encode($object), true);
Here is what my object looks like:
object(Handling\Model\SearchBooking\Booking)[133]
protected 'jabooknr' => string '018024709' (length=9)
protected 'jitsbooknr' => string '' (length=9)
protected 'status' => string 'Y' (length=1)
protected 'platform' => int 4
protected 'agentid' => string '' (length=6)
protected 'paymentInfo' => null
protected 'transports' =>
array (size=2)
0 =>
object(Handling\Model\SearchBooking\Transport)[145]
protected 'depdate' =>
object(DateTime)[146]
public 'date' => string '2016-12-06 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
protected 'carriercode' => string 'TB' (length=2)
protected 'carriernumber' => string '2067' (length=4)
protected 'brochure' => string '' (length=6)
protected 'pax' =>
array (size=2)
0 =>
object(Handling\Model\SearchBooking\Pax)[147]
protected 'id' => int 1
protected 'title' => string 'MRS' (length=3)
protected 'firstname' => string 'MA' (length=7)
protected 'name' => string 'BEN' (length=5)
protected 'age' => int 58
protected 'luggage' => int 20
protected 'handLuggage' => null
1 =>
object(Handling\Model\SearchBooking\Pax)[148]
protected 'id' => int 2
protected 'title' => string 'MR' (length=2)
protected 'firstname' => string 'P' (length=6)
protected 'name' => string 'FT' (length=4)
protected 'age' => int 60
protected 'luggage' => int 20
protected 'handLuggage' => null
protected 'departureAirport' => string 'BRU' (length=3)
protected 'arrivalAirport' => string 'AGP' (length=3)
1 =>
object(Handling\Model\SearchBooking\Transport)[149]
protected 'depdate' =>
object(DateTime)[150]
public 'date' => string '2016-12-13 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
protected 'carriercode' => string 'TB' (length=2)
protected 'carriernumber' => string '2068' (length=4)
protected 'brochure' => string '' (length=6)
protected 'pax' =>
array (size=2)
0 =>
object(Handling\Model\SearchBooking\Pax)[151]
protected 'id' => int 1
protected 'title' => string 'MRS' (length=3)
protected 'firstname' => string 'MANE' (length=7)
protected 'name' => string 'BN' (length=5)
protected 'age' => int 58
protected 'luggage' => int 20
protected 'handLuggage' => null
1 =>
object(Handling\Model\SearchBooking\Pax)[152]
protected 'id' => int 2
protected 'title' => string 'MR' (length=2)
protected 'firstname' => string 'PIRE' (length=6)
protected 'name' => string 'FYT' (length=4)
protected 'age' => int 60
protected 'luggage' => int 20
protected 'handLuggage' => null
protected 'departureAirport' => string 'AGP' (length=3)
protected 'arrivalAirport' => string 'BRU' (length=3)
protected 'extraLuggage' => null
EDIT
I have a method in my class where I "find" the result that looks like this:
public function findBooking()
{
//here happens a bunch of logic to get the right result
var_dump($object); exit; // this is the result that is show above
return $object;
}
There are a few issues, that make this difficult.
Property visibility, (private, protected) can cause issues when trying to read them outside of the class, proper. This is expected behavior as that's the point to not use public.
Classes are different. They are well defined and we know them ahead of time, but they are too diverse to account of all property names, at least not with a lot of wasted effort. Not to mention defining them "hard coding" would bite you later as it would make it difficult to maintain. For example if one of the packages does an update and you have coded the property names in you may have issues if they change them. On top of this given that these properties are not part of the classes Public "API" but instead part of the internals, it would not be unreasonable for them to change.
Properties can contain a mix of data types, including other classes or objects. This can make it challenging to handle.
Classes are part of other packages/frameworks and editing them is not practical, this restricts us to working outside of these classes.
So given these difficulties I would recommend using reflection to access the protected properties. Reflection allows you to inspect the definition of classes (and other stuff).
function jsonSerialize($obj){
return json_encode(toArray($obj));
}
function toArray($obj){
$R = new ReflectionObject($obj);
$proerties = $R->getProperties();
$data = [];
foreach($proerties as $k => $v){
$v->setAccessible(true);
$property = $v->getName();
$value = $v->getValue($obj);
if(!is_object($value)){
$data[$property] = $value;
}else if( is_a($obj,'\\DateTime')){
//if its a descendant of Datetime, get a formatted date.
// you can add other special case classes in this way
$data[$property] = $value->format('Y-m-d H:i:s');
}else{
$data[$property] = toArray($value); //call recursively
}
}
return $data;
}
So assume we have these classes
class foo{
private $bar; //private nested object
public function __construct(){
$this->bar = new bar();
}
}
class bar{
private $something = 'hello';
}
$obj = new foo;
echo jsonSerialize($obj);
See it in a sandbox here
Outputs:
{"bar":{"something":"hello"}}
Also of note is we have a special consideration for the DateTime class. Instead of getting all the properties of this we just want the date (probably) formatted in some standard way. So by using is_a() (I'm old school) we can tell if the Object $value has a given class as an ancestor of it. Then we just do our formatting.
There are probably a few special cases like this, so I wanted to mention how to handle them.
Though it is an old query, most answers are not easy to follow. So I tried to simplify the code for this specific question.
The cleaner way to get JSON objects is by implementing the JsonSerializable interface.
class Booking implements JsonSerializable
{
protected $jabooknr;
protected $platform;
//Other attributes ....
//Array of tronsport
protected $transports;
protected $extraLuggage;
public function jsonSerialize()
{
return [
'jabooknr'=> $this->jabooknr,
'platform'=> $this->platform,
'transports' => [json_encode($this->transports)
],
'$extraLuggage' => $this->extraLuggage
];
}
public function __construct($jabooknr, $platform){
$this->jabooknr = $jabooknr;
$this->platform = $platform;
$this->transports=[new Transport()];
}
}
class Transport implements JsonSerializable{
protected $carriercode;
protected $carriernumber;
//Array of Pax
protected $pax ;
public function jsonSerialize()
{
return [
'carriercode'=> $this->carriercode,
'carriernumber'=> $this->carriernumber
];
}
}
$booking = new Booking('018024709',25);
echo json_encode($booking);

How to convert Doctrine array to PHP associative array

I'm integrating Doctrine2 into CodeIgniter.
My Entity class News.php
<?php
namespace Models\Entities;
/**
* News
*
* #Table(name="news", indexes={#Index(name="slug", columns={"slug"})})
* #Entity
*/
class News {
//HERE: properties, getter, setter, etc.
}
And my model class News_model.php
<?php
require_once(APPPATH."models/entities/News.php");
use Models\Entities\News;
class News_model extends CI_Model {
//Model code here
}
When I use $news = $this->em->getRepository('Entities:News')->findAll() in News_model class and printed, var_dump($news), I get an array of object(Models\Entities\News), like follow:
array (size=6)
0 =>
object(Models\Entities\News)[87]
private 'id' => int 1
private 'title' => string 'text here' (length=9)
private 'slug' => string '' (length=0)
private 'text' => string 'text here' (length=9)
private 'news' => null
)
But I expected an associative array, like follow:
array (size=6)
0 =>
array (size=4)
'id' => string '1' (length=1)
'title' => string 'text here' (length=9)
'slug' => string '' (length=0)
'text' => string 'text here' (length=9)
)
How can I convert the Doctrine Entity object (first showed array) result to a PHP associative array (second showed array)?
You are working with Doctrine ORM. ORM means Object Relational Mapper. You use ORM because you want to get results as objects. Otherwise you better start to read about Doctrine DBAL.
Then this line:
$news = $this->em->getRepository('Entities:News')->findAll();
If you use findAll() then you expect an Collection of objects. In Doctrine we talk about collections instead of array's.
Those collections you can simple walk through with a foreach just like a normal array. Then you can use each object inside the collection which has some benefits: especial directly call some custom methods
$newitems = $this->em->getRepository('Entities:News')->findAll();
foreach($newsitems as $newsitem)
{
echo '<h3>' . $newsitem->getTitle() . '</h3>';
}
why don't you use native doctrine method getArrayResult in your class repository?
In your controller :
/***/
$news = $this->em->getRepository('Entities:News')->yourMethodName();
/***/
In your class Repository :
class NewsRepository extends \Doctrine\ORM\EntityRepository
{
public function yourMethodName()
{
$query = $this->createQueryBuilder('n');
/***/
return $query->getQuery()->getArrayResult();
}
}
I agree with #Frank B, the reason you use Doctrine is that you get to work with objects instead of a magic array.
However, if you are set on having an array, you can use the Symfony Serializer to convert any object to an array.
Just add some annotations to your entity:
use Symfony\Component\Serializer\Annotation\Groups;
class News {
/**
* #Groups({"group1"})
*/
protected $id;
/**
* #Groups({"group1"})
*/
protected $title;
/**
* #Groups({"group1"})
*/
protected $slug;
}
Then you can convert your array collection like this:
$news = $this->em->getRepository('Entities:News')->findAll();
$serializer = $this->getContainer()->get('serializer');
$newsArray = $serializer->normalize($news, 'json', ['groups' => ['group1']]);

PHP ArrayAccess set multidimensional

EDIT: I realized the amount of text might be intimidating. The essence of this question:
How to implement ArrayAccess in a way that makes setting multidimensional values possible?
I am aware that this was discussed here already but I seem unable to implement the ArrayAccess interface correctly.
Basically, I've got a class to handle the app configuration with an array and implemented ArrayAccess. Retrieving values works fine, even values from nested keys ($port = $config['app']['port'];). Setting values works only for one-dimensional arrays, though: As soon as I try to (un)set a value (eg. the port in the previous example), i get the following error message:
Notice: Indirect modification of overloaded element <object name> has no effect in <file> on <line>
Now the general opinion seems to be that the offsetGet() method has to return by reference (&offsetGet()). That, however, does not solve the problem and I'm afraid I don't know how to implement that method correctly - why is a getter method used to set a value? The php doc here is not really helpful either.
To directly replicate this (PHP 5.4-5.6), please find a sample code attached below:
<?php
class Config implements \ArrayAccess
{
private $data = array();
public function __construct($data)
{
$this->data = $data;
}
/**
* ArrayAccess Interface
*
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function &offsetGet($offset)
{
return isset($this->data[$offset]) ? $this->data[$offset] : null;
}
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
}
$conf = new Config(array('a' => 'foo', 'b' => 'bar', 'c' => array('sub' => 'baz')));
$conf['c']['sub'] = 'notbaz';
EDIT 2: The solution, as Ryan pointed out, was to use ArrayObject instead (which already implements ArrayAccess, Countable and IteratorAggregate).
To apply it to a class holding an array, structure it like so:
<?php
class Config extends \ArrayObject
{
private $data = array();
public function __construct($data)
{
$this->data = $data;
parent::__construct($this->data);
}
/**
* Iterator Interface
*
*/
public function getIterator() {
return new \ArrayIterator($this->data);
}
/**
* Count Interface
*
*/
public function count()
{
return count($this->data);
}
}
I used this for my Config library libconfig which is available on Github under the MIT license.
I am not sure if this will be useful. I have noticed that the ArrayObject class is 'interesting'...
I am not sure that this is even an 'answer'. It is more an observation about this class.
It handles the 'multidimensional array' stuff correctly as standard.
You may be able to add methods to make it do more of what you wish?
<?php //
class Config extends \ArrayObject
{
// private $data = array();
public function __construct(array $data = array())
{
parent::__construct($data);
}
}
$conf = new Config(array('a' => 'foo', 'b' => 'bar', 'c' => array('sub' => 'baz')));
$conf['c']['sub'] = 'notbaz';
$conf['c']['sub2'] = 'notbaz2';
var_dump($conf, $conf['c'], $conf['c']['sub']);
unset($conf['c']['sub']);
var_dump('isset?: ', isset($conf['c']['sub']));
var_dump($conf, $conf['c'], $conf['c']['sub2']);
Output:
object(Config)[1]
public 'a' => string 'foo' (length=3)
public 'b' => string 'bar' (length=3)
public 'c' =>
array
'sub' => string 'notbaz' (length=6)
'sub2' => string 'notbaz2' (length=7)
array
'sub' => string 'notbaz' (length=6)
'sub2' => string 'notbaz2' (length=7)
string 'notbaz' (length=6)
string 'isset?: ' (length=8)
boolean false
object(Config)[1]
public 'a' => string 'foo' (length=3)
public 'b' => string 'bar' (length=3)
public 'c' =>
array
'sub2' => string 'notbaz2' (length=7)
array
'sub2' => string 'notbaz2' (length=7)
string 'notbaz2' (length=7)

In a Laravel 5 Collection how do you return an array of objects instead of an array of arrays?

I am using Laravel 5 and a Blade template. In a view I want to iterate over an array of Model objects, not an array of arrays. If I did want to iterate over an array of arrays I would do the following, which works as expected:
$models = Foo::where('id', '>', 5)->get();
return view('home.index', ['models' => $models->toArray()]);
However I want an array of objects with accessible properties. If I were to run:
$models = Foo::where('id', '>', 5)->get();
return view('home.index', ['models' => $models->all()]);
The var_dump would look like this:
object(Illuminate\Support\Collection)[164]
protected 'items' =>
array (size=3)
0 =>
object(App\Foo)[172]
public 'id' => null
public 'foo' => null
private 'created_at' => null
private 'updated_at' => null
protected 'connection' => null
protected 'table' => null
protected 'primaryKey' => string 'id' (length=2)
protected 'perPage' => int 15
public 'incrementing' => boolean true
public 'timestamps' => boolean true
protected 'attributes' =>
array (size=4)
'id' => int 1
'foo' => string 'Foo!' (length=4)
'created_at' => string '2015-02-27 15:44:09' (length=19)
'updated_at' => null
Not only is the Model in an 'items' object the properties are not filled.
In a view I would like to do something like this:
#foreach ($models as $model)
#include('_partial') {
'id' => $model->id,
'foo' => $model->foo,
}
#endforeach
How do I get an array of Models instead of an array of an array of Models?
According to the Docs:
$array = $collection->all();
"The all method returns the underlying array represented by the collection."
Your code is just fine, except you don't need to call toArray on your Eloquent query result. Let me explain what the code does, so you can understand why the following is what you want:
$models = Foo::where('id', '>', 5)->get();
return view('home.index', ['models' => $models]);
The first statememt Foo::where('id', '>', 5)->get(); returns a value of type Illuminate\Support\Collection.
That Collection class holds the collection elements in a protected property called $items (as you could see from your dump protected 'items' =>), which is of type array. The class also implements an interface called IteratorAggregate, which basically means it allows any variable of that type to be iterated using a foreach statement.
In your case this means that, even if $models is of type Illuminate\Support\Collection it will behave as an array when you go over it with foreach:
#foreach ($models as $model)
{
{{ $model->foo }}
}
So in short Collection is an iterable object that can be treated as an array, but is better than an array because if offers extra methods that allow you to manipulate the items from the collection. You can check the Collection API to see a complete list of available methods.
So in reality you're getting an improved array of models.
Also, don't worry that the properties are not filled, they are in fact filled, I just think you're looking in the wrong place.
If you look closely at your var_dump, you'll see that you have some lines that start with public, protected or private. Those keywords mean that those lines contain object properties. In the case of Laravel Eloquent models, the values fetched from the database are not stored directly in the properties that are named like the database columns. The values are in fact stored in a single property called attributes and are fetched using PHP's magic _get. Take a look at the comments on the code below:
object(Illuminate\Support\Collection)[164]
protected 'items' =>
array (size=3)
0 =>
object(App\Foo)[172]
public 'id' => null // <<< THE VALUES ARE
public 'foo' => null // <<< NOT STORED HERE
private 'created_at' => null
private 'updated_at' => null
protected 'connection' => null
protected 'table' => null
protected 'primaryKey' => string 'id' (length=2)
protected 'perPage' => int 15
public 'incrementing' => boolean true
public 'timestamps' => boolean true
protected 'attributes' =>
array (size=4)
'id' => int 1 // <<< THEY ARE HERE
'foo' => string 'Foo!' (length=4) // <<< AND HERE
'created_at' => string '2015-02-27 15:44:09' (length=19)
'updated_at' => null
Laravel does a lot of trickery behind the scenes to allow you to get things done with only a few lines of code. That's why a var_dump will not always display the simple data structures that you might expect.
Figured out the problem. I was explicitly defining the attributes in the Model. Laravel uses __get() in a particular way which causes passed parameters to be overridden whatever attributes are explicitly defined.
In other words, I was getting null values in the partial because the info I was passing to the partial was overridden.

Categories