Создан импорт из 1C, без поддержки групп

This commit is contained in:
root 2021-01-19 07:42:10 +00:00
parent 94b719b67e
commit 212694c917
13 changed files with 900 additions and 546 deletions

View File

@ -12,9 +12,9 @@
}
],
"require": {
"php": ">=8.0.0",
"php": "^8.0.0",
"twbs/bootstrap": ">=4.5",
"yiisoft/yii2": ">=2.0.14",
"yiisoft/yii2": "2.*",
"yiisoft/yii2-bootstrap": ">=2.0.0",
"yiisoft/yii2-swiftmailer": ">=2.0.0",
"bower-asset/bootstrap": "*",

627
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -68,12 +68,19 @@ $config = [
'class' => 'carono\exchange1c\ExchangeModule',
'groupClass' => 'app\models\SupplyGroup',
'productClass' => 'app\models\Supply',
'offerClass' => 'app\models\Product',
'offerClass' => 'app\models\SupplyEdgeProduct',
'partnerClass' => 'app\models\Account',
'documentClass' => 'app\models\Purchase',
'auth' => function ($mail, $pswd) {
// Необходимо уничтожить AccountForm
return (new \app\models\AccountForm())->authentication($mail, $pswd);
// return (new \app\models\AccountForm())->authentication($mail, $pswd);
if ($user = \app\models\Account::findByMail($mail)) {
if ($user->validatePassword($pswd)) {
return $user;
}
}
return false;
}
]
],

View File

