您知道,发生时,该任务必须做得不好,但要迅速完成,因为 金钱,合作伙伴和其他对业务非常重要的事物都与金钱紧密相关。结果,通常是为了提高速度,他们在某个地方没有考虑任何事情,在某个地方错过了某些事情,然后对某些事情进行了硬编码。而且,就像,一切都很好,一切正常,但是...
一段时间后,事实证明需要扩展功能,但是很难做到,没有足够的灵活性。对于设置,他们当然要向开发人员求助。并且,当然,它分散了其他任务的注意力,并且不会留下浪费时间的感觉。
所以我有这种情况。曾几何时,他们迅速写下了与电子邮件营销系统的集成,然后执行诸如“如果用户这样做,则需要在此处写下来”之类的任务。由于缺乏业务流程的可见性,因此发生了交叉,数据相互覆盖,记录了错误的内容。
我想告诉你我们如何摆脱这种情况。
在系统中的某个时刻,某物或某人会生成一个事件。例如,用户已注册,更新了个人资料数据,进行了购买等。
. , , 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;
}