Symfony 3 app customizing JSON output - php

In my Symfony 3 web app I'm serializing some DB rows into Json as follows:
$doc = $this->get ( 'doctrine' );
$repo = $doc->getRepository ( 'AppBundle:Customer' );
$result = $repo->createQueryBuilder ( 'c' )->setMaxResults(25)->getQuery ()->getResult ();
$encoder = new JsonEncoder ();
$normalizer = new GetSetMethodNormalizer ();
$serializer = new Serializer ( array (
new \AppBundle\DateTimeNormalizer(), $normalizer
), array (
$encoder
) );
$json = $serializer->serialize ( $result, 'json' );
This outputs the desired data, e.g:
{companyname:"Microsoft"}
In order to (at least initially) maintain compatibility with a legacy system, I'd like all the Json names to be in uppercase, e.g.
{COMPANYNAME:"Microsoft"}
Is the best way to tackle this by approaching from:
The Encoder
The Normalizer(s)
The Serializer
Some other way?
Please briefly describe the suggested approach

You can implement your custom NameConverter a class that implements the NameConverterInterface and pass as second argument to the GetSetMethodNormalizer. As Example:
<?php
namespace AppBundle;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
class ToUppercaseNameConverter implements NameConverterInterface
{
/**
* Converts a property name to its normalized value.
*
* #param string $propertyName
*
* #return string
*/
public function normalize($propertyName)
{
return strtoupper($propertyName);
}
/**
* Converts a property name to its denormalized value.
*
* #param string $propertyName
*
* #return string
*/
public function denormalize($propertyName)
{
}
}
?>
and use it as follow:
$uppercaseConverter = new ToUppercaseNameConverter();
$normalizer = new GetSetMethodNormalizer (null, $uppercaseConverter);
You can take a look at the doc Converting Property Names when Serializing and Deserializing
Hope this help

Related

How to update a field annotated as ReferenceMany by using setNewObj in findAndUpdate of QueryBuilder

I have this class (a semplified version of my real class...):
/**
* Description of A
*
* #ODM\Document(collection="A")
*/
class A {
/** #ODM\Id(name="_id") */
private $id;
/**
* #ODM\Field(type="collection")
*/
private $names;
/**
* #ODM\ReferenceMany(targetDocument="B", storeAs="id", cascade={"persist"})
*/
private $hasManyB;
}
I want to update the A object identified by $oid with hasManyB property set to an array of two objects of class B identified by $oid1 and $oid2:
$oid = new \MongoDB\BSON\ObjectId('5e8af06ec8a067559836e856');
$oid1 = new \MongoDB\BSON\ObjectId('5e8af06ec8a067559836e855');
$oid2 = new \MongoDB\BSON\ObjectId('5e8af066c8a067559836e854');
$newObj = [
'id' => $oid,
'names' => ['alfa','beta','gamma'],
'hasManyB' => [$oid1, $oid2]
];
$query_builder = $this->_dm->createQueryBuilder(A::class);
$object = $query_builder->findAndUpdate()
->field('id')->equals($oid)
->setNewObj($newObj)
->returnNew()
->getQuery()
->execute();
The method prepareQueryOrNewObj() of DocumentPersister calls the prepareQueryElement() for each element in $newObj and the array of two ObjectId is passed to getDatabaseIdentifierValue() that returns a new ObjectId and this is the resulting object inside the mongo db after saving:
{
"_id" : ObjectId("5e8af06ec8a067559836e856"),
"hasManyB" : ObjectId("5c3f21b67d97cb3a08e7411f"),
"names": [
"alfa",
"beta",
"gamma"
]
}
To me this makes no sense. Obviously I'm doing something wrong. What is the correct way to update a ReferenceMany property using the setNewObj in findAndUpdate of QueryBuilder? Please note that I'm not interested in alternative methods based on manipulating the php object and saving it using persist() of DocumentManager (I know it works). In mongo shell the correct/equivalent command is:
db.A.findOneAndUpdate({
_id:ObjectId("5e8af06ec8a067559836e856")
}, {
$set:{
names:["alfa","beta","gamma"],
hasManyB:[
ObjectId("5e8af06ec8a067559836e855"),
ObjectId('5e8af066c8a067559836e854')
]
}
})

