Converting XML to json using php - php

I am not much familiar with XML. can you guys please help me with convering this xml to json using php?. this is an API response.
<?xml version="1.0" encoding="utf-8"?>
<ecomexpress-objects version="1.0">
<object pk="1" model="awb">
<field type="BigIntegerField" name="awb_number">115979601</field>
<field type="CharField" name="orderid">001</field>
<field type="FloatField" name="actual_weight">0.8</field>
<field type="CharField" name="origin">DELHI-DLW</field>
<field type="CharField" name="destination">DELHI-DLW</field>
<field type="CharField" name="current_location_name">DELHI-DLW</field>
<field type="CharField" name="current_location_code">DLW</field>
<field type="CharField" name="customer">Freshbells - 471459</field>
<field type="CharField" name="consignee">Forward</field>
<field type="CharField" name="pickupdate"></field>
<field type="CharField" name="status">Shipment Uploaded</field>
<field type="CharField" name="tracking_status">Shipment Not Handed over</field>
<field type="CharField" name="reason_code"></field>
<field type="CharField" name="reason_code_description"></field>
<field type="CharField" name="reason_code_number">001</field>
<field type="CharField" name="receiver"></field>
<field type="CharField" name="lat">0.0000000</field>
<field type="CharField" name="long">0.0000000</field>
<field type="CharField" name="rev_pickup_signature" ></field>
<field type="CharField" name="rev_pickup_packed_image" ></field>
<field type="CharField" name="rev_pickup_open_image" ></field>
</ecomexpress-objects>
I have tried with so many solution found here. but that doesn't work.

This XML seems to serialize data model objects. It is fairly easy to read them into stdClass objects.
// bootstrap DOM+Xpath
$document = new DOMDocument();
$document->loadXML(getXMLString());
$xpath = new DOMXpath($document);
$objects = [];
// iterate object elements
foreach ($xpath->evaluate('/ecomexpress-objects/object') as $objectNode) {
$object = new stdClass();
// iterate field elements
foreach ($xpath->evaluate('field', $objectNode) as $fieldNode) {
// read attributes
$name = $fieldNode->getAttribute('name');
$type = $fieldNode->getAttribute('type');
// read field content and cast according to type
switch ($type) {
case 'BigIntegerField':
$object->{$name} = (int)$fieldNode->textContent;
break;
case 'FloatField':
$object->{$name} = (float)$fieldNode->textContent;
break;
default:
$object->{$name} = $fieldNode->textContent;
}
}
$objects[] = $object;
}
echo json_encode($objects, JSON_PRETTY_PRINT);
function getXMLString(): string {
return <<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<ecomexpress-objects version="1.0">
<object pk="1" model="awb">
<field type="BigIntegerField" name="awb_number">115979601</field>
<field type="CharField" name="orderid">001</field>
<field type="FloatField" name="actual_weight">0.8</field>
<field type="CharField" name="origin">DELHI-DLW</field>
<field type="CharField" name="destination">DELHI-DLW</field>
<field type="CharField" name="current_location_name">DELHI-DLW</field>
<field type="CharField" name="current_location_code">DLW</field>
<field type="CharField" name="customer">Freshbells - 471459</field>
<field type="CharField" name="consignee">Forward</field>
<field type="CharField" name="pickupdate"></field>
<field type="CharField" name="status">Shipment Uploaded</field>
<field type="CharField" name="tracking_status">Shipment Not Handed over</field>
<field type="CharField" name="reason_code"></field>
<field type="CharField" name="reason_code_description"></field>
<field type="CharField" name="reason_code_number">001</field>
<field type="CharField" name="receiver"></field>
<field type="CharField" name="lat">0.0000000</field>
<field type="CharField" name="long">0.0000000</field>
<field type="CharField" name="rev_pickup_signature" ></field>
<field type="CharField" name="rev_pickup_packed_image" ></field>
<field type="CharField" name="rev_pickup_open_image" ></field>
</object>
</ecomexpress-objects>
XML;
}
Output:
[
{
"awb_number": 115979601,
"orderid": "001",
"actual_weight": 0.8,
"origin": "DELHI-DLW",
"destination": "DELHI-DLW",
"current_location_name": "DELHI-DLW",
"current_location_code": "DLW",
"customer": "Freshbells - 471459",
"consignee": "Forward",
"pickupdate": "",
"status": "Shipment Uploaded",
"tracking_status": "Shipment Not Handed over",
"reason_code": "",
"reason_code_description": "",
"reason_code_number": "001",
"receiver": "",
"lat": "0.0000000",
"long": "0.0000000",
"rev_pickup_signature": "",
"rev_pickup_packed_image": "",
"rev_pickup_open_image": ""
}
]
This approach could be extended with actual model classes in your code. Here is a very basic approach for this (without error handling):
// define a class for the model
class AWB implements JsonSerializable {
public readonly int $awb_number;
public readonly string $orderid;
public readonly float $actual_weight;
public function __construct(stdClass $values) {
foreach ((array)$values as $name => $value) {
if (property_exists($this, $name)) {
$this->{$name} = $value;
}
}
}
public function jsonSerialize(): mixed {
return get_object_vars($this);
}
}
// mapping array
$models = [
'awb' => AWB::class
];
$document = new DOMDocument();
$document->loadXML(getXMLString());
$xpath = new DOMXpath($document);
$objects = [];
foreach ($xpath->evaluate('/ecomexpress-objects/object') as $objectNode) {
$object = new stdClass();
foreach ($xpath->evaluate('field', $objectNode) as $fieldNode) {
// read field values - just like the previous example
}
// map model name to class
$class = $models[$objectNode->getAttribute('model')];
$objects[] = new $class($object);
}
echo json_encode($objects, JSON_PRETTY_PRINT);
Output:
[
{
"awb_number": 115979601,
"orderid": "001",
"actual_weight": 0.8
}
]
Also PHP attributes could be used to map field names to properties.

