Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Самое главное, что универсальный контейнер каждой «вселенной» может разбирать и генерировать транскод (JSON/XML/YAML/...), адаптируя к своей среде выполнения не только те данные, которые заложил в него разработчик самого приложения («A»), но и дополнительные данные, которые прицепили к «посылке» разработчики сервиса («C») или клиента («B»).Вас не напрягает, что сервис («C») или клиент («B») могут не только добавлять свои данные, но и изменять данные приложения («A»), причем так, что приложение («A») даже не будет догадываться, что его данные кто-то поменял?
Что имеем в результате? Контейнер для переноса любых данных.
Можно все то же самое делать через аннотацию @property
class GetForDependentCalc extends DataObject
{
public $baseCalcData;
public $dependentCalcData;
}
$a = new GetForDependentCalc();
$a->test = 'test';
var_dump($a);
/*
object(GetForDependentCalc)[1]
public 'baseCalcData' => null
public 'dependentCalcData' => null
public 'test' => string 'test' (length=4)
*/
class DataObject extends ArrayObject
{
public function __construct($input = [], $flags = ArrayObject::STD_PROP_LIST, $iterator_class = 'ArrayIterator')
{
parent::__construct($input, $flags, $iterator_class);
}
}
Более того, можно «научить» универсальный контейнер автоматически преобразовывать хранимые в нем данные в формат, например, JSON
можно получить весьма интересные последствия в виде цепочек функций-процессоров, где выходные данные одних функций являются входными данными для других
Для себя я решил задачу экстрагирования в статье небольшого размера
сути подхода по отделению обрабатываемых данных от обработчиков,
при распределенной разработке (слабосвязанными командами разработчиков) гетерогенных программных систем (типичный пример — web-приложение), интегрированных с внешними сервисами посредством SOAP/REST
$transId = $data['Sales'][3]['Payments'][0]['Transactions'][0]['Id'];
$transId = $data['/Sales/3/Payments/0/Transactions/0/Id'];
Вот только не будет отражена структура «известных данных», что изрядно усложнит разработку.
$transId = $data->getSales()[3]->getPayments()[0]->getTransactions()[0]->getId();
Ну вот! Так это то, о чем я и писал!!! Делая класс на базе DataObject вы делаете его:
а) типизируемым;
б) дополняемым;
Проблема в том, что в некоторых случаях множественное наследование не работает. Например, когда два-три расширения переопределяют один и тот же класс основного функционала.
CREATE TABLE Customer (
Id int NOT NULL AUTO_INCREMENT COMMENT 'this is attribute from base implementation',
Ref varchar(255) DEFAULT NULL COMMENT 'this attribute is added by plugin 1',
Email varchar(50) DEFAULT NULL COMMENT 'this attribute is added by plugin 2',
PRIMARY KEY (Id)
)
/**
* This is base object.
*
* @method int getId()
* @method void setId(int $data)
*/
class Customer extends DataObject {
}
/**
* This is extended customer (plugin 1).
*
* @method string getRef()
* @method void setRef(string $data)
*/
class CustomerRef extends Customer {
}
/**
* This is extended customer (plugin 2).
*
* @method string getEmail()
* @method void setEmail(string $data)
*/
class CustomerEmail extends Customer {
}
// data loader (base impl.)
$base = $repo->load('Customer', 21);
// plugin1 code on event 1
$cust1 = new CustomerRef($base);
$id1 = $cust1->getId();
$ref = $cust1->getRef();
// plugin2 code on event 2
$cust2 = new CustomerEmail($base);
$id2 = $cust2->getId();
$cust2->setEmail('any@email.com');
// data saver (base impl.)
$repo->save('Customer', $cust2);
declare(strict_types=1);
interface CustomerInterface
{
public function getId(): int;
public function setId(int $id);
}
interface CustomerPluginInterface extends CustomerInterface
{
public function getCustomer();
}
// This is base object.
class Customer implements CustomerInterface
{
private $id;
private $ref;
private $email;
public function getId(): int
{
return $this->id;
}
public function setId(int $id)
{
$this->id = $id;
return $this;
}
public function getRef(): string
{
return $this->ref;
}
public function setRef(string $ref)
{
$this->ref = $ref;
return $this;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email)
{
$this->email = $email;
return $this;
}
}
// This is extended customer (plugin 1).
class CustomerRef implements CustomerPluginInterface
{
private $customer;
public function __construct(Customer $customer)
{
$this->customer = $customer;
}
public function getId(): int
{
return $this->customer->getId();
}
public function setId(int $id)
{
$this->customer->setId($id);
return $this;
}
public function getRef(): string
{
return $this->customer->getRef();
}
public function setRef(string $ref)
{
$this->customer->setRef($ref);
return $this;
}
public function getCustomer()
{
return $this->customer;
}
}
// This is extended customer (plugin 2).
class CustomerEmail implements CustomerPluginInterface
{
private $customer;
public function __construct(Customer $customer)
{
$this->customer = $customer;
}
public function getId(): int
{
return $this->customer->getId();
}
public function setId(int $id)
{
$this->customer->setId($id);
return $this;
}
public function getEmail(): string
{
return $this->customer->getEmail();
}
public function setEmail(string $email)
{
$this->customer->setEmail($email);
return $this;
}
public function getCustomer()
{
return $this->customer;
}
}
$base = $repo->load('Customer', 21);
// plugin1 code on event 1
$cust1 = new CustomerRef($base);
$id1 = $cust1->getId();
$ref = $cust1->getRef();
// plugin2 code on event 2
$cust2 = new CustomerEmail($base);
$id2 = $cust2->getId();
$cust2->setEmail('any@email.com');
// data saver (base impl.)
$repo->save('Customer', $cust2->getCustomer()); // <-- различие только здесь
// но можно и так, ибо изменив $cust2 мы изменили $base
//$repo->save('Customer', $base);
$base = $repo->load('Customer', 21);
$base->setId('foo');
$base->setFieldNotSetInDb('bar');
$repo->save('Customer', $base);
class Customer implements CustomerInterface
{
private $id;
public function getId(): int
{
return $this->id;
}
public function setId(int $id)
{
$this->id = $id;
return $this;
}
}
Разработчик базового функционала заложил только $id. Атрибут $ref заложил разработчик плагина 1, атрибут $email — разработчик плагина 2. Разработчики друг с другом не знакомы.
Обмен данными как объектами самое правильное. Ещё более правильное, если функционал будет в одном формате. Этот формат JSON, а выбирать нужный контент, нужно запросами.
$customer_ref->getCustomer()->getId();
$customer_ref->getRef();
$customer->getCustomerRef()->getRef();
$customer->getId();
// $customer->getRef(); // no work
$customer_ref->getId();
$customer_ref->getRef();
Из вас всю информацию приходится прям клещами вытягивать.
В таком случае нужно сразу сказать что дополнительные поля из плагинов не должны быть обязательными для заполнения, иначе они могут поломать базовый функционал.
Создание отдельной таблицы с дополнительными полями необходимыми для плагина и сделать связь с базовой таблицей OneToOne.
Нет возможности получить сущность плагина из базовой сущности.
Нужно создавать еще одну таблицу и делать JOIN для выбора дополнительных данных
$customer->getId();
$customer->getRef(); // works, but autocomplete doesn't
$customer_ref->getId();
$customer_ref->getRef();
надо отметь что отсутствие возможности получить поля плагина из базового класса не всегда является минусом. В некоторых случаях это позволяет избежать ошибок.
Ну я же не знаю, чего не знаете вы :)
Почему же «слегка не так»?Создание отдельной таблицы с дополнительными полями необходимыми для плагина и сделать связь с базовой таблицей OneToOne.Отличная идея! Без преувеличения (просто в Magento это сделано слегка не так).


