たまたま、タスクはうまく行われなくてはなりませんが、迅速に行わなければなりません。お金、パートナー、そしてビジネスにとって非常に重要な他の多くのものがそれに結びついています。その結果、どこかで何かを考えなかった場所、どこかでそれを見逃した場所、一般的にはすべて速度のために何かをハードコーディングしました。そして、すべてがうまくいくように、すべてが機能しますが...
しばらくすると、機能を拡張する必要があることがわかりましたが、それを行うのは難しく、十分な柔軟性がありません。もちろん、設定については開発者に頼ります。そしてもちろん、それは他の仕事から気をそらし、時間を無駄にしているという感覚を残しません。
だから私はそのような状況にありました。昔々、彼らはすぐに電子メールマーケティングシステムとの統合を書き留め、次に「ユーザーがこれを行った場合は、ここにこれを書き留める必要があります」などのタスクを書き留めました。ビジネスプロセスの可視性の欠如のために、それらの交差が発生し、データが互いに上書きされ、間違ったものが記録されました。
この状況からどうやって抜け出したのかをお話ししたいと思います。
システムのある時点で、何かまたは誰かがイベントを生成します。たとえば、ユーザーがプロファイルデータを登録、更新、購入したなどです。
. , , 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\Services\Events;
use App\Services\FieldMapper;
use App\Interfaces\Services\Filter;
use App\Interfaces\Actions\ActionPool;
use App\Interfaces\Events\SystemEvent;
use App\Interfaces\Events\EventProcessor as IEventProcessor;
/**
* event processor
* @package App\Services\Events
*/
class EventProcessor implements IEventProcessor
{
/** @var ActionPool */
private $actionPool;
/** @var Filter */
private $filter;
/** @var FieldMapper */
private $fieldMapper;
public function __construct(ActionPool $actionPool, Filter $filter, FieldMapper $fieldMapper)
{
$this->setActionPool($actionPool)->setFilter($filter)->setFieldMapper($fieldMapper);
}
/**
* Process system event
*
* @param SystemEvent $event
* @param array $settings
*/
public function process(SystemEvent $event, array $settings = []): void
{
collect($settings)->each(function (array $action) use ($event) {
$eventData = $event->toArray();
$conditions = $action['conditions'] ?? [];
foreach ($conditions as $index => $condition) {
if (isset($condition['not']) && $condition['not'] == 1) {
$conditions[$index]['condition'] .= '|!';
}
}
if ($this->getFilter()->check($conditions, $eventData)) {
foreach ($action['actions'] as $actionData) {
if (($actionO = $this->getActionPool()->create($actionData['action'])) !== null) {
try {
$freeInput = $actionData['free_input'] ?? [];
foreach ($freeInput as $key => $data) {
unset($freeInput[$key]);
$freeInput[$data['id']] = $data;
}
$data = $this->getFieldMapper()->map(array_merge($actionData['input'] ?? [], $freeInput), $eventData);
foreach ($data as $key => $val) {
$data[$key] = $this->prepareValue($val);
}
$data['event_fields'] = $eventData;
$actionO->run($data);
} catch (\Throwable $ex) {
\Log::critical($ex);
}
} else {
\Log::info('System', ['Can\'t create action ' . $actionData['action']]);
}
}
}
});
}
/**
* Prepare constants
*
* @param $value
*
* @return false|string
*/
protected function prepareValue($value)
{
if ($value === 'current_date') {
return date('Y-m-d H:i:s');
}
return $value;
}
/**
* @return ActionPool
*/
public function getActionPool(): ActionPool
{
return $this->actionPool;
}
/**
* @param ActionPool $actionPool
*
* @return $this
*/
public function setActionPool(ActionPool $actionPool): self
{
$this->actionPool = $actionPool;
return $this;
}
/**
* @return Filter
*/
public function getFilter(): Filter
{
return $this->filter;
}
/**
* @param Filter $filter
*
* @return $this
*/
public function setFilter(Filter $filter): self
{
$this->filter = $filter;
return $this;
}
/**
* @return FieldMapper
*/
public function getFieldMapper(): FieldMapper
{
return $this->fieldMapper;
}
/**
* @param FieldMapper $fieldMapper
*
* @return $this
*/
public function setFieldMapper(FieldMapper $fieldMapper): self
{
$this->fieldMapper = $fieldMapper;
return $this;
}
}
プロセスメソッドは、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;
}