How do I map a JSON object to a PHP Class? - php

I'm building a REST API endpint that adds a company to a MySQL database. The client sends a POST request with an attached data package. The data package is a JSON object. Assume the JSON Company Object is formatted to exactly match the Company Class that the API uses.
How do I get the JSON Company Object data into the Company Class? It seems silly to instantiate a Company Object, json_decode() the JSON Object, then call dozens of set() methods.
It seems especially silly, since I'm planning on offering the same models in my client package to build the objects that get passed as JSON to my API, before being decoded, and mapped back into the same objects again.
Am I missing something? I'm constantly running up against things that seem redundant while building my API, but perhaps that's just what has to happen.

Why don't you handle is all in the constructor of the Company object so that you pass in the JSON object as the parameter and the constructor handles all of the assignments. That way you don't even need public set methods.
$companyData = $_POST['company'];
//or $companyData = json_decode($_POST['company']);
//or whatever depending on how you are posting
class Company {
private $companyName;
//etc...
function __construct(array $data) {
foreach($data as $key => $val) {
if(property_exists(__CLASS__,$key)) {
$this->$key = $val;
}
}
}
}

We built JsonMapper to map JSON objects onto our own model classes automatically.
It only relies on docblock type information for mapping, which most class properties have anyway.

Why don't you just make a method in the Company object that declares the variables for the object (you don't need to write a set method for each variable, just one that'll set all the variables).
//Why not write something like this in the class
function setFromJSON($json){
$jsonArray = json_decode($json, true);
foreach($jsonArray as $key=>$value){
$this->$key = $value;
}
}

this one might help you.
class ObjectMapper
{
protected string $modelClass='';
public function __construct(string $modelClass){
$this->modelClass = $modelClass;
}
protected function mapObject(array $values){
$object = new $this->modelClass();
foreach ($values as $key=>$value){
$mapperFunction = 'set'.$key;
$object->{$mapperFunction}($value);
}
return $object;
}
}

Don't match the JSON data you get exactly to the data object you want to use. There will be changes to the database and the objects, and you do not want these to affect the interface you created and vice versa.

I Had the same problem, so I wrote a packagist to solve it, here is the gift for you.
https://packagist.org/packages/stark641/stark-object
https://github.com/stark641/stark-object
you can generate PHP class file from json
./vendor/bin/stark-object-gen --path=/tmp --json='{"foo_bar":{"foo":1.3,"bar":[641,641]}}'
output
Class FooBar saved at: /tmp/FooBar.php
Class BaseClass saved at: /tmp/BaseClass.php
you can map json to php object
class FooBarClass extends StarkObject
{
/** #var string */
public $foo;
/** #var integer */
public $bar;
}
class DemoClass extends StarkObject
{
/** #var FooBarClass */
public $foobar;
/** #var FooBarClass[] */
public $foobars;
}
$json = '{"foobar":{"foo":"hello world","bar":64100},"foobars":[{"foo":"hello","bar":641},{"foo":"world","bar":664411}]}';
$demo = (new DemoClass())->fromJson($json);
var_export($demo);

I think this can help: https://github.com/ABGEO/json-to-popo
I tried it and it works fine!
An example:
We have these php classes
<?php
class Department
{
/**
* #var string
*/
private $title;
// Getters and Setters here...
}
<?php
class Position
{
/**
* #var string
*/
private $title;
/**
* #var Department
*/
private $department;
// Getters and Setters here...
}
<?php
class Person
{
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
private $lastName;
/**
* #var bool
*/
private $active;
/**
* #var Position
*/
private $position;
// Getters and Setters here...
}
Person class is represented by this json:
{
"firstName": "Temuri",
"lastName": "Takalandze",
"active": true,
"position": {
"title": "Developer",
"department": {
"title": "IT"
}
}
}
So, now you only need to do this:
// $jsonContent represents your json string
$resultObject = $composer->composeObject($jsonContent, Person::class);
var_dump($resultObject);
and this is what you get:
//class Person#2 (4) {
// private $firstName =>
// string(6) "Temuri"
// private $lastName =>
// string(10) "Takalandze"
// private $active =>
// bool(true)
// private $position =>
// class Position#4 (2) {
// private $title =>
// string(9) "Developer"
// private $department =>
// class Department#7 (1) {
// private $title =>
// string(2) "IT"
// }
// }
//}
I tried to implement the library with public class properties but it seems it doesn't work without declared setter and getters.

Related

Multiple dimensional map using objects as keys

