I've got a ResponseGrid object and I want to encode it in Json. This object has got a $row variable inside, which will contain an array of Document objects.
I implemented JsonSerializable interface and jsonSerialize() method in both ResponseGrid and Document.
What I've got is this:
ResponseGrid.php:
require_once ROOT_DIR . "/business/models/Document.php";
class ResponseGrid implements JsonSerializable {
private $sEcho;
private $iTotalRecords;
private $iTotalDisplayRecords;
private $rows; // This will contain an Array of Documents
public function jsonSerialize() {
return [
'sEcho' => $this->sEcho,
'iTotalRecords' => $this->iTotalRecords,
'iTotalDisplayRecords' => $this->iTotalDisplayRecords,
'rows' => json_encode($this->rows), //This will cause troubles
];
}
}
Document.php:
class Document implements JsonSerializable {
private $id;
private $name;
private $description;
private $contentId;
public function jsonSerialize() {
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'contentId' => $this->contentId,
];
}
}
If I do an echo of a ResponseGrid object filled with two Documents in the $row array, I get this
echo json_encode($responseGrid);
{"sEcho":1,"iTotalRecords":27,"iTotalDisplayRecords":4,"rows":"{\"1\":{\"id\":1,\"name\":\"Greece\",\"description\":\"descGreece\",\"contentId\":2},\"2\":{\"id\":2,\"name\":\"Rome\",\"description\":\"descRome\",\"contentId\":3}}"}
Why is that? Am I doing it wrong? What are all those backslashes?
Related
i was playing around with spatie dto library and i found myself with a problem.
how do i cast an array to a subobject?
use Spatie\DataTransferObject\DataTransferObject;
class OtherDTOCollection extends DataTransferObject {
public OtherDto $collection;
}
class OtherDTO extends DataTransferObject {
public int $id;
public string $test;
}
class MyDTO extends DataTransferObject {
public OtherDTO $collection;
}
class MyDTO2 extends DataTransferObject {
public OtherDTOCollection $collection;
}
// WORKS
$MyDTO = new MyDTO(
collection: [
[
'id' => 1,
'test' => 'test'
],
[
'id' => 1,
'test' => 'test'
]
]
);
// DOESN'T WORK
$MyDTO2 = new MyDTO2(
collection: [
[
'id' => 1,
'test' => 'test'
],
[
'id' => 1,
'test' => 'test'
]
]
);
here's the relevant code, how can i make it work so that i have an array of objects?
thanks
You need to use the CastWith class form Spatie Attributes. .
use Spatie\DataTransferObject\Casters\ArrayCaster;
use Spatie\DataTransferObject\Attributes\CastWith;
This example will give you array of dto objects. /** #var \DTO\PrefixListItem[] */ comment has key role, type here full namespace, not on the top with use
namespace \DTO;
use Spatie\DataTransferObject\DataTransferObject;
class PrefixListResult extends DataTransferObject
{
/** #var \DTO\PrefixListItem[] */
public ?array $list = [];
public ?string $server_time = null;
public ?int $code = null;
public ?string $message = null;
public ?array $details = [];
}
namespace \DTO;
use Spatie\DataTransferObject\DataTransferObject;
class PrefixListItem extends DataTransferObject
{
public string $imsi_prefix;
public string $operator;
public int $min_sim_active_days;
public int $max_sim_active_days;
public bool $call_forwarding;
}
I need to serialize an object as its own property (it's type is array), I mean that the object has an array property books, and after transforming it I want to skip the books key, so the structure will be more flat [book1, book2] (not [books => [book1, book2]]. I have the following classes:
<?php
class Store
{
private ?BooksCollection $booksCollection = null;
public function __construct(?BooksCollection $booksCollection = null)
{
$this->booksCollection = $booksCollection;
}
public function getBooksCollection(): ?BooksCollection
{
return $this->booksCollection;
}
}
class BooksCollection
{
/** #var Book[] */
private array $books;
public function __construct(Book ...$books)
{
$this->books = $books;
}
public function getBooks(): array
{
return $this->books;
}
}
class Book
{
private string $title;
public function __construct(string $title)
{
$this->title = $title;
}
public function getTitle(): string
{
return $this->title;
}
}
and serialization config:
Store:
exclusion_policy: ALL
properties:
booksCollection:
type: BooksCollection
BooksCollection:
exclusion_policy: ALL
properties:
books:
type: array<int, Book>
Book:
exclusion_policy: ALL
properties:
title:
type: string
The test I want to pass:
<?php
use JMS\Serializer\ArrayTransformerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class StoreSerializeTest extends KernelTestCase
{
/** #var ArrayTransformerInterface */
private $serializer;
protected function setUp(): void
{
self::bootKernel();
$this->serializer = self::$kernel->getContainer()->get('jms_serializer');
}
public function testSerialization(): void
{
$store = new Store(new BooksCollection(new Book('Birdy'), new Book('Lotr')));
$serializedStore = $this->serializer->toArray($store);
$storeUnserialized = $this->serializer->fromArray($serializedStore, Store::class);
self::assertSame(
[
'books_collection' => [
['title' => 'Birdy'],
['title' => 'Lotr']
]
],
$serializedStore
);
self::assertEquals($store, $storeUnserialized);
}
}
As you can see below the test is failing. How can I get rid of one nesting 'books'?
The main idea I had, was to use EventSubscriberInterface and onPreSerialize event, but I really can't figure out how can I replace an object BooksCollection with an array made of its own property books. Is there anyone who already know how to do it?
Finally, I figured it out. I implemented SubscribingHandlerInterface
<?php
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonDeserializationVisitor;
use JMS\Serializer\JsonSerializationVisitor;
use Book;
use BooksCollection;
class BooksCollectionHandler implements SubscribingHandlerInterface
{
public static function getSubscribingMethods(): array
{
return [
[
'type' => BooksCollection::class,
'format' => 'json',
'method' => 'serialize',
'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
],
[
'type' => BooksCollection::class,
'format' => 'json',
'method' => 'deserialize',
'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
]
];
}
public function serialize(
JsonSerializationVisitor $visitor,
BooksCollection $booksCollection,
array $type,
Context $context
) {
return $visitor->visitArray($booksCollection->getBooks(), ['name' => 'array'], $context);
}
public function deserialize(
JsonDeserializationVisitor $visitor,
array $data,
array $type,
Context $context
): BooksCollection {
$collection = [];
foreach ($data as $book) {
$collection[] =
$visitor->getNavigator()->accept($book, ['name' => Book::class], $context);
}
return new BooksCollection(...$collection);
}
}
service config:
books_handler:
class: BooksCollectionHandler
tags:
- { name: jms_serializer.subscribing_handler }
i have private class with array :
class Whois{
private $whoisServers = array(
"ac"=> "whois.nic.ac",
"ae" => "whois.nic.ae",
"tech" => "whois.nic.tech",
"yu" => "whois.ripe.net");
}
now, possible i get array() for private $whoisServers from database?
Now make an object of a class and access to public function.
<?php
class Whois{
private $whoisServers = array(
"ac"=> "whois.nic.ac",
"ae" => "whois.nic.ae",
"tech" => "whois.nic.tech",
"yu" => "whois.ripe.net");
/*
!----------------------------------------------
! Getting Private Server List Using Public Function
! #getter
!----------------------------------------------
*/
public function getWhichServers()
{
return $this->whoisServers;
}
/*
https://github.com/arif98741
*/
}
$object = new Whois();
$serverlist = $object->getWhichServers();
In a BI project we have multiple reporter functionalities. So, we have defined some classes to implement this feature. This classes needs many attributes to chain and build complex queries to generate reports. Any other classes should set specific values for these attributes, to get reports from this class. Values of these attributes are Non-Dynamic. I don't use the database to store them.
Below codes are the current model i am using:
Report generator (Main class):
class Report
{
private $indicator;
private $ratio;
private $divider;
private $criteria;
private $plantation;
private $reporter;
public function reporter($reporter)
{
$this->reporter = (new Reporters())->get($reporter);
return $this;
}
public function plantation($plantationId)
{
$this->plantation = $plantationId;
return $this;
}
public function ratio($ratio)
{
$this->ratio = (new Ratios())->get($ratio);
return $this;
}
public function divider($divider)
{
$this->divider = (new Dividers())->get($divider);
return $this;
}
public function criteria($criteria)
{
$this->criteria = $criteria;
return $this;
}
public function get()
{
return $this->mocker();
}
}
Dividers Class:
class Dividers
{
public $dividers = [
'sum' => [
'name' => 'مجموع',
'alias' => 'sum',
],
'plantations' => [
'name' => 'مجموعه انتخابی',
'alias' => 'plantations',
'model' => Plantation::class
],
'operation_types' => [
'name' => 'نوع عملیات',
'alias' => 'operation_type',
'model' => OperationType::class
],
'planting_years' => [
'name' => 'سال زراعی',
'alias' => 'planting_years',
'model' => Planting_year::class
],
'crops' => [
'name' => 'انواع گیاهان',
'alias' => 'crops',
'model' => Crop::class
],
];
public function get($divider)
{
if(!array_key_exists($divider, $this->dividers)){
return false;
}
return $this->dividers[$divider];
}
}
Ratio Class:
class Ratios
{
public $ratios = [
'SUM' => 'انباشته',
'KILOGRAM' => 'کیلوگرم',
'HECTARE' => 'هکتار',
'RIALPERKILO' => 'ریال به کیلوگرم',
'MILIONRIALPERTON' => 'میلیون ریال بر تن',
];
public function get($ratio)
{
if(!array_key_exists($ratio, $this->ratios)){
return false;
}
return $this->ratios[$ratio];
}
}
So for using report generator i will use this method:
$report = (new Report())
->plantation(352)
->divider('sum')
->reporter('NetProfit', ['operation_type'=> 364])
->criteria([['criteriaType'=> 'human_resources', 'value'=> '256'],['criteriaType'=> 'human_resources', 'value'=> '326']])
->ratio('sum')
->indicator(324, 523, 632)
->get();
My question is: what is the best pattern to store this data objects to reduce human mistakes?
This is more of an opinion based answer, so I'll suggest what I do when I am using static values.
Declare class members as static and protected for variables.
Like in your question class Ratios { } is a static class
class Ratios{
protected static $ratios = [...];
public static function get($ratio)
{
if(!array_key_exists($ratio, self::$ratios)){
return false;
}
return self::$ratios[$ratio];
}
}
//Access the Ratios class with.
$val = Ratios::get($ratio);
This ensures that
the values won't change throughout the lifecycle of your request
Adds a layer of security.
Also maintains the source, i.e. no change will occur if you don't change the code.
Doesn't create a new Instance(new Ratios()) for getting static values and you have that memory edge.
Do the same with the class Dividers { }.
I don't know if this is the best practice, but i would make a separate directory called "constants" or "config" or something that seems intuitive to you, and add there files named like Class_Property.php that return the value of that property
For example, in you Ratios class:
class Ratios
{
public $ratios = require('config/Ratios_ratios.php');
public function get($ratio)
{
if(!array_key_exists($ratio, $this->ratios)){
return false;
}
return $this->ratios[$ratio];
}
}
And in config/Ratios_ratios.php:
<?php
return [
'SUM' => 'انباشته',
'KILOGRAM' => 'کیلوگرم',
'HECTARE' => 'هکتار',
'RIALPERKILO' => 'ریال به کیلوگرم',
'MILIONRIALPERTON' => 'میلیون ریال بر تن',
];
?>
Depending on how critical that data is, opt for require/require_once/include. This is done mainly to keep your class more skinny, separating the constants
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)