@ -17,7 +17,16 @@ class Account extends Document implements IdentityInterface, PartnerInterface
{
return array_merge(
parent::attributes(),
['mail', 'pswd', 'name', 'simc', 'sity', 'comp', 'taxn', 'auth']
[
'mail',
'pswd',
'name',
'simc',
'sity',
'comp',
'taxn',
'auth'
]
);
}
@ -73,11 +82,16 @@ class Account extends Document implements IdentityInterface, PartnerInterface
];
}
public function getId()
public function getId(): string
{
return $this->_key;
}
public function readId(): string
{
return self::collectionName() . '/' . $this->getId();
}
public function getAuthKey()
{
return $this->auth;

View File

@ -2,30 +2,36 @@
namespace app\models;
use Yii;
use explosivebit\arangodb\ActiveRecord;
abstract class Document extends ActiveRecord
{
public function attributes()
{
return ['_key', 'date'];
}
public function rules()
{
return [
// [
// 'date',
// 'required',
// 'message' => 'Заполните поле: {attribute}'
// ]
'_key',
'date',
'writer'
];
}
public function attributeLabels()
{
return [
'date' => 'Дата'
'date' => 'Дата',
'writer' => 'Аккаунт записавшего'
];
}
public function rules()
{
return [
[
'writer',
'string'
// Надо добавить проверку существования аккаунта
]
];
}
@ -33,16 +39,23 @@ abstract class Document extends ActiveRecord
{
if (parent::beforeSave($data)) {
if ($this->isNewRecord) {
$this->date = time();
}
$this->date = time();
$this->writer = $this->writer ?? Yii::$app->user->identity->readId();
return true;
}
return false;
}
public static function readAmount()
public function readId(): ?string
{
return isset($this->_key) ? static::collectionName() . '/' . $this->_key : null;
}
public static function readAmount(): int
{
return static::find()->count();
}

View File

@ -4,53 +4,89 @@ namespace app\models;
use explosivebit\arangodb\ActiveRecord;
abstract class Edge extends ActiveRecord
abstract class Edge extends Document
{
public function attributes()
{
return ['_key', '_from', '_to', 'date', 'type', 'account'];
}
public function rules()
{
return [
return array_merge(
parent::attributes(),
[
['_from', '_to', 'date', 'type', 'account'],
'required',
'message' => 'Заполните поле: {attribute}'
],
[
'date',
'integer'
],
[
'type',
'string'
],
[
'account',
'string'
// Надо добавить проверку существования аккаунта
'_from',
'_to',
'type'
]
];
);
}
public function attributeLabels()
{
return [
'date' => 'Дата',
'type' => 'Тип',
'account' => 'Аккаунт'
];
return array_merge(
parent::attributeLabels(),
[
'date' => 'От кого',
'date' => 'К кому',
'type' => 'Тип'
]
);
}
public function rules()
{
return array_merge(
parent::rules(),
[
[
['_from', '_to'],
'required',
'message' => 'Заполните поле: {attribute}'
],
[
'type',
'string'
]
]
);
}
/**
* Записать
*/
public function write(string $_from, string $_to, string $type = '', array $data = []): ?static
{
// Инициализация
$edge = isset($this->_key) ? $this : new static;
// Настройка
$edge->_from = $_from;
$edge->_to = $_to;
$edge->type = $type;
foreach ($data as $key => $value) {
if(is_int($key)) {
// Если ключ задан автоматически
$edge->{$value} = true;
} else {
// Иначе ключ записан вручную
$edge->{$key} = $value;
}
}
// Запись
$edge->save();
return $edge;
}
public function beforeSave($data)
{
if (parent::beforeSave($data)) {
if ($this->isNewRecord) {
$this->date = time();
}
$this->type = $this->type ?? '';
return true;
}

View File

@ -3,30 +3,37 @@
namespace app\models;
use moonland\phpexcel\Excel;
use carono\exchange1c\interfaces\ProductInterface;
use Zenwalker\CommerceML\Model\Product as Product1c;
class Product extends Document implements ProductInterface
class Product extends Document
{
const SCENARIO_IMPORT = 'import';
const SCENARIO_WRITE = 'write';
public $file;
public $group;
public static function collectionName()
public static function collectionName(): string
{
return 'product';
}
public function attributes()
public function attributes(): array
{
return array_merge(
parent::attributes(),
['name', 'catn', 'oemn', 'data', 'cost', 'time']
[
'name',
'ocid',
'catn',
'oemn'
// 'data',
// 'cost',
// 'time'
]
);
}
public function rules()
public function rules(): array
{
return array_merge(
parent::rules(),
@ -35,6 +42,7 @@ class Product extends Document implements ProductInterface
['name', 'catn'],
'required',
'message' => 'Заполните поля: {attribute}',
'on' => self::SCENARIO_WRITE,
'except' => self::SCENARIO_IMPORT
],
[
@ -43,7 +51,7 @@ class Product extends Document implements ProductInterface
'message' => 'Заполните поля: {attribute}',
'on' => self::SCENARIO_IMPORT
],
['catn', 'integer', 'message' => '{attribute} должен быть числом'],
['catn', 'string', 'message' => '{attribute} должен быть строкой'],
// ['oemn', 'integer'], Нужна своя проверка на массив
[
'file',
@ -61,37 +69,24 @@ class Product extends Document implements ProductInterface
);
}
public function attributeLabels()
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'name' => 'Название (name)',
'ocid' => 'Идентификатор 1C (ocid)',
'catn' => 'Каталожный номер (catn)',
'oemn' => 'OEM номера (oemn)',
'data' => 'Данные товара (data)',
'cost' => 'Цены (cost)',
'time' => 'Сроки доставки (time)',
// 'data' => 'Данные товара (data)',
// 'cost' => 'Цены (cost)',
// 'time' => 'Сроки доставки (time)',
'file' => 'Документ',
'group' => 'Группа',
'group' => 'Группа'
]
);
}
public function beforeSave($data)
{
if (parent::beforeSave($data)) {
if ($this->isNewRecord) {
// Надо избавиться от unset();
unset($this->_key);
}
return true;
}
return false;
}
public function import()
{
// Инициализация массива данных
@ -144,171 +139,6 @@ class Product extends Document implements ProductInterface
return false;
}
/**
* Установка реквизитов для продукта
*/
// public function setRequisite1c(string $name, string $value): bool
public function setRequisite1c($name, $value): bool
{
if (!$requisite = Requisite::readByName($name)) {
// Реквизиты не найдены
// Инициализация
$requisite = new Requisite();
$requisite->name = $name;
$requisite->value = $value;
// Запись
return $requisite->save();
}
return false;
}
/**
* Установка группы, где находится продукт
*/
// public function setGroup1c(ProductGroup $group): bool
public function setGroup1c($group): bool
{
// Чтение группы
$group = SupplyGroup::readByOnecName($group->id)[0];
// Запись ребра: ПОСТАВКА => ГРУППА ПОСТАВОК
return static::writeEdgeBetweenGroup(static::collectionName() . '/' . $this->_key, $group->collectionName() . '/' . $group->_key);
}
/**
* Запись всех параметров. Вызывается 1 раз при импорте
*/
public static function createProperties1c($properties): void
{
// Это нам не нужно, кажется, надо будет тестить
/**
* @var \Zenwalker\CommerceML\Model\Property $property
*/
// foreach ($properties as $property) {
// $propertyModel = Property::createByMl($property);
// foreach ($property->getAvailableValues() as $value) {
// if (!$propertyValue = PropertyValue::findOne(['onec_name' => $value->id])) {
// $propertyValue = new PropertyValue();
// $propertyValue->name = (string)$value->Значение;
// $propertyValue->property_id = $propertyModel->id;
// $propertyValue->onec_name = (string)$value->ИдЗначения;
// $propertyValue->save();
// unset($propertyValue);
// }
// }
// }
}
/**
* $property - Свойство товара (import.xml > Классификатор > Свойства > Свойство)
* $property->value - Разыменованное значение (string) (import.xml > Классификатор > Свойства > Свойство > Значение)
* $property->getValueModel() - Данные по значению, Ид значения, и т.д (import.xml > Классификатор > Свойства > Свойство > ВариантыЗначений > Справочник)
*
* @param MlProperty $property
* @return void
*/
public function setProperty1c($property): void
{
// Это тоже нам не нужно
// $propertyModel = Property::findOne(['onec_name' => $property->id]);
// $propertyValue = $property->getValueModel();
// if ($propertyAccountingId = (string)$propertyValue->ИдЗначения) {
// $value = PropertyValue::findOne(['onec_name' => $propertyAccountingId]);
// $attributes = ['property_value_id' => $value->id];
// } else {
// $attributes = ['value' => $propertyValue->value];
// }
// $this->addPivot($propertyModel, PvProductProperty::class, $attributes);
}
/**
* В этой фукнции мы получаем абсолютный путь до картинки и название изрбражения (для alt аттрибута)
*
* @param string $path
* @param string $caption
* @return mixed
*/
public function addImage1c($path, $caption): bool
{
// if (!$this->getImages()->andWhere(['md5' => md5_file($path)])->exists()) {
// $this->addPivot(FileUpload::startUpload($path)->process(), PvProductImage::class, ['caption' => $caption]);
// }
return false;
}
/**
* В эту фукнцию отправляется xml данные предложения из файла
*/
// public function getOffer1c(Supply $offer): Supply
public function getOffer1c($offer): Supply
{
$supply = Supply::createByMl($offer);
$supply->product_id = $this->id;
if ($supply->getDirtyAttributes()) {
$supply->save();
}
return $supply;
}
/**
* @param MlOffer $offer
*/
public static function createByMl($offer): Supply
{
if (!$model = static::readByOnecName($offer->id)) {
// Нет записей в базе данных
// Инициализация
$model = new static;
$model->name = (string) $offer->name;
$model->onec_name = (string) $offer->id;
}
$model->remnant = (string) $offer->Количество;
return $model;
}
/**
* @param $product
* @return self
*/
public static function createModel1c($product): static
{
if (!$model = static::findOne(['onec_name' => $product->id])) {
$model = new static();
$model->onec_name = $product->id;
}
$model->name = $product->name;
$model->description = (string) $product->Описание;
$model->article = (string) $product->Артикул;
$model->save();
return $model;
}
public function setRaw1cData($cml, $object)
{
}
/**
* Название поля в котором хранится ID из 1C
*/
public static function getIdFieldName1c(): string
{
return 'onec_name';
}
private static function writeEdgeBetweenGroup(string $from, string $to): bool
{
// Инициализация
@ -337,16 +167,14 @@ class Product extends Document implements ProductInterface
public static function readById(string $_key): ?Product
{
return Product::findOne(['_key' => $_key]);
return self::findOne(['_key' => $_key]);
}
public function getGroup1c(): ProductGroup
/**
* Поиск по каталожному номеру
*/
public static function readByCatn(string $catn): ?Product
{
return $this->group;
}
public static function readByOnecName(string $name): ?Product
{
return static::findOne([static::getIdFieldName1c() => $name]);
return self::findOne(['catn' => $catn]);
}
}

View File

@ -16,7 +16,7 @@ class ProductGroup extends Document implements GroupInterface
{
return array_merge(
parent::attributes(),
['name', 'onec_name', 'onec_prnt_name']
['name', 'onec_id', 'onec_prnt_id']
);
}
@ -40,8 +40,8 @@ class ProductGroup extends Document implements GroupInterface
parent::attributeLabels(),
[
'name' => 'Название (name)',
'onec_name' => 'Название 1C (onec_name)',
'onec_prnt_name' => 'Название родителя 1C (onec_prnt_name)',
'onec_id' => 'Название 1C (onec_id)',
'onec_prnt_id' => 'Название родителя 1C (onec_prnt_id)',
]
);
}
@ -95,13 +95,13 @@ class ProductGroup extends Document implements GroupInterface
*/
public static function createByML(Group $group): static|array|null
{
if (!$model = static::readByOnecName($group->id)) {
if (!$model = static::readByOnecId($group->id)) {
// Группа не найдена
// Инициализация
$model = new static;
$model->onec_name = $group->id;
$model->onec_id = $group->id;
}
$model->name = $group->name;
@ -112,11 +112,11 @@ class ProductGroup extends Document implements GroupInterface
// Инициализация (рекурсия)
$parentModel = static::createByML($parent);
$model->onec_prnt_name = $parentModel->id;
$model->onec_prnt_id = $parentModel->id;
unset($parentModel);
} else {
$model->onec_prnt_name = null;
$model->onec_prnt_id = null;
}
$model->save();
@ -165,11 +165,11 @@ class ProductGroup extends Document implements GroupInterface
*/
public static function getIdFieldName1c(): string
{
return 'onec_name';
return 'onec_id';
}
public static function readByOnecName(string $name): ?Product
public static function readByOnecId(string $onec_id): ?ProductGroup
{
return static::findOne([static::getIdFieldName1c() => $name]);
return static::findOne(['onec_id' => $onec_id]);
}
}