Laravel cursorPaginate without visible ID field. (InvalidArgumentException Illegal operator and value combination.)

How can I implement Laravel's cursor paginator without returning the id column in the collection?
The following query:
'users' => User::cursorPaginate(15)
returns the individual users like this:
{
"id": 1,
"uuid": "376bec76-9095-4510-a5ba-fea0f234c6cf",
"username": "alexanderhorner",
"password": "$2y$12$qMITOdMr2XdAq3EMKwc/WeB/db9IaQdkZ5egqY7CX5WpUwwHLKOLK"
}
Now, lets say I have an API. I want api/v1/user to return the same paginated results using cursorPaginate(), but I don't want to return the id column.
'users' => User::select('uuid', 'username')->cursorPaginate(15)
would return an error though, since cursorPaginate() needs the ID column or something similar:
InvalidArgumentException
Illegal operator and value combination.
What's the best way to fix this? Filter the collection returned by cursorPaginate()?
You can also check the cursorPaginate implementation (and signature) inside Builder.php.
There is a parameter columns. Check it out:
/**
* Get a paginator only supporting simple next and previous links.
*
* This is more efficient on larger data-sets, etc.
*
* #param int|null $perPage
* #param array $columns
* #param string $cursorName
* #param \Illuminate\Pagination\Cursor|string|null $cursor
* #return \Illuminate\Contracts\Pagination\CursorPaginator
*/
public function cursorPaginate($perPage = 15, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
{
return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor);
}
You can map the Users Collection of Models before return as response.
By doing the following you map the properties you want in a new variable called $users_to_be_returned and you return this in a property in your reponse.
You can do whatever with your structure of response with arrays, stdClass or even better DTO's.
$users = User::cursorPaginate(10);
$users_dtos = collect($users->items())->map(function ($user) {
$user_dto = new UserDTO(); // or even an stdClass object
$user_dto->username = $user->username;
$user_dto->password = $user->password;
return $user_dto;
})->toArray();
$response = new UserResponseDTO();
$response->users = $users_dtos;
$response->pagination_info = new PaginationInfoDTO();
$response->pagination_info->per_page = $users->perPage();
$response->pagination_info->path = $users->path();
$response->pagination_info->next_cursor = $users->nextCursor()->parameter('users.id');
$response->pagination_info->next_page_url = $users->nextPageUrl() ? $users->nextPageUrl() : null;
$response->pagination_info->previous_cursor = $users->previousCursor() ? $users->previousCursor()->parameter('users.id') : null;
$response->pagination_info->prev_page_url = $users->previousPageUrl() ? $users->previousPageUrl() : null;
return response()->json($response);

Unit test: using the proper terminology for mocking/stubbing

