ZF2 Hydrator and LEFT JOIN - php

I am hydrating a MySQL result:
public function getMiscdamage($id)
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('misc_damage');
$select->where(array('vehicle_id = ?' => $id))->order('date_added DESC');
$select->join('user','user.user_id = misc_damage.added_user_id', array('added_user_display_name' => 'display_name'), 'left');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet(new \Zend\Stdlib\Hydrator\ClassMethods(), new \Application\Model\Miscdamage());
return $resultSet->initialize($result);
}
throw new \InvalidArgumentException("Vehicle with given ID:{$id} not found.");
}
The query has a LEFT join, and the query DOES work. But when I var_dump in a loop through, the added_user_display_name` is null:
/var/www/zf-skeleton/module/Application/view/application/live/details.phtml:337:
object(Application\Model\Miscdamage)[662]
protected 'id' => string '100' (length=3)
protected 'vehicle_id' => string '8' (length=1)
protected 'added_user_id' => string '1' (length=1)
protected 'added_user_display_name' => null
protected 'description' => string 'dfgdggfdd' (length=9)
protected 'date_added' => string '2016-04-15 12:26:40' (length=19)
protected 'date_repaired' => null
protected 'repaired_user_id' => null
protected 'status' => string '0' (length=1)
How do I go about using a left join and hydrator? Any help is appreciated.

Your sql result is a response from a functionnal need of data. Therefore, you should use a custom hydrator built specifically for this functionnal need. This hydrator is often an aggregator hydrator which uses multiple hydrator to build a custom model. You should check it out

Related

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);

Initialize a hydrator result set and aggregate in ZF2

I am joining two tables and have successfully managed to write a hydrator that outputs the following array:
$sql = new Sql($this->dbAdapter);
$select = $sql->select('misc_damage');
$select->where(array('vehicle_id = ?' => $id))->order('date_added DESC');
$select->join('user','user.user_id = misc_damage.added_user_id',
array(
'user_display_name' => 'display_name',
'user_email' => 'email',
'user_username' => 'username'
),
'left');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$hydrator = new AggregateHydrator();
$hydrator->add(new ClassMethods());
$hydrator->add(new \Application\Hydrator\UserHydrator());
$miscDamage = $hydrator->hydrate($result->current(), new \Application\Model\Miscdamage());
var_dump($miscDamage);
die();
}
This produces 1 result:
/var/www/zf-skeleton/module/Application/src/Application/Mapper/ZendDbSqlMapper.php:95:
object(Application\Model\Miscdamage)[655]
protected 'id' => string '97' (length=2)
protected 'vehicle_id' => string '3' (length=1)
protected 'added_user_id' => string '1' (length=1)
protected 'description' => string 'sdfsdsdf' (length=8)
protected 'date_added' => string '2016-04-15 08:19:17' (length=19)
protected 'date_repaired' => null
protected 'repaired_user_id' => null
protected 'status' => string '0' (length=1)
protected 'user' =>
object(Application\Model\User)[664]
protected 'user_id' => string '1' (length=1)
protected 'username' => null
protected 'email' => string 'alex#home.com' (length=13)
protected 'display_name' => string 'Alex' (length=4)
There should be multiple results as each vehicle can have multiple damage entries. How would I go about using HydratingResultSet with my AggregateHydrator? I would also like to initialize the result: return $resultSet->initialize($result);
Any help is appreciated!
I found out how to do this. Need to use HydratingResultSet and Reflection:
use Zend\Stdlib\Hydrator\Reflection as ReflectionHydrator;
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$hydrator = new AggregateHydrator();
$hydrator->add(new ClassMethods());
$hydrator->add(new \Application\Hydrator\UserHydrator());
$resultSet = new HydratingResultSet(new ReflectionHydrator, new \Application\Model\Miscdamage());
$resultSet->setHydrator($hydrator);
return $resultSet->initialize($result);
}

Accessing object properties in PHP

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();

JMS serializer - Why are new objects not being instantiated through constructor

Why are new entities instantiated with null for all values except the data in the json, why is the entity constructor not setting defaults - putting a die() in the constructor never gets executed.
Update:
Ok so digging into the code, when no managed entity is found, JMSS will use the doctrine instantiator class to create the entity - its sole job, to create entities without calling the constructor. Is there a reason for this? this is inside JMS\Serializer\Construction\UnserializeObjectConstructor
I've configured the object constructor to use the doctrine object constructor written by JMS, but the same issue happens with and without this.
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
Existing entities are updated without trouble, however new entities are missing all constructor set defaults.
Under 'fields' element 0 is existing, element 1 is new.
array (size=3)
'id' => int 2
'name' => string 'Categories' (length=10)
'fields' =>
array (size=2)
0 =>
array (size=7)
'id' => int 49
'displayName' => string 'Car Branded' (length=11)
'type' => string 'checkboxlist' (length=12)
'required' => boolean false
'disabled' => boolean false
'name' => string 'h49' (length=3)
1 =>
array (size=3)
'type' => string 'email' (length=5)
'name' => string 'field3491' (length=9)
'displayName' => string 'Email' (length=5)
The entity looks like this after deserializing:
object(stdClass)[2000]
public '__CLASS__' => string 'AppBundle\Entity\FormElement' (length=28)
public 'id' => null
public 'label' => string 'Email' (length=5)
public 'type' => string 'email' (length=5)
public 'defaultValue' => null
public 'required' => null
public 'mappedField' => null
public 'garbageCollection' => null
public 'sortOrder' => null
public 'disabled' => null
public 'uuid' => null
public 'form' => null
public 'multiOptions' => null
public 'filters' => null
public 'submissions' => null
The entity constructor:
public function __construct()
{
$this->required = false;
$this->disabled = false;
$this->garbageCollection = false;
$this->sortOrder = 0;
$this->type = 'text';
}
And finally this is how im deserializing:
$serializer = $this->get('jms_serializer');
$entryForm = $serializer->deserialize($json_data, 'AppBundle\Entity\EntryForm', 'json');
The issue is the default ObjectConstructor uses Doctrine's Instantiator, which does not call the class' constructor. To solve this, you can create your own ObjectConstructor that just returns a new instance of the class.
Example:
<?php
namespace AppBundle\Serializer;
use JMS\Serializer\Construction\ObjectConstructorInterface;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\VisitorInterface;
class ObjectConstructor implements ObjectConstructorInterface
{
/**
* {#inheritdoc}
*/
public function construct(
VisitorInterface $visitor,
ClassMetadata $metadata,
$data,
array $type,
DeserializationContext $context
) {
$className = $metadata->name;
return new $className();
}
}
If you're using the bundle, just set jms_serializer.unserialize_object_constructor.class parameter to that new class. Otherwise in your builder, use the class as your object constructor.
What worked for me was simply adding this to the jms_serializer config:
jms_serializer:
object_constructors:
doctrine:
fallback_strategy: "fallback"

PHP object properties are null after instantiation

Here is what is making my brain hurt. First, I know just enough php to explain it in about 5 minutes. I am however fairly versed in object oriented programming. The situation:
I am trying to build a php application to be used as an intranet only tool. My thought is to save the current user in the session. I created a PHP model of my database user table. In the code I am authenticating the user and if the user authenticates then I am creating a User object and saving it in the session. The problem is that the User object is created but all of the properties are null. I have researched on here and php net for some answers and have tried several things but the problem persists. I do not think it is php related, I absolutely think it is something I am doing wrong but just can't put a finger on it. Here is the relevant code sample from the login script. To get here the password must validate, the variable $users is the direct return from the function in my database controller. Since the variable is included in the var_dump the code returning that variable may not be needed. I have tried passing the relevant array elements separately to the constructor, I have tried passing the full array, I have used serialize and unserialize. I have tried to set them using $this->property = $someValue .
if ($hash) {
$current_user = new User($users);
$_SESSION['current_user'] = serialize($current_user);
$current_user = unserialize($_SESSION['current_user']);
var_dump(session_id());
var_dump($users);
var_dump($current_user);
var_dump($_SESSION['current_user']);
}
Here is my constructor:
class User {
public $userid;
public $fname;
public $lname;
public $email;
public $username;
public $function;
public $d_joined;
public $is_internal;
public $is_active;
function __costruct() {
$arguments = func_get_args();
if (!empty($arguments)) {
foreach ($arguments[0] as $key => $property) {
if ($property_exists($this, $key)) {
$this -> {$key} = $property;
}
}
}
}
I have tried _construct($args) and _construct(array($args)) and nothing. Here is the output of the var_dump() calls
string 'cgotecrpu7soqvepoimjo2s116' (length=26)
array (size=1)
0 =>
array (size=20)
'userid' => string '1' (length=1)
0 => string '1' (length=1)
'fname' => string 'First Name' (length=4)
1 => string 'First Name' (length=4)
'lname' => string 'Last Name' (length=8)
2 => string 'Last Name' (length=8)
'email' => string 'email address' (length=21)
3 => string 'email address' (length=21)
'username' => string 'username' (length=9)
4 => string 'username' (length=9)
'password' => string 'hashed password:salt' (length=65)
5 => string 'hashed password:salt' (length=65)
'function' => string '4' (length=1)
6 => string '4' (length=1)
'd_joined' => string '2013-01-14' (length=10)
7 => string '2013-01-14' (length=10)
'is_internal' => string '1' (length=1)
8 => string '1' (length=1)
'is_active' => string '1' (length=1)
9 => string '1' (length=1)
object(User)[2]
public 'userid' => null
public 'fname' => null
public 'lname' => null
public 'email' => null
public 'username' => null
public 'function' => null
public 'd_joined' => null
public 'is_internal' => null
public 'is_active' => null
string 'O:4:"User":9{
s:6:"userid";N;
s:5:"fname";N;
s:5:"lname";N;
s:5:"email";N;
s:8:"username";N;
s:8:"function";N;
s:8:"d_joined";N;
s:11:"is_internal";N;
s:9:"is_active";N;}' (length=162)
So from what I can see, the initial array $users has the correct data. The User object is created in the variable $current_user . However, when I try to access an object property I get an error about accessing an object property from a non-object. It looks like the session variable is not an object, but the $current_user is, even though it has null properties. Can someone point me in the right direction? Thanks.
Your constructor looks wrong for the thing you want to do, I think you should do it this way :
/**
* Constructor set each public values with the ones passed from the passed array
* #param array $userDatas Array containing the user datas
*/
function __construct($userDatas) {
foreach($userDatas as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
}

Categories