View File

@ -6,15 +6,15 @@ use carono\exchange1c\interfaces\DocumentInterface;
class Purchase extends Document implements DocumentInterface
{
public static function collectionName()
public static function collectionName(): string
{
return 'requisite';
return 'purchase';
}
/**
* @return DocumentInterface[]
*/
public static function findDocuments1c()
public static function findDocuments1c(): ?self
{
return self::find()->andWhere(['status_id' => 2])->all();
}
@ -22,13 +22,14 @@ class Purchase extends Document implements DocumentInterface
/**
* @return OfferInterface[]
*/
public function getOffers1c()
public function getOffers1c(): mixed
{
return $this->offers;
return true;
}
public function getRequisites1c()
public function getRequisites1c(): mixed
{
return true;
}
/**
@ -36,32 +37,15 @@ class Purchase extends Document implements DocumentInterface
*
* @return PartnerInterface
*/
public function getPartner1c()
public function getPartner1c(): Account
{
return $this->user;
// !!!!!!!!!!!!!!!!!!!
return $this->user ?? new Account;
}
public function getExportFields1c($context = null)
{
return [
'Ид' => 'id',
'Наименование' => 'login',
'ПолноеНаименование' => 'full_name',
'Фамилия' => 'surname',
'Имя' => 'name',
'Контакты' => [
[
'@name' => 'Контакт',
'Тип' => 'Почта',
'Значение' => $this->email,
],
[
'@name' => 'Контакт',
'Тип' => 'ТелефонРабочий',
'Значение' => $this->phone,
],
],
];
return [];
}
/**
@ -71,10 +55,10 @@ class Purchase extends Document implements DocumentInterface
*/
public static function getIdFieldName1c()
{
return 'accounting_id';
return 'onec["Ид"]';
}
public function setRaw1cData($cml, $object)
public function setRaw1cData($cml, $object): void
{
}
}