Related

Doctrine orm mapping returns error Undefined index

I'm trying to write a code that maps two to entities but in the result,
I get an error Undefined index: filterId
So without doctrine, the query is simple SELECT * FROM filter f INNER JOIN filter_options fo ON f.id = fo.filter_id WHERE f = 1;
So I need to get the same results as this query would give.
tables example:
filter
id|status
---------
1|active
2|active
3|active
filter_options:
id|filter_id|text
---------
1|1|lorem
2|1|ipsum
3|3|and
4|2|etc
Entity classes :
class Filter
{
private $id;
private $status;
/**
* #var FilterOption[]
*/
private $options;
}
class FilterOption
{
private $id;
private $filterId;
private $text;
}
Filter.orm.xml:
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="status" column="status"/>
<one-to-many field="options" target-entity="FilterOption" mapped-by="filterId"/>
FilterOption.orm.xml:
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="text" column="text"/>
<field name="filterId" column="filter_id"/>
What I'm doing wrong. I want to get Filter entity in which property $options in an array with all other filterOptions entities.
You need to think about relation in Doctrine as of relation between objects, not as relation into database. Because of this you should not think about database columns like filter_id as plain properties, they needs to be replaced with actual entity associations instead.
In your case you need to replace plain filterId property into FilterOption entity with inverse side of Many-to-One association. Please refer Doctrine documentation for details.
Your code may look something like this:
Filter.php
class Filter
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $status;
/**
* #var FilterOption[]
*/
private $options;
}
FilterOption.php
class FilterOption
{
/**
* #var int
*/
private $id;
/**
* #var Filter
*/
private $filter;
/**
* #var string
*/
private $text;
}
Filter.orm.xml:
<doctrine-mapping>
<entity name="Filter" table="filter">
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="status" column="status"/>
<one-to-many field="options" target-entity="FilterOption" mapped-by="filter"/>
</entity>
<doctrine-mapping>
FilterOption.orm.xml:
<doctrine-mapping>
<entity name="FilterOption" table="filter_option">
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="text" column="text"/>
<many-to-one field="filter" target-entity="Filter" inversed-by="options">
<join-column name="filter_id" referenced-column-name="id" />
</many-to-one>
</entity>
<doctrine-mapping>

Magento 2 / Custom Config Field Array Mapping