Да. Причем попытаться уложиться в несколько экранов.
Слышал
Если DTO не зависит от типа переносимых данных — да, это он и есть.
SOA — не понимаю, о чем это.
мухи (код обработчиков) отдельно, а котлеты (обрабатываемые данные) — отдельно.
А если «мухи» и/или «котлеты» будут в виде типизируемых объектов — так оно даже и лучше.
Да. Теперь мои мысли приобрели форму ссылки. Это удобно.
В некотором смысле этот подход является следствием функциональной парадигмы.
В таком случае «DTO — как паттерн» можно использовать в качестве универсального контейнера данных;
Знаю
Чтобы не смешивать данные и обрабатывающий их код — как в DTO.
Если типизируемый объект позволит добавлять к себе любую структуру данных и выдавать ее — то он и будет являться универсальным контейнером.
Я решал задачу уменьшения сложности при производстве ПО.
Функциональная парадигма делает акцент на вычислениях, я же делаю ацент на данных.
В таком случае DTO нельзя использовать как «универсальный контейнер данных».
Не отличается, но дополняет — вводит расширяемость на уровне данных в «чётко определённые интерфейсы» SOA.
«Универсальный контейнер данных» — это более узкий термин, чем «любой способ обработки структурированных данных». [...] Это все равно спросить «Зачем вводить понятие 'функционального программирования', когда уже есть всем понятный термин 'программирование'».
В таком случае, типизируемый объект не может являться универсальным контейнером данных.
/**
* @method array getBaseCalcData()
* @method void setBaseCalcData(array $data)
*/
private $_baseCalcData;
public function getBaseCalcData() {
return $this->_baseCalcData;
}
public function setBaseCalcData(array $data) {
$this->_baseCalcData = $data;
}
А разве это не очевидно?
object[] BaseCalcData {get; set;}
object там только потому, что я не знаю, какой именно у вас тип под массивом).На основании названия.
Если DTO полностью удовлетворяет условиям, значит его можно использовать в качестве «универсального контейнера данных».
Если есть расширяемость на уровне данных, то значит в SOA уже используется концепция «универсального контейнера данных».
Если вы используете любой способ структурирования данных для формирования «посылки» с произвольным содержимым, то вы таким образом создаете «универсальный контейнер данных».
Сравнил. Это точно не PHP.
Я имел в виду не LISP, я имел в виду именно «функциональное программирование».
«Строго типизированный» DTO не удовлетворяет условию «распределенной разработки» — там по определению не может быть строгой типизации.
Практика «игнорирую то, чего не знаю» весьма хорошо сочетается с «универсальным контейнером».
То, что это ассоциативный массив никак не отменяет того, что он может использоваться в качестве «универсального контейнера данных».
Функциона́льное программи́рование — раздел дискретной математики и парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании).
Но я не могу сказать, какая разница, потому что я не понимаю, что вы написали.
BaseCalcData с типом «массив объектов».Вопросов по PHP у вас не возникло — я делаю вывод, что этот язык вам знаком. А раз знаком, значит вы можете изложить свои мысли на нем.
Я обратил ваше внимание на то, что мы говорим не про LISP, а именно про «функциональную парадигму» и в данном конкретном случае название вполне соответствует сути:
Потому что при распределенной разработке вполне возможен вариант, когда разработчики не могут договориться друг с другом просто в силу того, что они не знакомы друг с другом.
Дополнение по пункту 3: первых 4 плагина как раз и занимаются поиском подобных конфликтов.
Если вы не видите выигрыша, то вам не и стоит этого делать.
Ваш «семантически эквивалентный код» ни в коем разе не опровергает мой демонстрационный пример, что предложенный подход сокращает кол-во строк кода минимум в 2 раза для программ на PHP.
Если это невозможно сделать на PHP, то попробуйте изложить ваши мысли на Java/C/JavaScript/Python.
Т.е., вы утверждаете, что название «функциональная парадигма» не отражает сути?
Считайте, что я привел пример для случая, когда разработчик базового функционала ничего не знает о разработчиках плагинов, а разработчики плагинов знают только базовый функционал и не могут никаким образом повлиять ни на него, ни друг на друга.
PHP вполне позволяет строго использовать типизацию
вы все еще не поняли, что даже самая расстрогая типизация не дает возможности создавать изолированным командам разработчиков общее приложение, даже наоборот — мешает.
Универсальный контейнер данных