View File

@ -6,153 +6,182 @@ use Yii;
use app\models\Account;
use app\models\Product;
use app\models\SupplyEdgeProduct;
use carono\exchange1c\interfaces\OfferInterface;
use app\models\traits\Xml2Array;
use carono\exchange1c\interfaces\ProductInterface;
class Supply extends Product implements OfferInterface
// class Supply extends Product implements OfferInterface
class Supply extends Product implements ProductInterface
{
public static function collectionName()
use Xml2Array;
public static function collectionName(): string
{
return 'supply';
}
public function afterSave($data, $vars)
public function attributes(): array
{
if (is_null($product = self::readByCatn($this->catn))) {
// Товар не найден
return array_merge(
parent::attributes(),
[
'onec'
]
);
}
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'onec' => 'Данные 1C'
]
);
}
public function afterSave($data, $vars): void
{
// Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write(Yii::$app->user->identity->readId(), $this->readId(), 'import');
}
public function setRequisite1c($name, $value): mixed
{
return true;
}
/**
* Установка группы, где находится продукт
*/
public function setGroup1c($group): mixed
{
// Чтение группы
// if ($group = SupplyGroup::readByOnecId($group->id)) {
// // Запись ребра: ПОСТАВКА => ГРУППА ПОСТАВОК
// return static::writeEdgeBetweenGroup(static::collectionName() . '/' . $this->_key, $group->collectionName() . '/' . $group->_key);
// }
return true;
}
public static function createProperties1c($properties): mixed
{
return true;
}
public function setProperty1c($property): mixed
{
return true;
}
public function addImage1c($path, $caption): mixed
{
return true;
}
/**
* Запись ребра (предложения от поставки к продукту)
*/
public function getOffer1c($offer): SupplyEdgeProduct
{
if (empty($this->catn)) {
// Не передан каталожный номер
// Разработчику библеотеки надо дать по жопе
return new SupplyEdgeProduct;
} else if (!$catn = Product::readByCatn($this->catn)) {
// Продукт не найден
if (!$this->initProduct()) {
// Не удалось инициализировать продукт
// Разработчику библеотеки надо дать по жопе
return new SupplyEdgeProduct;
}
}
$catn = Product::readByCatn($this->catn);
// Запись ребра: ПОСТАВКА -> ПРОДУКТ
return (new SupplyEdgeProduct)->write(
$this->readId(),
$catn->readId(),
'sell',
[
'onec' => self::xml2array($offer->xml)
]
);
}
/**
* Создать продукт
*/
public static function createModel1c($product): self
{
// Инициализация
$product = (new Product(array_intersect_key($this->getAttributes(), (new Product)->getAttributes())));
$model = self::readByOnecId($id = (string) $product->Ид) ?? new self;
// Настройки
$model->ocid = $id ?? null;
$model->catn = (string) $product->Артикул;
$model->oemn = null;
$model->onec = self::xml2array($product->xml);
// Запись
if (!$product->save()) {
return false;
}
$model->save();
return $model;
}
// Запись рёбер: АККАУНТ => ПОСТАВКА => ТОВАР, и проверка на то, что оба созданы
static::writeEdgeBetweenAccount(Account::collectionName() . '/' . Yii::$app->user->identity->_key, static::collectionName() . '/' . $this->_key);
static::writeEdgeBetweenProduct(static::collectionName() . '/' . $this->_key, Product::collectionName() . '/' . $product->_key);
}
/**
* В этом методе необходимо создать все типы цен, фукнция вызывается один раз
*/
public static function createPriceTypes1c($types): void
protected function initProduct(): bool
{
foreach ($types as $type) {
// PriceType::createByMl($type);
}
// Надо не забыть сделать выборку полей и ручное подключение
if (empty($this->catn)) {
// Не передан каталожный номер
return false;
} else if (Product::readByCatn($this->catn)) {
// Продукт уже был инициализирован
return true;
}
/**
* offers.xml > ПакетПредложений > Предложения > Предложение > Цены
*
* Цена товара,
* К $price можно обратиться как к массиву, чтобы получить список цен (Цены > Цена)
* $price->type - тип цены (offers.xml > ПакетПредложений > ТипыЦен > ТипЦены)
*
* @param \Zenwalker\CommerceML\Model\Price $price
*/
// Инициализация
$product = new Product();
// Настройки
$product->catn = $this->catn;
// Запись
return $product->save();
}
public function setPrice1c($price): void
{
// $priceType = PriceType::findOne(['accounting_id' => $price->getType()->id]);
// $priceModel = Price::createByMl($price, $this, $priceType);
// $this->addPivot($priceModel, PvOfferPrice::class);
}
public function setRaw1cData($cml, $object): bool
{
return false;
}
public static function readByOnecId(string $ocid): ?Supply
{
return self::findOne([self::getIdFieldName1c() => $ocid]);
}
public function getGroup1c(): SupplyGroup
{
return $this->group;
}
/**
* offers.xml > ПакетПредложений > Предложения > Предложение > ХарактеристикиТовара > ХарактеристикаТовара
*
* Характеристики товара
* $name - Наименование
* $value - Значение
*
* @param \Zenwalker\CommerceML\Model\Simple $specification
* @return void
* Название поля в котором хранится ID из 1C
*/
public function setSpecification1c($specification)
public static function getIdFieldName1c(): string
{
// $specificationModel = Specification::createByMl($specification);
// $this->addPivot($specificationModel, PvOfferSpecification::class, ['value' => (string)$specification->Значение]);
}
public function getExportFields1c($context = null)
{
return [
'Ид' => 'id',
'Наименование' => 'login',
'ПолноеНаименование' => 'full_name',
'Фамилия' => 'surname',
'Имя' => 'name',
'Контакты' => [
[
'@name' => 'Контакт',
'Тип' => 'Почта',
'Значение' => $this->email,
],
[
'@name' => 'Контакт',
'Тип' => 'ТелефонРабочий',
'Значение' => $this->phone,
],
],
];
}
public static function writeEdgeBetweenAccount(string $from, string $to): bool
{
// Инициализация
$edge = new AccountEdgeSupply();
// Настройка
$edge->_from = $from;
$edge->_to = $to;
// Запись
return $edge->save();
}
private static function writeEdgeBetweenProduct(string $from, string $to): bool
{
// Инициализация
$edge = new SupplyEdgeProduct();
// Настройка
$edge->_from = $from;
$edge->_to = $to;
// Запись
return $edge->save();
}
private static function writeEdgeBetweenGroup(string $from, string $to): bool
{
// Инициализация
$edge = new SupplyEdgeSupplyGroup();
// Настройка
$edge->_from = $from;
$edge->_to = $to;
// Запись
return $edge->save();
}
private static function writeEdgeBetweenRequisite(string $from, string $to): bool
{
// Инициализация
$edge = new SupplyEdgeRequisite();
// Настройка
$edge->_from = $from;
$edge->_to = $to;
// Запись
return $edge->save();
}
public static function readByCatn(string $catn): ?Product
{
return Product::findOne(['catn' => $catn]);
return 'ocid';
}
}