hope your will have the time to help me.
On any circumstances my Magento 2 Array Mapping will not write selected="selected" to my field. Getting crazy about this.
The Data will be saved and fetched correct.
Big thanks to your help.
Version: Magento 2.2.2
/app/code/Sumedia/Switcher/Block/Adminhtml/System/Config/Form/Field/Group.php
<?php
namespace Sumedia\Switcher\Block\Adminhtml\System\Config\Form\Field;
class Group extends \Magento\Framework\View\Element\Html\Select {
/**
* #var \Magento\Store\Model\StoreManager
*/
protected $storeManager;
/**
* Store constructor.
* #param \Magento\Framework\View\Element\Context $context
* #param \Magento\Store\Model\StoreManager $storeManager
* #param array $data
*/
public function __construct(
\Magento\Framework\View\Element\Context $context,
\Magento\Store\Model\StoreManager $storeManager,
array $data = []
){
parent::__construct($context, $data);
$this->storeManager = $storeManager;
}
/**
* #return string
*/
public function _toHtml(){
if(!$this->getOptions()){
$groups = $this->storeManager->getGroups();
foreach($groups AS $row){
$this->addOption($row->getGroupId(),$row->getName());
}
}
return parent::_toHtml();
}
/**
* #param string $value
* #return $this
*/
public function setInputName($value){
return $this->setName($value);
}
}
/app/code/Sumedia/Switcher/Block/Adminhtml/System/Config/Form/Field/Groupmap.php
<?php
namespace Sumedia\Switcher\Block\Adminhtml\System\Config\Form\Field;
use Magento\Backend\Block\Template\Context;
use Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray;
class Groupmap extends AbstractFieldArray
{
/**
* #var \Sumedia\Switcher\Block\Adminhtml\System\Config\Form\Field\Group
*/
protected $renderer;
public function __construct(
Context $context,
array $data = [])
{
parent::__construct($context, $data);
}
public function getRenderer()
{
if(!$this->renderer){
$this->renderer = $this->getLayout()->createBlock(
'Sumedia\Switcher\Block\Adminhtml\System\Config\Form\Field\Group',
'',['data' => ['is_renderer_to_js_template' => true]]);
}
return $this->renderer;
}
protected function _prepareToRender()
{
$renderer = $this->getRenderer();
$this->addColumn('store',[
'label' => __('Store'),
'renderer'=>$renderer
]);
$this->addColumn('name',[
'label' => __('Name')
]);
$this->_addAfter = false;
$this->_addButtonLabel = __('Add');
parent::_prepareToRender();
}
protected function _prepareArrayRow(\Magento\Framework\DataObject $row)
{
$store = $row->getStore();
$options = array();
if($store){
$options['option_'.$this->getRenderer()->calcOptionHash($store)] = 'selected="selected"';
}
$row->setData('option_extra_attrs',$options);
}
public function renderCellTemplate($columnName)
{
if($columnName == 'store'){
$this->_columns[$columnName]['class'] = 'input-text required-entry validate-number';
$this->_columns[$columnName]['style'] = 'width:50px';
}
return parent::renderCellTemplate($columnName);
}
}
/app/code/Sumedia/Switcher/Model/Adminhtml/System/Config/Groupmap.php
<?php
namespace Sumedia\Switcher\Model\Adminhtml\System\Config;
use Magento\Framework\App\Config\Value;
class Groupmap extends Value {
public function beforeSave(){
$data = array();
$value = $this->getValue();
if(is_array($value)) {
foreach($value AS $_data){
if(!isset($_data['store']) || !isset($_data['name'])){
continue;
}
$id = uniqid();
$data[$id] = array('store' => $_data['store'],'name' => $_data['name']);
}
}
$this->setValue(serialize($data));
return $this;
}
public function afterLoad(){
$value = #unserialize($this->getValue());
if (is_array($value)) {
$data = array();
foreach($value AS $id => $_data){
$data[$id] = array('store' => $_data['store'], 'name' => $_data['name']);
}
$this->setValue($data);
}
return $this;
}
}
/app/code/Sumedia/Switcher/etc/adminhtml/system.xml
<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<tab id="sumedia" translate="label" sortOrder="100">
<label>Sumedia</label>
</tab>
<section id="sumedia_switcher" translate="label" sortOrder="100" showInDefault="1" showInStore="1" showInWebsite="1">
<class>seperator-top</class>
<label>Switcher</label>
<tab>sumedia</tab>
<resource>Sumedia_Switcher::config</resource>
<group id="general" translate="label" type="text" sortOrder="10" showInWebsite="1" showInStore="1" showInDefault="1">
<label>General</label>
<field id="enabled" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
<group id="store_switch" translate="label" type="text" sortOrder="10" showInWebsite="1" showInStore="1" showInDefault="1">
<label>Store Switch</label>
<field id="enabled" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="use_mapping" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
<label>Use Mapping</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="mapping" translate="label comment tooltip" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Mapping</label>
<frontend_model>Sumedia\Switcher\Block\Adminhtml\System\Config\Form\Field\Groupmap</frontend_model>
<backend_model>Sumedia\Switcher\Model\Adminhtml\System\Config\Groupmap</backend_model>
</field>
</group>
</section>
</system>
</config>
I see that you have reload all needed methods with your data, but there you have an issue in getRenderer method in Sumedia\Switcher\Block\Adminhtml\System\Config\Form\Field\Groupmap class, try to change 'is_renderer_to_js_template' to 'is_render_to_js_template'
I had the same issue during development custom module couple days ago and found a solution only after reviewing magento's core code (catalog inventory module's code) =)

