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.
Related
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'm trying to create a general method to automatically instantiate objects from a query like this:
SELECT town.*, content.*, user.*
FROM townhub.content
LEFT JOIN town ON content.townReceiver = town.id_town
LEFT JOIN user ON content.author = user.id_user
The method that I want to build should return 3 type of objects: Town, User and Content into an array. I thought on something like that:
protected function build_objects($result, Array $classes) {
$data = array();
$i = 0;
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
foreach ($classes as $class) {
$$class = new $class;
$$class = $$class->fill_object($$class, $row);
$data[$i][$class] = $$class;
}
$i++;
}
return $data;
}
And then, in each class, a method like that:
public function fill_object($object, Array $row) {
$attributes = get_object_vars($object);
foreach ($row as $attribute => $value) {
foreach ($attributes as $objAtt => $emptyValue) {
if ($attribute == $objAtt) {
$object->$attribute = $value;
}
}
}
return $object;
}
Actually, this is doing what I want, the following array (was printed using using var_dump($data) in build_objects() ):
array (size=4)
0 =>
array (size=3)
'Content' =>
object(Content)[4]
protected 'id_content' => string '1' (length=1)
public 'title' => string 'Hello World!' (length=10)
public 'description' => string 'Hello world description' (length=43)
public 'category' => string '1' (length=1)
public 'date' => string '2015-01-01' (length=10)
public 'townReceiver' => string '1' (length=1)
public 'author' => string '1' (length=1)
private 'dbHost' (Model) => string 'localhost' (length=9)
private 'dbUser' (Model) => string 'root' (length=4)
private 'dbPass' (Model) => string 'root' (length=4)
private 'dbName' (Model) => string 'townhub' (length=7)
private 'conn' (Model) => null
'Town' =>
object(Town)[5]
protected 'id_town' => string '1' (length=1)
public 'name' => string 'Isaac' (length=5)
public 'population' => string '750' (length=3)
private 'dbHost' (Model) => string 'localhost' (length=9)
private 'dbUser' (Model) => string 'root' (length=4)
private 'dbPass' (Model) => string 'root' (length=4)
private 'dbName' (Model) => string 'townhub' (length=7)
private 'conn' (Model) => null
'User' =>
object(User)[6]
protected 'id_user' => string '1' (length=1)
protected 'dni' => string '20011225' (length=9)
private 'password' => string '1234' (length=4)
public 'name' => string 'Isaac' (length=5)
public 'firstSurname' => string 'Surname1' (length=5)
public 'secondSurname' => string 'Surname2' (length=5)
public 'birthdate' => string '0000-00-00' (length=10)
public 'gender' => string 'H' (length=1)
public 'email' => string 'isaac#mail.com' (length=19)
public 'isAdmin' => string '1' (length=1)
public 'id_town' => string '1' (length=1)
private 'dbHost' (Model) => string 'localhost' (length=9)
private 'dbUser' (Model) => string 'root' (length=4)
private 'dbPass' (Model) => string 'root' (length=4)
private 'dbName' (Model) => string 'townhub' (length=7)
private 'conn' (Model) => null
The problem is that when I fech_array (in fill_object() method) town's names are overridden by user's names; so I can't get the town's name. The easy solution is to change attribute's names on db and classes; but I think that will be a bad solution...
Keep in mind that attribute's names should be equals on db and classes, so alias in the query is not possible.
There are any way to get with fetch_array an Array with key [table.attribute] instead [attribute]?
I would also like to know better ways to do this if what I want to do is not possible.
<?php
class DataCollectionHelper
{
/**
* $result var is the result of:
* "SELECT *
* FROM townhub.content".
*
* As you left join your tables, it may appear that you haven't any towns and users
* for particular content.
*
* #param $result
*
* #return array
*/
protected function build_objects($result)
{
$data = array();
$i = 0;
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
$contentObject = new Content();
$contentObject->fill_object($row);
$data[$i]['Content'] = $contentObject;
$data[$i]['User'] = $this->getUserByContentAuthor($contentObject->getAuthor());
$data[$i]['Town'] = $this->getTownByContentByTownReceiver($contentObject->getTownReceiver());
$i++;
}
return $data;
}
/**
* #param integer $townReceiver
*
* #return Town
*/
private function getTownByContentByTownReceiver($townReceiver)
{
/**
* Here you have to get town data by your $townReceiver - property
* and return the object of Town
*/
}
/**
* #param integer $author
*
* #return User
*/
private function getUserByContentAuthor($author)
{
/**
* The same here for users
*/
}
}
class Content
{
/**
* #var integer
*/
protected $content_id;
/**
* #var string
*/
public $title;
/**
* #var integer
*/
public $townReceiver;
/**
* #var string
*/
public $description;
/**
* #var integer
*/
public $author;
/* other vars */
/**
* #param array $row
*
* #return $this
*/
public function fill_object(array $row)
{
/*
* It's not the best approach, to do like that.
* It's better to use setters or just set your properties
* $this->title = $row['title']; etc
*
* Still you could use $this instead of your $object variable
*/
$attributes = get_object_vars($this);
foreach ($row as $attribute => $value) {
foreach ($attributes as $objAtt => $emptyValue) {
if ($attribute == $objAtt) {
$this->$attribute = $value;
}
}
}
return $this;
}
/**
* #return int
*/
public function getTownReceiver()
{
return $this->townReceiver;
}
/**
* #return int
*/
public function getAuthor()
{
return $this->author;
}
}
so i am using an API whose return is protected members.
My code :-
var_dump($a)
Gets me something like this :-
array (size=1)
0 =>
object(Klarna\XMLRPC\Address)[26]
protected 'email' => string '' (length=0)
protected 'telno' => string '' (length=0)
protected 'cellno' => string '' (length=0)
protected 'fname' => string 'Testperson-se' (length=13)
protected 'lname' => string 'Approved' (length=8)
How can I access the protected members?
I tried to do it via :- var_dump($addrs->country) but it doesn't work.
You cannot access a protected method/property outside of that class. That's why is called "protected". You have to create a public getter:
public function getEmail()
{
return $this->email;
}
and use it as $a->getEmail(); and so on for rest of properties you need
Why does findone() returns all public property as null.
class User extends \yii\db\ActiveRecord
{
public $id;
public $username;
public $password;
public $authKey;
public $accessToken;
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
$result = static::findOne(['username' => $username]);
var_dump($result);
return $result;
}
This returns
object(app\models\User)[81]
public 'id' => null
public 'username' => null
public 'password' => null
public 'authKey' => null
public 'accessToken' => null
private '_attributes' (yii\db\BaseActiveRecord) =>
array (size=6)
'id' => int 1
'username' => string 'admin' (length=5)
'password' => string '123456' (length=6)
'auth_key' => string 'jkkk' (length=4)
'created' => null
'modified' => null
You should simply remove db attributes from your model :
class User extends \yii\db\ActiveRecord
{
public static function findByUsername($username)
{
....
Yii automatically defines an attribute in Active Record for every column of the associated table. You should NOT redeclare any of the attributes.
Read more : http://www.yiiframework.com/doc-2.0/guide-db-active-record.html
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