View File

@ -2,10 +2,76 @@
namespace app\models;
class SupplyEdgeProduct extends Edge
use app\models\traits\Xml2Array;
use carono\exchange1c\interfaces\OfferInterface;
use Zenwalker\CommerceML\Model\Offer;
class SupplyEdgeProduct extends Edge implements OfferInterface
{
public static function collectionName()
use Xml2Array;
public static function collectionName(): string
{
return 'supply_edge_product';
}
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
'ocid',
'onec'
]
);
}
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'ocid' => 'Идентификатор 1C (ocid)',
'onec' => 'Данные 1C'
]
);
}
public static function createPriceTypes1c($types): mixed
{
return true;
}
public function setPrice1c($price): mixed
{
return true;
}
public function setSpecification1c($specification): mixed
{
return true;
}
public function getExportFields1c($context = null): array
{
return [];
}
public function getGroup1c(): ProductGroup
{
return $this->group ?? new ProductGroup;
}
public static function readByOnecId(string $ocid): ?Supply
{
return self::findOne([self::getIdFieldName1c() => $ocid]);
}
/**
* Название поля в котором хранится ID из 1C
*/
public static function getIdFieldName1c(): string
{
return 'ocid';
}
}

View File

@ -34,9 +34,4 @@ class SupplyGroup extends ProductGroup
// Запись
return $edge->save();
}
public static function readByOnecName(string $onec_name): ?Product
{
return static::findOne(['onec_name' => $onec_name]);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace app\models\traits;
trait Xml2Array {
protected static function xml2array($xmlObject, $out = [])
{
foreach ((array) $xmlObject as $index => $node)
$out[$index] = (is_object($node) || is_array($node)) ? self::xml2array($node) : $node;
return $out;
}
}