[Laravel, PHP, Проектирование и рефакторинг] Подсистема событий как способ избавиться от задач по «допилу»
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Знаете, как бывает, задачу надо сделать не хорошо, а быстро, т.к. на нее завязаны деньги, партнеры и много всего другого очень важного для бизнеса. В итоге где-то что-то не продумали, где-то упустили, что-то захардкодили, в общем, все ради скорости. И, вроде, все хорошо, все работает, но…
Через какое-то время оказывается, что функционал нужно расширять, а сделать это сложно, не хватает гибкости. За настройками, конечно, обращаются к разработчикам. И, конечно же, это отвлекает от других задач и не покидает ощущение, что время потрачено зря.
Вот и у меня возникла такая ситуация. Когда-то по-быстрому запилили интеграцию с системой e-mail-маркетинга, а потом посыпались задачи по типу «если пользователь сделал это, необходимо вот это записать вот сюда». Из-за отсутствия наглядности бизнес-процессов возникало их пересечение, данные затирали друг друга, записывалось не то.
Хочу рассказать, как вышли из этой ситуации.
В какой-то момент в системе что-то или кто-то генерирует событие. Например, пользователь зарегистрировался, обновил данные профиля, совершил покупку и т.п.
Это событие нужно поймать и обработать. Например, отправить письмо, передать данные в CRM или какую-то другую систему. Обработчиков может быть много и их количество будет увеличиваться со временем.
Необходимо связать событие и обработчики. Запускать их нужно как безусловно, так и по некоему условию. Например, если пользователю 20 лет, то ему отправляем письмо одного вида, а если 60, то другого.
Разработка ведется на PHP на Laravel. В этом фреймворке уже есть события и обработчики, на их основе и построена подсистема.
оригинал
Обрабатывать все возможные существующие события в системе не целесообразно, будем перехватывать только события, реализующие специальный интерфейс. Согласно ему, каждое событие должно сообщать, какие данные несёт в себе и иметь свой уникальный идентификатор.
<?php App\Interfaces\Events
use Illuminate\Contracts\Support\Arrayable;
/**
* System event
* @package App\Interfaces\Events
*/
interface SystemEvent extends Arrayable
{
/**
* Get event id
*
* @return string
*/
public static function getId(): string;
/**
* Event name
*
* @return string
*/
public static function getName(): string;
/**
* Available params
*
* @return array
*/
public static function getAvailableParams(): array;
/**
* Get param by name
*
* @param string $name
*
* @return mixed
*/
public function getParam(string $name);
}
Ещё есть пул доступных событий. В нем регистрируются те события, которые являются системными и имеют какое-то значение для бизнес-процессов.
<?php namespace App\Interfaces\Events;
/**
* Interface for event pool
* @package App\Interfaces\Events
*/
interface EventsPool
{
/**
* Register event
*
* @param string $event
*
* @return mixed
*/
public function register(string $event): self;
/**
* Get events list
*
* @return array
*/
public function getAvailableEvents(): array;
/**
* @param string $alias
*
* @param array $params
*
* @return mixed
*/
public function create(string $alias, array $params = []);
}
Обработчик событий это просто класс, имеющий определённый интерфейс. И он, как и событие, сообщает, какие данные может принимать, что получается на выходе, имеет название и ID.
<?php namespace App\Interfaces\Actions;
/**
* Interface for system action
* @package App\Interfaces\Actions
*/
interface Action
{
/**
* Get ID
*
* @return string
*/
public static function getId(): string;
/**
* Get name
*
* @return string
*/
public static function getName(): string;
/**
* Available input params
*
* @return array
*/
public static function getAvailableInput(): array;
/**
* Available output params
*
* @return array
*/
public static function getAvailableOutput(): array;
/**
* Run action
*
* @param array $params
*
* @return void
*/
public function run(array $params): void;
}
Обработчики так же регистрируются в реестре с таким же интерфейсом как у пула событий.
Рассмотрим gui настройки связи событие-обработчик. У меня он реализован с использованием knockout.js, но это не принципиально.
Как вы видите, есть блок в котором настраиваются условия запуска обработчика. Первая колонка – параметр из события, затем идёт условие и значение, с которым будет сравнение.
В настройке обработчика так же три основных колонки. Первая – параметр из обработчика. В него нужно передать параметр из события(это вторая колонка). Параметр события можно не задавать, значение может быть константой. Например, в случае регистрации по e-mail передаётся 0, а в случае регистрации через соц.сеть передаётся 1, или какие-то человекопонятные значения.
В самом начале говорил, что все началось с интеграции с системой email- маркетинга Sendsay. В момент создания сущности в нашей системе, должна создаваться так называемая «анкета» на стороне Sendsay. При создании, в неё не передаются пользовательские данные, все статично. Это тот случай, когда нужно задать произвольные значения. Добавляем строку, вбиваем название поля в анкете, а в значение тип поля.
Связь настроили, посмотрим на главный обработчик событий.
<?php namespace App\Interfaces\Events;
/**
* Interface for event processor
* @package App\Interfaces\Events
*/
interface EventProcessor
{
/**
* Process system event
*
* @param SystemEvent $event
* @param array $settings
*/
public function process(SystemEvent $event, array $settings = []): void;
}
<?php namespace App\Interfaces\Events;
/**
* Interface for event processor
* @package App\Interfaces\Events
*/
interface EventProcessor
{
/**
* Process system event
*
* @param SystemEvent $event
* @param array $settings
*/
public function process(SystemEvent $event, array $settings = []): void;
}
Метод process будем вызывать в SystemEventListener.
<?php namespace App\Listeners;
use App\Interfaces\Events\SystemEvent;
use App\Interfaces\Events\EventProcessor;
use App\Models\EventSettings;
use Illuminate\Support\Collection;
class SystemEventListener
{
/** @var EventProcessor */
private $eventProcessor;
public function __construct(EventProcessor $eventProcessor)
{
$this->setEventProcessor($eventProcessor);
}
public function handle(SystemEvent $event): void
{
EventSettings::query()->where('is_active', true)->where('event_id', $event::getId())->chunk(10, function (Collection $collection) use ($event) {
$collection->each(function (EventSettings $model) use ($event) {
$this->getEventProcessor()->process($event, $model->settings);
});
});
}
/**
* @return EventProcessor
*/
public function getEventProcessor(): EventProcessor
{
return $this->eventProcessor;
}
/**
* @param EventProcessor $eventProcessor
*
* @return $this
*/
public function setEventProcessor(EventProcessor $eventProcessor): self
{
$this->eventProcessor = $eventProcessor;
return $this;
}
}
Регистрируем в провайдере:
<?php namespace App\Providers;
use App\Interfaces\Events\SystemEvent;
use App\Listeners\SystemEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
SystemEvent::class => [
SystemEventListener::class,
],
];
}
В итоге мы получили возможность настраивать события в системе через интерфейс. Включать и выключать обработчики без изменения кода. Новые модули системы без дополнительных вмешательств могут добавлять свои события и/или обработчики.
После небольшого обучения все это было передано пользователям админки, что высвободило дополнительное рабочее время.
И еще немного кода.
Проверка условий и маппинг параметров:
<?php namespace App\Interfaces\Services;
/**
* Interface for service to filter data (from HUB)
* @package App\Interfaces\Services
*/
interface Filter
{
public const CONDITION_EQUAL = '=';
public const CONDITION_MORE = '>';
public const CONDITION_LESS = '<';
public const CONDITION_NOT = '!';
public const CONDITION_BETWEEN = 'between';
public const CONDITION_IN = 'in';
public const CONDITION_EMPTY = 'empty';
/**
* Filter data
*
* @param array $filter
* @param array $data
*
* @return array
*/
public function filter(array $filter, array $data): array;
/**
* Check conditions
*
* @param array $conditions
* @param array $data
*
* @return bool
*/
public function check(array $conditions, array $data): bool;
}
<?php namespace App\Services;
use Illuminate\Support\Arr;
use App\Interfaces\Services\Filter as IFilter;
/**
* Service to filter data by conditions
* @package App\Services
*/
class Filter implements IFilter
{
/**
* Filter data
*
* @param array $filter
* @param array $data
*
* @return array
*/
public function filter(array $filter, array $data): array
{
if (!empty($filter)) {
foreach ($filter as $condition) {
$field = $condition['field'] ?? null;
if (empty($field)) {
continue;
}
$operation = $condition['operation'] ?? null;
$value1 = $condition['value1'] ?? null;
$value2 = $condition['value2'] ?? null;
$success = $condition['success'] ?? null;
$filterResult = $condition['result'] ?? null;
$value = Arr::get($data, $field, '');
if ($field !== null && $this->checkCondition($value, $operation, $value1, $value2)) {
return $success !== null ? $this->filter($success, $data) : $filterResult;
}
}
}
return [];
}
/**
* Check condition
*
* @param $value
* @param $condition
* @param $value1
* @param $value2
*
* @return bool
*/
protected function checkCondition($value, $condition, $value1, $value2): bool
{
$result = false;
$value = \is_string($value) ? mb_strtolower($value) : $value;
$value1 = \is_string($value1) ? mb_strtolower($value1) : $value1;
if ($value2 !== null) {
$value2 = \is_string($value2) ? mb_strtolower($value2) : $value2;
}
$conditions = explode('|', $condition);
$invert = \in_array(self::CONDITION_NOT, $conditions);
$conditions = array_filter($conditions, function ($item) {
return $item !== self::CONDITION_NOT;
});
$condition = implode('|', $conditions);
switch ($condition) {
case self::CONDITION_EQUAL:
$result = ($value == $value1);
break;
case self::CONDITION_IN:
$result = \in_array($value, (array)$value1);
break;
case self::CONDITION_LESS:
$result = ($value < $value1);
break;
case self::CONDITION_MORE:
$result = ($value > $value1);
break;
case self::CONDITION_MORE . '|' . self::CONDITION_EQUAL:
case self::CONDITION_EQUAL . '|' . self::CONDITION_MORE:
$result = ($value >= $value1);
break;
case self::CONDITION_LESS . '|' . self::CONDITION_EQUAL:
case self::CONDITION_EQUAL . '|' . self::CONDITION_LESS:
$result = ($value <= $value1);
break;
case self::CONDITION_BETWEEN:
$result = (($value >= $value1) && ($value <= $value2));
break;
case self::CONDITION_EMPTY:
$result = empty($value);
break;
}
return $invert ? !$result : $result;
}
/**
* Check conditions
*
* @param array $conditions
* @param array $data
*
* @return bool
*/
public function check(array $conditions, array $data): bool
{
$result = true;
if (!empty($conditions)) {
foreach ($conditions as $condition) {
$field = $condition['param'] ?? null;
if (empty($field)) {
continue;
}
$operation = $condition['condition'] ?? null;
$value1 = $condition['value'] ?? null;
$value2 = $condition['value2'] ?? null;
$value = Arr::get($data, $field, '');
$result &= $this->checkCondition($value, $operation, $value1, $value2);
}
}
return $result;
}
}
<?php namespace App\Interfaces\Services;
/**
* Interface for service to map params
* @package App\Interfaces\Services
*/
interface FieldMapper
{
/**
* Map
*
* @param array $map
* @param array $data
*
* @return array
*/
public function map(array $map, array $data): array;
}
<?php namespace App\Services;
use Illuminate\Support\Arr;
use App\Interfaces\Services\FieldMapper as IFieldMapper;
/**
* Params/fields mapper (by HUB)
* @package App\Services
*/
class FieldMapper implements IFieldMapper
{
/**
* Map
*
* @param array $map
* @param array $data
*
* @return array
*/
public function map(array $map, array $data): array
{
$result = [];
foreach ($map as $from => $to) {
$to = (array)$to;
if (!empty($to['param']) && ($value = Arr::get($data, $to['param'])) !== null) {
Arr::set($result, $from, $value);
} elseif ($to['value'] !== '') {
Arr::set($result, $from, Arr::get($data, $to['value'], isset($to['value_as_param']) && $to['value_as_param'] ? '' : $to['value']));
}
}
return $result;
}
===========
Источник:
habr.com
===========
Похожие новости:
- [PHP] Порядок вычисления в PHP (перевод)
- [Анализ и проектирование систем, Программирование, Проектирование и рефакторинг, Управление разработкой] Методика проектирования архитектурных слоев на основе анемичной модели и DDD
- [Flask, Python, Проектирование и рефакторинг] Flask + Dependency Injector — руководство по применению dependency injection
- [PHP] PHP Internals News Эпизод #38: предзагрузка и WeakMaps (перевод)
- [Анализ и проектирование систем, Микросервисы, Проектирование и рефакторинг, Управление разработкой] От монолита к микросервисам: ускорили банковские релизы в 15 раз
- [PHP, Системное администрирование] Apache & Nginx. Связаны одной цепью (2 часть)
- [Программирование, Проектирование и рефакторинг, Совершенный код] Поговорим о код-ревью
- [PHP] Hire PHP Developers: Cost & Procedure
- [UML Design, Анализ и проектирование систем, Программирование, Разработка веб-сайтов] UML для самых маленьких: диаграмма классов
- [PHP] Получение видео из Tik Tok без водяного знака
Теги для поиска: #_laravel, #_php, #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_arhitektura_prilozhenij (архитектура приложений), #_php, #_laravel, #_laravel, #_php, #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:49
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Знаете, как бывает, задачу надо сделать не хорошо, а быстро, т.к. на нее завязаны деньги, партнеры и много всего другого очень важного для бизнеса. В итоге где-то что-то не продумали, где-то упустили, что-то захардкодили, в общем, все ради скорости. И, вроде, все хорошо, все работает, но… Через какое-то время оказывается, что функционал нужно расширять, а сделать это сложно, не хватает гибкости. За настройками, конечно, обращаются к разработчикам. И, конечно же, это отвлекает от других задач и не покидает ощущение, что время потрачено зря. Вот и у меня возникла такая ситуация. Когда-то по-быстрому запилили интеграцию с системой e-mail-маркетинга, а потом посыпались задачи по типу «если пользователь сделал это, необходимо вот это записать вот сюда». Из-за отсутствия наглядности бизнес-процессов возникало их пересечение, данные затирали друг друга, записывалось не то. Хочу рассказать, как вышли из этой ситуации. В какой-то момент в системе что-то или кто-то генерирует событие. Например, пользователь зарегистрировался, обновил данные профиля, совершил покупку и т.п. Это событие нужно поймать и обработать. Например, отправить письмо, передать данные в CRM или какую-то другую систему. Обработчиков может быть много и их количество будет увеличиваться со временем. Необходимо связать событие и обработчики. Запускать их нужно как безусловно, так и по некоему условию. Например, если пользователю 20 лет, то ему отправляем письмо одного вида, а если 60, то другого. Разработка ведется на PHP на Laravel. В этом фреймворке уже есть события и обработчики, на их основе и построена подсистема. оригинал Обрабатывать все возможные существующие события в системе не целесообразно, будем перехватывать только события, реализующие специальный интерфейс. Согласно ему, каждое событие должно сообщать, какие данные несёт в себе и иметь свой уникальный идентификатор. <?php App\Interfaces\Events
use Illuminate\Contracts\Support\Arrayable; /** * System event * @package App\Interfaces\Events */ interface SystemEvent extends Arrayable { /** * Get event id * * @return string */ public static function getId(): string; /** * Event name * * @return string */ public static function getName(): string; /** * Available params * * @return array */ public static function getAvailableParams(): array; /** * Get param by name * * @param string $name * * @return mixed */ public function getParam(string $name); } Ещё есть пул доступных событий. В нем регистрируются те события, которые являются системными и имеют какое-то значение для бизнес-процессов. <?php namespace App\Interfaces\Events;
/** * Interface for event pool * @package App\Interfaces\Events */ interface EventsPool { /** * Register event * * @param string $event * * @return mixed */ public function register(string $event): self; /** * Get events list * * @return array */ public function getAvailableEvents(): array; /** * @param string $alias * * @param array $params * * @return mixed */ public function create(string $alias, array $params = []); } Обработчик событий это просто класс, имеющий определённый интерфейс. И он, как и событие, сообщает, какие данные может принимать, что получается на выходе, имеет название и ID. <?php namespace App\Interfaces\Actions;
/** * Interface for system action * @package App\Interfaces\Actions */ interface Action { /** * Get ID * * @return string */ public static function getId(): string; /** * Get name * * @return string */ public static function getName(): string; /** * Available input params * * @return array */ public static function getAvailableInput(): array; /** * Available output params * * @return array */ public static function getAvailableOutput(): array; /** * Run action * * @param array $params * * @return void */ public function run(array $params): void; } Обработчики так же регистрируются в реестре с таким же интерфейсом как у пула событий. Рассмотрим gui настройки связи событие-обработчик. У меня он реализован с использованием knockout.js, но это не принципиально. Как вы видите, есть блок в котором настраиваются условия запуска обработчика. Первая колонка – параметр из события, затем идёт условие и значение, с которым будет сравнение. В настройке обработчика так же три основных колонки. Первая – параметр из обработчика. В него нужно передать параметр из события(это вторая колонка). Параметр события можно не задавать, значение может быть константой. Например, в случае регистрации по e-mail передаётся 0, а в случае регистрации через соц.сеть передаётся 1, или какие-то человекопонятные значения. В самом начале говорил, что все началось с интеграции с системой email- маркетинга Sendsay. В момент создания сущности в нашей системе, должна создаваться так называемая «анкета» на стороне Sendsay. При создании, в неё не передаются пользовательские данные, все статично. Это тот случай, когда нужно задать произвольные значения. Добавляем строку, вбиваем название поля в анкете, а в значение тип поля. Связь настроили, посмотрим на главный обработчик событий. <?php namespace App\Interfaces\Events;
/** * Interface for event processor * @package App\Interfaces\Events */ interface EventProcessor { /** * Process system event * * @param SystemEvent $event * @param array $settings */ public function process(SystemEvent $event, array $settings = []): void; } <?php namespace App\Interfaces\Events;
/** * Interface for event processor * @package App\Interfaces\Events */ interface EventProcessor { /** * Process system event * * @param SystemEvent $event * @param array $settings */ public function process(SystemEvent $event, array $settings = []): void; } Метод process будем вызывать в SystemEventListener. <?php namespace App\Listeners;
use App\Interfaces\Events\SystemEvent; use App\Interfaces\Events\EventProcessor; use App\Models\EventSettings; use Illuminate\Support\Collection; class SystemEventListener { /** @var EventProcessor */ private $eventProcessor; public function __construct(EventProcessor $eventProcessor) { $this->setEventProcessor($eventProcessor); } public function handle(SystemEvent $event): void { EventSettings::query()->where('is_active', true)->where('event_id', $event::getId())->chunk(10, function (Collection $collection) use ($event) { $collection->each(function (EventSettings $model) use ($event) { $this->getEventProcessor()->process($event, $model->settings); }); }); } /** * @return EventProcessor */ public function getEventProcessor(): EventProcessor { return $this->eventProcessor; } /** * @param EventProcessor $eventProcessor * * @return $this */ public function setEventProcessor(EventProcessor $eventProcessor): self { $this->eventProcessor = $eventProcessor; return $this; } } Регистрируем в провайдере: <?php namespace App\Providers;
use App\Interfaces\Events\SystemEvent; use App\Listeners\SystemEventListener; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ SystemEvent::class => [ SystemEventListener::class, ], ]; } В итоге мы получили возможность настраивать события в системе через интерфейс. Включать и выключать обработчики без изменения кода. Новые модули системы без дополнительных вмешательств могут добавлять свои события и/или обработчики. После небольшого обучения все это было передано пользователям админки, что высвободило дополнительное рабочее время. И еще немного кода. Проверка условий и маппинг параметров: <?php namespace App\Interfaces\Services;
/** * Interface for service to filter data (from HUB) * @package App\Interfaces\Services */ interface Filter { public const CONDITION_EQUAL = '='; public const CONDITION_MORE = '>'; public const CONDITION_LESS = '<'; public const CONDITION_NOT = '!'; public const CONDITION_BETWEEN = 'between'; public const CONDITION_IN = 'in'; public const CONDITION_EMPTY = 'empty'; /** * Filter data * * @param array $filter * @param array $data * * @return array */ public function filter(array $filter, array $data): array; /** * Check conditions * * @param array $conditions * @param array $data * * @return bool */ public function check(array $conditions, array $data): bool; } <?php namespace App\Services;
use Illuminate\Support\Arr; use App\Interfaces\Services\Filter as IFilter; /** * Service to filter data by conditions * @package App\Services */ class Filter implements IFilter { /** * Filter data * * @param array $filter * @param array $data * * @return array */ public function filter(array $filter, array $data): array { if (!empty($filter)) { foreach ($filter as $condition) { $field = $condition['field'] ?? null; if (empty($field)) { continue; } $operation = $condition['operation'] ?? null; $value1 = $condition['value1'] ?? null; $value2 = $condition['value2'] ?? null; $success = $condition['success'] ?? null; $filterResult = $condition['result'] ?? null; $value = Arr::get($data, $field, ''); if ($field !== null && $this->checkCondition($value, $operation, $value1, $value2)) { return $success !== null ? $this->filter($success, $data) : $filterResult; } } } return []; } /** * Check condition * * @param $value * @param $condition * @param $value1 * @param $value2 * * @return bool */ protected function checkCondition($value, $condition, $value1, $value2): bool { $result = false; $value = \is_string($value) ? mb_strtolower($value) : $value; $value1 = \is_string($value1) ? mb_strtolower($value1) : $value1; if ($value2 !== null) { $value2 = \is_string($value2) ? mb_strtolower($value2) : $value2; } $conditions = explode('|', $condition); $invert = \in_array(self::CONDITION_NOT, $conditions); $conditions = array_filter($conditions, function ($item) { return $item !== self::CONDITION_NOT; }); $condition = implode('|', $conditions); switch ($condition) { case self::CONDITION_EQUAL: $result = ($value == $value1); break; case self::CONDITION_IN: $result = \in_array($value, (array)$value1); break; case self::CONDITION_LESS: $result = ($value < $value1); break; case self::CONDITION_MORE: $result = ($value > $value1); break; case self::CONDITION_MORE . '|' . self::CONDITION_EQUAL: case self::CONDITION_EQUAL . '|' . self::CONDITION_MORE: $result = ($value >= $value1); break; case self::CONDITION_LESS . '|' . self::CONDITION_EQUAL: case self::CONDITION_EQUAL . '|' . self::CONDITION_LESS: $result = ($value <= $value1); break; case self::CONDITION_BETWEEN: $result = (($value >= $value1) && ($value <= $value2)); break; case self::CONDITION_EMPTY: $result = empty($value); break; } return $invert ? !$result : $result; } /** * Check conditions * * @param array $conditions * @param array $data * * @return bool */ public function check(array $conditions, array $data): bool { $result = true; if (!empty($conditions)) { foreach ($conditions as $condition) { $field = $condition['param'] ?? null; if (empty($field)) { continue; } $operation = $condition['condition'] ?? null; $value1 = $condition['value'] ?? null; $value2 = $condition['value2'] ?? null; $value = Arr::get($data, $field, ''); $result &= $this->checkCondition($value, $operation, $value1, $value2); } } return $result; } } <?php namespace App\Interfaces\Services;
/** * Interface for service to map params * @package App\Interfaces\Services */ interface FieldMapper { /** * Map * * @param array $map * @param array $data * * @return array */ public function map(array $map, array $data): array; } <?php namespace App\Services;
use Illuminate\Support\Arr; use App\Interfaces\Services\FieldMapper as IFieldMapper; /** * Params/fields mapper (by HUB) * @package App\Services */ class FieldMapper implements IFieldMapper { /** * Map * * @param array $map * @param array $data * * @return array */ public function map(array $map, array $data): array { $result = []; foreach ($map as $from => $to) { $to = (array)$to; if (!empty($to['param']) && ($value = Arr::get($data, $to['param'])) !== null) { Arr::set($result, $from, $value); } elseif ($to['value'] !== '') { Arr::set($result, $from, Arr::get($data, $to['value'], isset($to['value_as_param']) && $to['value_as_param'] ? '' : $to['value'])); } } return $result; } =========== Источник: habr.com =========== Похожие новости:
Проектирование и рефакторинг ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:49
Часовой пояс: UTC + 5