After fundamental changes on my project system architecture, I find myself in a situation where I would need to create "fake" implementation in order to test some functionality that used to be public like the following:
/**
* Display the template linked to the page.
*
* #param $newSmarty Smarty object to use to display the template.
*
* #param $parameters associative Array containing the values to pass to the template.
* The key is the name of the variable in the template and the value is the value of the variable.
*
* #param $account child class in the AccountManager hierarchy
*
* #param $partialview String name of the partial view we are working on
*/
protected function displayPageTemplateSmarty(Smarty &$newSmarty, array $parameters = array(), AccountManager $account = NULL, string $partialview = "")
{
$this->smarty = $newSmarty;
if (is_file(
realpath(dirname(__FILE__)) . "/../../" .
Session::getInstance()->getCurrentDomain() . "/view/" . (
!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
)
)) {
$this->smarty->assign(
'activeLanguage',
Session::getInstance()->getActiveLanguage()
);
$this->smarty->assign('domain', Session::getInstance()->getCurrentDomain());
$this->smarty->assign(
'languages',
Languagecontroller::$supportedLanguages
);
$this->smarty->assign(
'title',
Languagecontroller::getFieldTranslation('PAGE_TITLE', '')
);
$this->smarty->assign_by_ref('PageController', $this);
$htmlTagBuilder = HTMLTagBuilder::getInstance();
$languageController = LanguageController::getInstance();
$this->smarty->assign_by_ref('htmlTagBuilder', $htmlTagBuilder);
$this->smarty->assign_by_ref('languageController', $languageController);
if (!is_null($account)) {
$this->smarty->assign_by_ref('userAccount', $account);
}
if (!is_null($this->menuGenerator)) {
$this->smarty->assign_by_ref('menuGenerator', $this->menuGenerator);
}
foreach ($parameters as $key => $value) {
$this->smarty->assign($key, $value);
}
$this->smarty->display((!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
));
}
}
In this case, the PageController class used to be called directly in controllers, but is now an abstract class extended by the controllers and my unit tests can no longer access the method.
I also have methods like this one in my new session wrapper class that can only be used in very specific context and for which I really need to create fake page implementation to test them.
/**
* Add or update an entry to the page session array.
*
* Note: can only be updated by the PageController.
*
* #param $key String Key in the session array.
* Will not be added if the key is not a string.
*
* #param $value The value to be added to the session array.
*
* #return Boolean
*/
public function updatePageSession(string $key, $value)
{
$trace = debug_backtrace();
$updated = false;
if (isset($trace[1]) and
isset($trace[1]['class']) and
$trace[1]['class'] === 'PageController'
) {
$this->pageSession[$key] = $value;
$updated = true;
}
return $updated;
}
Even though I read a few article, it is still quite unclear in my mind if those fake classes should be considered as "stub" or a "mock" (or even "fake", "dummy" and so on).
I really need to use the proper terminology since my boss is expecting me (in a close future) to delegate most of my workload with oversea developers.
How would you call those fake class implementation created solely for testing purpose in order to be self-explanatory?
Gerard Meszaros explains the terminology of dummies, stubs, spies, mocks, and fakes here.
You can find examples from the PHP world here.

PHP memory references

I am wondering this question for a long time, how does PHP handle references are they a good idea to use and I can't explain better than using an example, lets look at the following class and then # the comment of the setResult method.
Lets imagine we are using a model view controller framework and we are building a basic AjaxController, we only got 1 action method (getUsers) so far. Read the comments, and I hope my question is clear, how does PHP handle these kind of situations and is it true what I wrote about the x times in the memory # the setResult docblock.
class AjaxController{
private $json = array(
'result' => array(),
'errors' => array(),
'debug' => array()
);
/**
* Adds an error, always displayed to users if any errors.
*
* #param type $description
*/
private function addError($description){
$this->json['errors'][] = $description;
}
/**
* Adds an debug message, these are displayed only with DEBUG_MODE.
*
* #param type $description
*/
private function addDebug($description){
$this->json['debug'][] = $description;
}
/**
* QUESTION: How does this go in memory? Cause if I use no references,
* the array would be 3 times in the memory, if the array is big (5000+)
* its pretty much a waste of resources.
*
* 1st time in memory # model result.
* 2th time in memory # setResult ($resultSet variable)
* 3th time in memory # $this->json
*
* #param array $resultSet
*/
private function setResult($resultSet){
$this->json['result'] = $resultSet;
}
/**
* Gets all the users
*/
public function _getUsers(){
$users = new Users();
$this->setResult($users->getUsers());
}
public function __construct(){
if(!DEBUG_MODE && count($this->json['debug']) > 0){
unset($this->json['debug']);
}
if(count($this->json['errors']) > 0){
unset($this->json['errors']);
}
echo json_encode($this->json);
}
}
Another simple example: What would be better to use technique A:
function example(){
$latestRequest = $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$_SESSION['abc']['test']['abc'] = 'test';
}
}
Or technique B:
function example(){
$latestRequest =& $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$latestRequest = 'test';
}
}
Thanks for reading and advise :)
In short: don't use references.
PHP copies on write. Consider:
$foo = "a large string";
$bar = $foo; // no copy
$zed = $foo; // no copy
$bar .= 'test'; // $foo is duplicated at this point.
// $zed and $foo still point to the same string
You should only use references when you need the functionality that they provide. i.e., You need to modify the original array or scalar via a reference to it.

Convert associative array to XML in PHP

