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)
Related
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().
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);
I am trying to access properties of a custom object in PHP:
<?php
namespace App\Classes;
use Illuminate\Database\Eloquent\Model;
class AED extends Model {
protected $table = 'aeds';
protected $fillable = ['owner', 'street', 'postal_code', 'locality', 'latitude', 'longitude', 'annotation_type'];
public $timestamps = true;
public $id;
public $owner;
public $object;
public $street;
public $postalCode;
public $locality;
public $latitude;
public $longitude;
public $annotation_type;
public $distance;
public function set($data) {
foreach ($data as $key => $value) {
if(property_exists($this, $key)) {
$this->$key = $value;
}
}
}
}
The code to access these properties:
<?php
namespace App\Transformer;
use App\Classes\AED;
use League\Fractal\TransformerAbstract;
class AEDTransformer extends TransformerAbstract {
public function transform(AED $aed) {
return [
'data' => $aed->owner
];
}
}
When I call the function, I get this as a response:
{
data: [
{
data: null
}
],
meta: "TestMeta"
}
The strange thing is, when I just var_dump the object I get the full info:
...
protected 'original' =>
array (size=11)
'id' => int 1
'owner' => string 'Owner 1' (length=7)
'object' => string 'Object 1' (length=8)
'street' => string 'Street 1' (length=8)
'postal_code' => string '11111' (length=5)
'locality' => string 'Locality 1' (length=10)
'latitude' => float 100
'longitude' => float 100
'annotation_type' => string '1' (length=1)
'created_at' => string '0000-00-00 00:00:00' (length=19)
'updated_at' => string '0000-00-00 00:00:00' (length=19)
...
So the data can be taken from the database as expected and is being received as well. Why does the accessing not work then and I receive a "null".
I use the "set" method inside the custom function here:
class AEDHelper {
static public function searchAED($position) {
$var1 = $position['latitude'];
$var2 = $position['longitude'];
$var3 = $position['latitude'];
$queryResults = DB::select(DB::raw("SQLCODE"));
$results = [];
foreach ($queryResults as $results) {
$aed = new AED();
$aed->set($results);
$results[] = $aed;
}
return $results;
}
Here I create a new AED() instance. So I would guess I need to define the object properties therefore as now not Eloquent will be used but a custom AED class needs to be instantiated for displaying SQL results.
Best
you don't have to define fields in your model. Eloquent makes them available dynamically.
if you want to fill those fields you can without having them in your model. because the field will be available if you try to set a value for it.
here is how
$aed = new AED;
$aed->owner = "The Owner";
$aed->object = "The Object";
....
....
....
$aed->save();
or this will work as well
AED::create([
'owner' => "The Owner",
'object' => "The Object",
.....
.....
.....
]);
or if you want update an existing model.
$aed = AED::find(1);
// change owner
$aed->owner= "New Owner";
$aed->save();
Given:
class myClass extends \Phalcon\Mvc\Model
{
public $a;
protected $b;
private $c;
}
How can I test that $a is public, $b is protected, and $c is private from within myClass?
You can use ReflectionProperty -
class myClass
{
public $a;
protected $b;
private $c;
}
$obj = new myClass();
$reflect_a = new ReflectionProperty(get_class($obj), 'a');
$reflect_c = new ReflectionProperty(get_class($obj), 'c');
var_dump($reflect_a->isProtected());
var_dump($reflect_c->isPrivate());
Depending on the result you can hide or show them.
For your needs you can use use Models Meta-Data. You can get the attributes of the model within the model:
<?php
/**
* Posts Model
*
*/
class Posts extends \Phalcon\Mvc\Model
{
public $id;
public $users_id;
public $categories_id;
public $title;
public $slug;
public $content;
public $number_views;
public $number_replies;
public $votes_up;
public $votes_down;
public $sticked;
public $modified_at;
public $created_at;
public $edited_at;
public $status;
public $locked;
public $deleted;
public $accepted_answer;
private $foo_bar;
}
Somewhere in the controller:
var_dump($this->modelsMetadata->getAttributes(new Posts()));die;
Output:
array (size=18)
0 => string 'id' (length=2)
1 => string 'users_id' (length=8)
2 => string 'categories_id' (length=13)
3 => string 'title' (length=5)
4 => string 'slug' (length=4)
5 => string 'content' (length=7)
6 => string 'number_views' (length=12)
7 => string 'number_replies' (length=14)
8 => string 'votes_up' (length=8)
9 => string 'votes_down' (length=10)
10 => string 'sticked' (length=7)
11 => string 'created_at' (length=10)
12 => string 'modified_at' (length=11)
13 => string 'edited_at' (length=9)
14 => string 'status' (length=6)
15 => string 'locked' (length=6)
16 => string 'deleted' (length=7)
17 => string 'accepted_answer' (length=15)
Also you can create an model's method:
public function getAttributes()
{
$metaData = $this->getModelsMetaData();
return $metaData->getAttributes($this);
}
\Phalcon\Mvc\Model\MetaData::getAttributes Returns table attributes names - fields of table.
Also PHP's get_class_vars() returns an array of all properties visible in the current scope. In your case it should return all public properties.
I have exactly the same structure like in the phalcon models documentation:
http://docs.phalconphp.com/en/latest/_images/eer-1.png
In the models I implemented the following hasmany and belongsto lines:
Robots model:
class Robots extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public function initialize(){
$this->hasMany("id", "RobotsParts", "robots_id");
}
}
Parts model:
class Parts extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public function initialize(){
$this->hasMany("id", "RobotsParts", "parts_id");
}
}
RobotParts model:
class RobotsParts extends \Phalcon\Mvc\Model
{
public $id;
public $robots_id;
public $parts_id;
public function initialize(){
$this->belongsTo("robots_id", "Robots", "id");
$this->belongsTo("parts_id", "Parts", "id");
}
}
At this point I was hoping to get all the data by calling RobotParts::find(), but I can see only the id's.
For debuging I dumped, but find only the ids:(
$rp = RobotParts::find()->toArray();
var_dump($rp);
I would like to get something like this as result:
array (size=1)
0 =>
array (size=7)
'id' => int '1' (length=1)
'robots_id' => int '4' (length=1)
'name' => string 'r2d2' (length=4)
'type' => string 'droid' (length=5)
'year' => int '2184' (length=4)
'parts_id' => int '4' (length=1)
'name' => string 'wheel' (length=5)
var_dump() does not contains the related tables, needed to reference to it from view like:
robots.RobotParts.name