Subsistem peristiwa sebagai cara untuk menyingkirkan tugas dengan "menyelesaikan"

Anda tahu, kebetulan, tugas itu harus diselesaikan tidak dengan baik, tetapi cepat, karena uang, mitra, dan banyak hal lain yang sangat penting untuk bisnis terikat padanya. Akibatnya, di suatu tempat mereka tidak memikirkan sesuatu, di suatu tempat mereka melewatkannya, mereka membuat kode keras sesuatu, secara umum, semuanya demi kecepatan. Dan, seperti, semuanya baik-baik saja, semuanya bekerja, tapi ...



Setelah beberapa waktu, ternyata fungsinya perlu diperluas, tetapi sulit dilakukan, fleksibilitas tidak cukup. Untuk pengaturannya, tentu saja, beralih ke pengembangnya. Dan, tentu saja, itu mengalihkan perhatian dari tugas-tugas lain dan tidak meninggalkan perasaan bahwa waktu terbuang percuma.



Jadi saya mengalami situasi seperti itu. Suatu ketika, mereka dengan cepat menuliskan integrasi dengan sistem pemasaran email, dan kemudian tugas seperti "jika pengguna melakukan ini, Anda perlu menuliskannya di sini". Karena kurangnya visibilitas proses bisnis, terjadi persimpangan mereka, datanya ditimpa satu sama lain, hal yang salah dicatat.



Subsistem acara



Saya ingin memberi tahu Anda bagaimana kami keluar dari situasi ini.



Di beberapa titik dalam sistem, sesuatu atau seseorang menghasilkan peristiwa. Misalnya, pengguna telah mendaftar, memperbarui data profil, melakukan pembelian, dll.



. , , CRM - . .



. , . , 20 , , 60, .



PHP Laravel. , .



Skema subsistem acara

, , . , , .



<?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;
    }
}


Metode proses akan dipanggil di 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; 
    } 
} 


Kami mendaftar dengan penyedia:



<?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, 
        ], 
 
    ]; 
}


Hasilnya, kami mendapat kesempatan untuk mengonfigurasi acara di sistem melalui antarmuka. Aktifkan dan nonaktifkan penangan tanpa mengubah kode. Modul baru dari sistem dapat menambahkan acara dan / atau penangannya sendiri tanpa intervensi tambahan.



Setelah sedikit pelatihan, semua ini ditransfer ke pengguna panel admin, yang membebaskan waktu kerja tambahan.



Dan beberapa kode lagi.



Pemeriksaan kondisi dan pemetaan parameter:



<?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; 
    } 



All Articles