I was wondering if a function capable of converting an associative array to an XML document exists in PHP (or some widely available PHP library).
I've searched quite a lot and could only find functions that do not output valid XML. I believe that the array I'm testing them on is correctly constructed, since it can be correctly used to generate a JSON document using json_encode. However, it is rather large and it is nested on four levels, which might explain why the functions I've tried so far fail.
Ultimately, I will write the code to generate the XML myself but surely there must be a faster way of doing this.
I realize I am a Johnny-Come-Lately here, but I was working with the VERY same problem -- and the tutorials I found out there would almost (but not quite upon unit testing) cover it.
After much frustration and research, here is what I cam up with
XML To Assoc. Array:
From http://www.php.net/manual/en/simplexml.examples-basic.php
json_decode( json_encode( simplexml_load_string( $string ) ), TRUE );
Assoc. Array to XML
notes:
XML attributes are not handled
Will also handle nested arrays with numeric indices (which are not valid XML!)
From http://www.devexp.eu/2009/04/11/php-domdocument-convert-array-to-xml/
/// Converts an array to XML
/// - http://www.devexp.eu/2009/04/11/php-domdocument-convert-array-to-xml/
/// #param <array> $array The associative array you want to convert; nested numeric indices are OK!
function getXml( array $array ) {
$array2XmlConverter = new XmlDomConstructor('1.0', 'utf-8');
$array2XmlConverter->xmlStandalone = TRUE;
$array2XmlConverter->formatOutput = TRUE;
try {
$array2XmlConverter->fromMixed( $array );
$array2XmlConverter->normalizeDocument ();
$xml = $array2XmlConverter->saveXML();
// echo "\n\n-----vvv start returned xml vvv-----\n";
// print_r( $xml );
// echo "\n------^^^ end returned xml ^^^----\n"
return $xml;
}
catch( Exception $ex ) {
// echo "\n\n-----vvv Rut-roh Raggy! vvv-----\n";
// print_r( $ex->getCode() ); echo "\n";
// print_r( $->getMessage() );
// var_dump( $ex );
// echo "\n------^^^ end Rut-roh Raggy! ^^^----\n"
return $ex;
}
}
... and here is the class to use for the $array2XmlConverter object:
/**
* Extends the DOMDocument to implement personal (utility) methods.
* - From: http://www.devexp.eu/2009/04/11/php-domdocument-convert-array-to-xml/
* - `parent::` See http://www.php.net/manual/en/class.domdocument.php
*
* #throws DOMException http://www.php.net/manual/en/class.domexception.php
*
* #author Toni Van de Voorde
*/
class XmlDomConstructor extends DOMDocument {
/**
* Constructs elements and texts from an array or string.
* The array can contain an element's name in the index part
* and an element's text in the value part.
*
* It can also creates an xml with the same element tagName on the same
* level.
*
* ex:
\verbatim
<nodes>
<node>text</node>
<node>
<field>hello</field>
<field>world</field>
</node>
</nodes>
\verbatim
*
*
* Array should then look like:
\verbatim
array(
"nodes" => array(
"node" => array(
0 => "text",
1 => array(
"field" => array (
0 => "hello",
1 => "world",
),
),
),
),
);
\endverbatim
*
* #param mixed $mixed An array or string.
*
* #param DOMElement[optional] $domElement Then element
* from where the array will be construct to.
*
*/
public function fromMixed($mixed, DOMElement $domElement = null) {
$domElement = is_null($domElement) ? $this : $domElement;
if (is_array($mixed)) {
foreach( $mixed as $index => $mixedElement ) {
if ( is_int($index) ) {
if ( $index == 0 ) {
$node = $domElement;
}
else {
$node = $this->createElement($domElement->tagName);
$domElement->parentNode->appendChild($node);
}
}
else {
$node = $this->createElement($index);
$domElement->appendChild($node);
}
$this->fromMixed($mixedElement, $node);
}
}
else {
$domElement->appendChild($this->createTextNode($mixed));
}
}
} // end of class
No. At least there is no such in-built function. It's not a probrem to write it at all.
surely there must be a faster way of doing this
How do you represent attribute in array? I can assume keys are tags and values are this tags content.
Basic PHP Array -> JSON works just fine, cause those structure is... well... almost the same.
Call
// $data = array(...);
$dataTransformator = new DataTransformator();
$domDocument = $dataTransformator->data2domDocument($data);
$xml = $domDocument->saveXML();
DataTransformator
class DataTransformator {
/**
* Converts the $data to a \DOMDocument.
* #param array $data
* #param string $rootElementName
* #param string $defaultElementName
* #see MyNamespace\Dom\DataTransformator#data2domNode(...)
* #return Ambigous <DOMDocument>
*/
public function data2domDocument(array $data, $rootElementName = 'data', $defaultElementName = 'item') {
return $this->data2domNode($data, $rootElementName, null, $defaultElementName);
}
/**
* Converts the $data to a \DOMNode.
* If the $elementContent is a string,
* a DOMNode with a nested shallow DOMElement
* will be (created if the argument $node is null and) returned.
* If the $elementContent is an array,
* the function will applied on every its element recursively and
* a DOMNode with a nested DOMElements
* will be (created if the argument $node is null and) returned.
* The end result is always a DOMDocument object.
* The casue is, that a \DOMElement object
* "is read only. It may be appended to a document,
* but additional nodes may not be appended to this node
* until the node is associated with a document."
* See {#link http://php.net/manual/en/domelement.construct.php here}).
*
* #param Ambigous <string, mixed> $elementName Used as element tagname. If it's not a string $defaultElementName is used instead.
* #param Ambigous <string, array> $elementContent
* #param Ambigous <\DOMDocument, NULL, \DOMElement> $parentNode The parent node is
* either a \DOMDocument (by the method calls from outside of the method)
* or a \DOMElement or NULL (by the calls from inside).
* Once again: For the calls from outside of the method the argument MUST be either a \DOMDocument object or NULL.
* #param string $defaultElementName If the key of the array element is a string, it determines the DOM element name / tagname.
* For numeric indexes the $defaultElementName is used.
* #return \DOMDocument
*/
protected function data2domNode($elementContent, $elementName, \DOMNode $parentNode = null, $defaultElementName = 'item') {
$parentNode = is_null($parentNode) ? new \DOMDocument('1.0', 'utf-8') : $parentNode;
$name = is_string($elementName) ? $elementName : $defaultElementName;
if (!is_array($elementContent)) {
$content = htmlspecialchars($elementContent);
$element = new \DOMElement($name, $content);
$parentNode->appendChild($element);
} else {
$element = new \DOMElement($name);
$parentNode->appendChild($element);
foreach ($elementContent as $key => $value) {
$elementChild = $this->data2domNode($value, $key, $element);
$parentNode->appendChild($elementChild);
}
}
return $parentNode;
}
}
PHP's DOMDocument objects are probably what you are looking for. Here is a link to an example use of this class to convert a multi-dimensional array into an xml file - http://www.php.net/manual/en/book.dom.php#78941
function combArrToXML($arrC=array(), $root="root", $element="element"){
$doc = new DOMDocument();
$doc->formatOutput = true;
$r = $doc->createElement( $root );
$doc->appendChild( $r );
$b = $doc->createElement( $element );
foreach( $arrC as $key => $val)
{
$$key = $doc->createElement( $key );
$$key->appendChild(
$doc->createTextNode( $val )
);
$b->appendChild( $$key );
$r->appendChild( $b );
}
return $doc->saveXML();
}
Example:
$b=array("testa"=>"testb", "testc"=>"testd");
combArrToXML($b, "root", "element");
Output:
<?xml version="1.0"?>
<root>
<element>
<testa>testb</testa>
<testc>testd</testc>
</element>
</root>
surely there must be a faster way of doing this
If you've got PEAR installed, there is. Take a look at XML_Seralizer. It's beta, so you'll have to use
pear install XML_Serializer-beta
to install
I needed a solution which is able to convert arrays with non-associative subarrays and content which needs to be escaped with CDATA (<>&). Since I could not find any appropriate solution, I implemented my own based on SimpleXML which should be quite fast.
https://github.com/traeger/SimplestXML (this solution supports an (Associative) Array => XML and XML => (Associative) Array conversion without attribute support). I hope this helps someone.

Categories