I have a set of objects (MainObject) which are uniquely defined by two objects (SubObject1, SubObject2) and a string (theString). I with to retrieve a MainObject from the set by returning an existing object based on the two subobjects and string should it exist, else creating a new one, adding it to the set, and returning that object.
The following pseudo code demonstrates this in the make believe world where a standard array can use objects as keys.
class SubObject1{}
class SubObject2{}
class MainObject{
private $subObject1, $subObject2, $theString;
public function __construct(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
$this->subObject1=$subObject1;
$this->subObject2=$subObject2;
$this->theString=$theString;
}
}
class ObjectCollection
{
private $map=[];
public function getObject(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
if(isset($this->map[$subObject1][$subObject2][$theString])) {
$mainObject=$this->map[$subObject1][$subObject2][$theString];
}
else {
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$this->map[$subObject1][$subObject2][$theString]=$mainObject;
}
return $mainObject;
}
}
$objectCollection=new ObjectCollection();
$subObject1_1=new SubObject1();
$subObject1_2=new SubObject1();
$subObject2_1=new SubObject2();
$subObject2_1=new SubObject2();
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'hello'); //returns a new object
$o=$objectCollection->getObject($subObject1_2, $subObject2_1, 'hello'); //returns a new object
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'goodby'); //returns a new object
$o=$objectCollection->getObject($subObject1_1, $subObject2_1, 'hello'); //returns existing object
How should this be best implemented?
One possibility is something like the following untested code, however, it is a little verbose and am interested if there is a cleaner solution.
public function getObject(SubObject1 $subObject1, SubObject2 $subObject2, string $theString):MainObject {
if(isset($this->map[$theString])) {
if($this->map[$theString]->contains($subObject1)) {
$subObject1Storage=$this->map[$theString][$subObject1];
if($subObject1Storage->contains($subObject2)) {
$mainObject=$subObject1Storage[$subObject2];
}
else {
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
}
else {
$subObject1Storage = new \SplObjectStorage();
$this->map[$theString][$subObject1]=$subObject1Storage;
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
}
else {
$this->map[$theString] = new \SplObjectStorage();
$subObject1Storage = new \SplObjectStorage();
$this->map[$theString][$subObject1]=$subObject1Storage;
$mainObject=new MainObject($subObject1, $subObject2, $theString);
$subObject1Storage[$subObject2]=$mainObject;
}
return $mainObject;
}
The logic I had in mind was as follows:
A factory(or abstract factory in case of too many objects) will take care of creating the object itself.
A container will map unique identifiers with objects created by the factory.
And can retrieve objects based on those identifiers.
That's the easy part, the custom part should be even easier, you can add your own methods to do whatever magic you need with aliases and such.
namespace Example;
/**
* Class ObjectFactory
*
* #package Example
*/
class ObjectFactory {
/**
* This is obviosuly not ideal but it can work
* with a limited amount of objects. Otherwise use an
* abstract factory and let each instance take care of a few
* related objects
*
* #param string $objectAlias
*
* #throws \Exception
*/
public function make(string $objectAlias) {
switch($objectAlias) {
case 'object_unique_id_1':
try{
$instance = new $objectAlias;
}catch (\Exception $exception) {
// log or whatever and rethrow
throw new \Exception("Invalid class? maybe, I dunno");
}
// return $instance
// etc
}
}
}
You can also use Reflection here to recursively get the arguments for the object and dump new instances of the object in the current object based on the arguments in the construct esentially make your own little DI container.
But if you want to keep your sanity use something like Pimple.
Container:
<?php
namespace Example;
/**
* Class Container
*
* #package Example
*/
class Container {
/**
* #var array
*/
private $map = [];
/**
* #param $objectAlias
* #param $objectInstance
*
* #throws \Exception
*/
public function set($objectAlias, $objectInstance) {
// You can use a try catch here, I chose not to
if(isset($this->map[$objectAlias])) {
throw new \Exception("Already exists");
}
$this->map[$objectAlias] = $objectInstance;
}
/**
* #param $objectAlias
*
* #return bool|mixed
*/
public function get($objectAlias) {
if(isset($this->map[$objectAlias])) {
return $this->map[$objectAlias];
}
return false;
}
}
Specific container which will hold your own methods
<?php
namespace Example;
/**
* Class ContainerHashMapThingy
*
* #package Example
*/
class ContainerHashMapThingy extends Container {
// Your methods go here
}
And an example object:
<?php
namespace Example;
/**
* Class ExampleObject1
*
* #package Example
*/
class ExampleObject1 {
/**
* #return string
*/
public function alias() {
// This is just for example sake
// You can just as well have a config, another class to map them or not map them at all
return 'example_object_1';
}
}
And an actual example
<?php
$factory = new \Example\ObjectFactory();
$container = new \Example\Container();
$objectOne = $factory->make('example_object_1');
$container->set('first_object', $objectOne);
The idea here is to give you a clean slate for a container + factory.
If you extend the container you can implement your own methods, remove stuff from the map array, even rewrite the set method to suit your own needs.
While this is not a complete answer it's very hard to give one since, as I said, your needs may vary.
I do hope this gets you on the right track.

Doctrine embeddable

I am trying to implement doctrine2 embeddable feature for value objects.
This is simple snippet, slightly modified from doctrine official documentation:
/** #Entity */
class User
{
/** #Embedded(class = "Address") */
protected $address;
/** #Column(type = "string") */
protected $street;
}
/** #Embeddable */
class Address
{
/** #Column(type = "string") */
protected $street;
/** #Column(type = "string") */
protected $postalCode;
/** #Column(type = "string") */
protected $city;
/** #Column(type = "string") */
protected $country;
}
When I try to access property from embedded object, I am getting "cannot access protected property" error - which is expected.
Embeddable objects should be read only by their base entity class, and not modified since they are immutable (following domain driven design guidelines).
What I don't understand is how can I read their properties since they are private/protected ? Is there any PHP magic involved here ? I was trying to use different getters like:
getStreet(Address $address){
$this->street = $address->street;
}
but always get error, before calling persist and flush methods on entity manager instance.
Thanks.
Use getters on the embedded class.
class Address
{
...
public function getStreet(){
return $this->street;
}
...
}
Then, your method changes
getStreet(Address $address){
$this->street = $address->getStreet();
}
If you want to retrieve the street value starting from a user, you should create a getter method in the User class that internally has access to the address and does:
class User
{
public getAddressStreet() {
$this->address->getStreet();
}
}
or something similar.

Getting the child-type of an object from a method inherited from the father

my problem is getting the right type of object from a method, which is returning a "mixed" type due to inhreitance.
I've got a generic list class
class List
{
/**
* #var Item[]
*/
protected $items;
public function __construct()
{
$this->items = array();
}
/**
* #return Item[]
*/
public function getAll()
{
return $this->items;
}
/**
* #return Item
*/
public function getOne($index)
{
if (isset($this->items[$index])) {
return $this->items[$index];
}
return null;
}
}
containing element of type Item, which is a generic class either
class Item
{
/**
* #var string
*/
protected $name;
public function __construct($name)
{
$this->name = $name;
}
}
Such generic classes are extended by N different lists. Just an example
class UserList extends List
{
/* user-specific implementation */
}
class User extends Item
{
/* user-specific implementation */
}
In the client code
$user_list = new UserList();
foreach ($user_list->getAll() as $user) {
echo $user->getEmailAddr();
}
Inside the foreach I don't have code completion, because my getAll method (inherited from the father) is returning Item[], or mixed[], not a User[]. Same problem with getOne method.
I wouldn't like to have to override such methods.
Is there a more clever and elegant solution?
Thank you
I don't think there's any way for the IDE to infer the type automatically. Use a phpdoc type annotation:
foreach ($user_list->getAll() as $user) {
/** #var User $user */
echo $user->getEmailAddr();
}
See the related question PHPDoc type hinting for array of objects?

How to unit test a php class constructor that returns no value

I'm a little bit confused about how to unit test a constructor, particularly since it returns no value.
Let's assume I have this class:
class MyClass {
/** #var array */
public $registered_items;
/**
* Register all of the items upon instantiation
*
* #param array $myArrayOfItems an array of objects
*/
public function __construct($myArrayOfItems) {
foreach($myArrayOfItems as $myItem) {
$this->registerItem($myItem);
}
}
/**
* Register a single item
*
* #param object $item a single item with properties 'slug' and 'data'
*/
private function registerItem($item) {
$this->registered_items[$item->slug] = $item->data;
}
}
Obviously this is a bit contrived and incredibly simple, but it's for the sake of the question. =)
So yeah, how would I go about writing a unit test for the constructor here?
Bonus question: am I right in thinking that no unit test for registerItem() would be needed in a case such as this?
EDIT
How about if I re-factored to remove the logic from the constructor. How would I test registerItem() in this case?
class MyClass {
/** #var array */
public $registered_items;
public function __construct() {
// Nothing at the moment
}
/**
* Register all of the items
*
* #param array $myArrayOfItems an array of objects
*/
public function registerItem($myArrayOfItems) {
foreach($myArrayOfItems as $item) {
$this->registered_items[$item->slug] = $item->data;
}
}
}
Add a method to look up a registered item.
class MyClass {
...
/**
* Returns a registered item
*
* #param string $slug unique slug of the item to retrieve
* #return object the matching registered item or null
*/
public function getRegisteredItem($slug) {
return isset($this->registered_items[$slug]) ? $this->registered_items[$slug] : null;
}
}
Then check that each item passed to the constructor in the test has been registered.
class MyClassTest {
public function testConstructorRegistersItems() {
$item = new Item('slug');
$fixture = new MyClass(array($item));
assertThat($fixture->getRegisteredItem('slug'), identicalTo($item));
}
}
Note: I'm using the Hamcrest assertions, but PHPUnit should have an equivalent.
For First Code
public function testConstruct{
$arrayOfItems = your array;
$myClass = new MyClass($arrayOfItems);
foreach($arrayOfItems as $myItem) {
$expected_registered_items[$item->slug] = $item->data;
}
$this->assertEquals($expected_registered_items, $myClass->registered_items);
}

Symfony - Deserialize json to an array of entities

I have a json object that I received by making a get API call. I make this call to receive a list of objects. It's a list of post... So I have an array of Post Objects.
Here the output :
{
"total":2,
"data":[
{
"id":2,
"user":{
"id":1,
"username":"sandro.tchikovani"
},
"description":"cool",
"nb_comments":0,
"nb_likes":0,
"date_creation":"2014-04-13T20:07:34-0700"
},
{
"id":1,
"user":{
"id":1,
"username":"sandro.tchikovani",
},
"description":"Premier pooooste #lol",
"nb_comments":0,
"nb_likes":0,
"date_creation":"2014-04-13T15:15:35-0700"
}
]
}
I would like to deserialize the data part...
The problem is that the Serializer in Symfony gives me an error ...
The error that I have :
Class array<Moodress\Bundle\PosteBundle\Entity\Poste> does not exist
How I do deserialize :
$lastPosts = $serializer->deserialize($data['data'], 'array<Moodress\Bundle\PosteBundle\Entity\Poste>', 'json');
How can I deserialze the data array... To have an array of Postes. I want to give to my view .twig an array Poste... I did precise the type when I deserialize... So I can't find what is the problem...
Thanks.
I think the best solution here is to create new PosteResponse class, like this one:
namespace Moodress\Bundle\PosteBundle\Response;
use JMS\Serializer\Annotation\Type;
class PosteResponse
{
/**
* #Type("integer")
*/
private $total;
/**
* #Type("array<Moodress\Bundle\PosteBundle\Entity\Poste>")
*/
private $data;
//getters here
}
and deserialize your response to that class:
$response = $serializer->deserialize(
$json,
'Moodress\Bundle\PosteBundle\Response\PosteResponse',
'json'
);
$posts = $response->getData();
That WILL do the trick, and it doesn't require you to decode and encode your json manually which is riddiculous in my opinion.
Since Symfony Serializer Component 2.8 to deserialize array of objects:
$persons = $serializer->deserialize($data, 'Acme\Person[]', 'json');
https://symfony.com/doc/master/components/serializer.html#handling-arrays
A less than ideal solution that I found was to first decode and then encode the json data again at the node that represents the data array. For example in your case:
$json = json_decode($json);
$json = json_encode($json->data);
$serializer->deserialize($json, 'array<Moodress\Bundle\PosteBundle\Entity\Poste>', 'json');
There must be a better solution than this but this seems more elegant than the above solution of de-serialising json.
The error is pretty clear. Your string does not match any existant class.
The example in official documentation says:
$person = $serializer->deserialize($data,'Acme\Person','xml');
In your case it should be more like:
$person = $serializer->deserialize($data['data'],'Moodress\Bundle\PosteBundle\Entity\Poste','json');
Update:
Ok then.
First, your json file does not seem to be valid (use http://jsonlint.com/ to test it). Be careful of that.
Second, you will have to fetch your json as an array with
$data = json_decode($yourJsonFile, true);
and then you can access to each 'data' array with
foreach($data['data'] as $result)
{
/* Here you can hydrate your object manually like:
$person = new Person();
$person->setId($user['id']);
$person->setDescription($user['description']);
Or you can use a denormalizer. */
}
I would make something like this
class PostsModel
{
/**
* #var int
*/
private $total;
/**
* #var PostModel[]
*/
private $data;
}
class PostModel
{
/**
* #var int
*/
private $id;
/**
* #var UserModel
*/
private $user;
/**
* #var string
*/
private $description;
/**
* #var int
*/
private $nb_comments;
/**
* #var int
*/
private $nb_likes;
/**
* #var \DateTime
*/
private $date_creation;
}
class UserModel
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $username;
}
And in controller
$posts = $this->serializer->deserialize($data, PostsModel::class, 'json');
And this will return $postsModel with $data property which will have your array of entities
In case someone will be searching how to decode an array of objects using Symfony Serializer:
use Moodress\Bundle\PosteBundle\Entity\Poste;
// your json data
$data = '{
"total":2,
"data":[
{...},
{...}
]
}';
$lastPosts = $serializer->deserialize(
$data['data'],
'Poste[]', // or Poste::class.'[]',
'json'
);
Notice that you need to add [] after your class name, in this way Symfony will recognize your json data as an array of objects.

Categories