Association entity related - change by hand

I do have an entity in AppBundle/Entity which I generated me from database.
<?php
namespace AppBundle\Entity;
/*
*
* ExtensionSyliusShopUser
*/
class ExtensionSyliusShopUser
{/**
* Set user
*
* #param \AppBundle\Entity\SyliusShopUser $user
*
* #return ExtensionSyliusShopUser
*/
public function setUser(\AppBundle\Entity\SyliusShopUser $user = null)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \AppBundle\Entity\SyliusShopUser
*/
public function getUser()
{
return $this->user;
}
Now I want to change the setUser() & getUser() to:
Sylius\Component\Core\Model\ShopUser
If I am going to change #parm and the value in braces e.g.:
public function setUser(\Sylius\Component\Core\Model\ShopUser $user = null)
I get the error:
Expected value of type "AppBundle\Entity\SyliusShopUser" for association field "AppBundle\Entity\ExtensionSyliusShopUser#$user", got "Sylius\Component\Core\Model\ShopUser" instead.
How can I change it?
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="AppBundle\Entity\ExtensionSyliusShopUser" table="extension_sylius_shop_user">
<indexes>
<index name="user_id" columns="user_id"/>
</indexes>
<id name="id" type="integer" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="wishlist" type="text" column="wishlist" length="65535" nullable="true">
<options>
<option name="fixed"/>
</options>
</field>
<many-to-one field="user" target-entity="SyliusShopUser" fetch="LAZY">
<join-columns>
<join-column name="user_id" referenced-column-name="id"/>
</join-columns>
</many-to-one>
</entity>
</doctrine-mapping>
You need to change the definition of your $user property (column). which is not shown in a code sample you provided.
In case you use annotations is should look like this:
/**
* #ORM\ManyToOne(targetEntity="Sylius\Component\Core\Model\ShopUser")
*/
private $user;
In case of XML config:
<many-to-one field="user" target-entity="Sylius\Component\Core\Model\ShopUser">

Doctrine Optimistic Locking

I am having a problem with doctrine optimistic locking.
I have mapped my entity like the documentation says (I guess I think so), and my optimistic locking version doesn't increment. It always stays as null inside the database.
Part of my entity and it's mapping:
Entity:
/** #var AccountID */
private $accountID;
/** #var ClientID */
private $clientID;
/** #var Money */
private $money;
/** #var array|Transfer[] */
private $transfers;
/** #var bool Account status - active not active */
private $active;
/** #var int */
private $version = 1;
Mapping:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="Madkom\ES\Banking\Domain\Account\Account" table="account">
<indexes>
<index name="client_id_idx" columns="client_id"/>
</indexes>
<embedded name="accountID" class="Madkom\ES\Banking\Domain\Account\AccountID" />
<embedded name="clientID" class="Madkom\ES\Banking\Domain\Account\ClientID" />
<embedded name="money" class="Madkom\ES\Banking\Domain\Money" />
<field name="transfers" column="transfers" type="jsonb"/>
<field name="active" column="active" type="boolean" length="32" unique="true" />
<field name="version" type="integer" version="true"/>
</entity>
Whenever I added new record, version is always the same, null.
I am using Postgresql 9.4 and Doctrine 2.5.1.
The jsonb type comes from outside library.

Symfony2/Doctrine2 model classes mappings for reusable bundle

I am currently trying to create a reusable bundle with Symfony2 using model classes but I am not able to register their mappings so Doctrine recognize them.
I read that using compiler pass could be the solution so I followed the guide in the Symfony Cookbook (http://symfony.com/doc/current/cookbook/doctrine/mapping_model_classes.html) and I also looked at the source code in the FOSUserBundle for some inspiration.
And here is what I've done so far :
class GPGoodsBundle extends Bundle{
public function build(ContainerBuilder $container)
{
parent::build($container);
$this->addRegisterMappingsPass($container);
}
/**
* #param ContainerBuilder $container
*/
private function addRegisterMappingsPass(ContainerBuilder $container)
{
$modelDir = realpath(__DIR__.'/Resources/config/doctrine/model');
$mappings = array(
$modelDir => 'GP\Bundle\GoodsBundle\Model',
);
$ormCompilerClass = 'Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass';
if (class_exists($ormCompilerClass)) {
$container->addCompilerPass(
DoctrineOrmMappingsPass::createXmlMappingDriver(
$mappings,
array('gp_goods.model_manager_name'),
'gp_goods.backend_type_orm'
)
);
}
}
}
But when trying to migrate my entity (just to see if it's working) here is the result :
$php app/console doctrine:migrations:diff
No mapping information to process.
My entities are stored under "GP\Bundle\GoodsBundle\Model" and their mappings under "GP\Bundle\GoodsBundle\Resources\config\doctrine\model"
So my question is : what is the good way to create a reusable bundle and how to register mappings of model classes?
If you need any additional information, do not hesitate to ask!
Thank you for your help!
Here is one of my model classes :
class Good implements GoodInterface{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
protected $serial;
/**
* #var \DateTime
*/
protected $manufacturedAt;
/**
* #var \DateTime
*/
protected $deliveredAt;
/**
* #var \DateTime
*/
protected $expiredAt;
/**
* #var string
*/
protected $checkInterval;
/**
* #var string
*/
protected $status;
/**
* #var string
*/
protected $slug;
/**
* #var \DateTime
*/
protected $createdAt;
/**
* #var \DateTime
*/
protected $updatedAt;
/**
* #var \DateTime
*/
protected $deletedAt;
public function __construct(){
$this->createdAt = new \DateTime("now");
$this->status = 'good';
}
.... getters/setters .....
}
And the mappings :
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="GP\Bundle\GoodsBundle\Model\Good" table="good">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="serial" type="string" column="serial" length="255"/>
<field name="manufacturedAt" type="date" column="manufactured_at"/>
<field name="deliveredAt" type="date" column="delivered_at"/>
<field name="expiredAt" type="date" column="expired_at"/>
<field name="checkInterval" type="string" column="check_interval" length="255" nullable="true"/>
<field name="status" type="string" column="status" length="255"/>
<field name="slug" type="string" column="slug" length="255">
<gedmo:slug fields="serial" unique="true" />
</field>
<field name="createdAt" type="datetime" column="created_at">
<gedmo:timestampable on="create"/>
</field>
<field name="updatedAt" type="datetime" column="updated_at" nullable="true">
<gedmo:timestampable on="update"/>
</field>
<field name="deletedAt" type="datetime" column="removed_at" nullable="true">
<gedmo:soft-deleteable field-name="deletedAt"/>
</field>
</entity>
</doctrine-mapping>
When you have entities outside of any bundle or that the location is not the usual one, you'll have to change the doctrine section in config.yml from
doctrine:
# ...
orm:
# ...
auto_mapping: true
to
doctrine:
# ...
orm:
# ...
mappings:
model: # replace `model` with whatever you want
type: annotation # could be xml, yml, ...
dir: %kernel.root_dir%/../path/to/your/model/directory
prefix: Prefix\Namespace # replace with your actual namespace
alias: Model # replace with the desired alias
is_bundle: false
The dir parameter informs Doctrine where to look for the mapping definitions. If you're using annotations, it will be your model directory. Otherwise, it will be the directory of your xml/yml files.
Entities's names — to access from Doctrine repositories — begin with Model in this case, for example, Model:User. It corresponds to the parameter alias.
When editing a configuration file, do not forget to clear the cache.
Moreover, in my question, I wrote that I changed something in my Bundle class but it was not useful as the bundle won't be reused by another project. So I removed everything.
See this answer for more details :
https://stackoverflow.com/a/10001019/2720307
Thanks to Elnur Abdurrakhimov!

Categories