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

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"

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

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.

Drilling down into JSON with PHP

This is a really simple task, that has me absolutely stumped! I'm pulling back some JSON via CURL and PHP, and attempting to access the data from the below structure:
object(stdClass)[1]
public 'maxResults' => int 43
public 'resultList' =>
array (size=43)
0 =>
object(stdClass)[2]
public '#class' => string '' (length=64)
public 'id' => int
public 'version' => int 0
public 'dateCreated' => string '2014-02-11T18:37:55.835+0000' (length=28)
public 'dateModified' => null
public 'locationId' => int
public 'departmentId' => int
public 'ownerCompanyId' => int
public 'active' => boolean true
public 'userId' => int
public 'userName' => string '' (length=24)
public 'externalCode' => null
public 'employeeDetails' =>
object(stdClass)[3]
...
public 'chargeBandAllocationsIds' =>
array (size=7)
...
public 'personalRateChargeBandId' =>
object(stdClass)[13]
...
public 'employeeGroupIds' =>
array (size=0)
...
public 'isResource' => boolean false
(I've removed some of the values, for privacy reasons)
Now I'm trying to var_dump using var_dump(json_decode($result, false));, however when I try and get into the 'resultList' array using var_dump(json_decode($result['resultList'], false)); I get an illegal string offset error.
$result is the JSON string, your cannot do $result['resultList'] on a string. It only becomes a structure after you json_decode it. However, you're decoding it as object, not array, so this wouldn't work either way.
$data = json_decode($result);
var_dump($data->resultList);
var_dump($data->resultList[0]);
var_dump($data->resultList[0]->id);
foreach ($data->resultList as $employee) {
var_dump($employee->id);
}

Zend2 + Doctrine2 Authentication

I would like to have authentication using Doctrine 2 and ZF2. To get some help I used Zend 2 + doctrine 2 Auth Adapter, but every time I call the $authService->authenticate($adapter); I get an error that the class '' does not exist.
It seems that the config from my module.config.php won#t be used. It shows like this:
'authenticationadapter' => array(
'orm_default' => array(
'objectManager' => 'doctrine.entitymanager.orm_default',
'identityClass' => 'Profile\Entity\User',
'identityProperty' => 'username',
'credentialProperty' => 'password',
),
),
But if i made a var_dump on the authService all sets are null.
In my service where I want to do the login I call
$authAdapter = $this->getAuthAdapter();
$authAdapter->setIdentityValue($username);
$authAdapter->setCredentialValue($password);
A dump from the $authAdapter tells me that the IdentityValue and the CredentialValue
are set correctly.
The other things in the $authAdabter are:
protected 'options' =>
object(DoctrineModule\Options\Authentication)[281]
protected 'objectManager' =>
object(Doctrine\ORM\EntityManager)[248]
private 'config' =>
object(Doctrine\ORM\Configuration)[250]
...
private 'conn' =>
object(Doctrine\DBAL\Connection)[252]
...
private 'metadataFactory' =>
object(Doctrine\ORM\Mapping\ClassMetadataFactory)[266]
...
private 'repositories' =>
array (size=0)
...
private 'unitOfWork' =>
object(Doctrine\ORM\UnitOfWork)[267]
...
private 'eventManager' =>
object(Doctrine\Common\EventManager)[242]
...
private 'hydrators' =>
array (size=0)
...
private 'proxyFactory' =>
object(Doctrine\ORM\Proxy\ProxyFactory)[268]
...
private 'expressionBuilder' => null
private 'closed' => boolean false
private 'filterCollection' => null
protected 'objectRepository' => null
protected 'identityClass' => null
protected 'identityProperty' => null
protected 'credentialProperty' => null
protected 'credentialCallable' => null
protected 'classMetadata' => null
protected 'storage' => null
protected '__strictMode__' => boolean true
protected 'authenticationResultInfo' => null
The getAuthAdapter shows like this:
public function getAuthAdapter()
{
if (null === $this->authAdapter) {
$this->authAdapter = $this->getServiceManager()
->get('doctrine.authenticationadapter.ormdefault');
}
return $this->authAdapter;
}
So can some one tell me how to set the options correctly?
It looks like you're using the wrong configuration values. If you look at the DoctrineORM documentation, they use the following to set the configuration for the authentication adapter:
'doctrine' => array(
'authentication' => array(
'orm_default' => array(
'object_manager' => 'Doctrine\ORM\EntityManager',
'identity_class' => 'Application\Entity\User',
'identity_property' => 'email',
'credential_property' => 'password',
),
),
)
So, instead of using authenticationadapter use authentication in your module.config.php; instead of using objectManager use object_manager, etc.
In your Module.php, you will need to add this:
use Zend\Authentication\AuthenticationService;
...
public function getServiceConfig()
{
return array(
'factories' => array(
'Zend\Authentication\AuthenticationService' => function($serviceManager) {
return $serviceManager->get('doctrine.authenticationservice.orm_default');
}
)
);
}
And in your controller, you can access the Adapter using:
$authService = $this->getServiceLocator()->get('Zend\Authentication\AuthenticationService');
$adapter = $authService->getAdapter();
$adapter->setIdentityValue($data['login']);
$adapter->setCredentialValue($data['password']);
Please follow the documentation.
If you are using 'Zend\Authentication\AuthenticationService' in Module.php as suggested by Hohner, this will not work with the BjyAuthorize Module roles and ACL. BjyAuthorize will default to its own default configuration of the Authentication service which uses 'ZfcUser\Authentication\Storage\Db'. To get BjyAuthorize to use the Doctrine identity replace it with (or add ) 'zfcuser_auth_service' as follows:
'zfcuser_auth_service' => function ($serviceManager) {
return $authenticationService = $serviceManager->get('doctrine.authenticationservice.orm_default');
},
Then you also use it in the controller in a similar way:
$authService = $this->getServiceLocator()->get( 'zfcuser_auth_service' );

Categories