Type-specific core framework for SalesRender macros plugins
salesrender/plugin-core-macros is the core package for building MACROS type plugins on the SalesRender platform.
Macros plugins perform bulk operations on orders -- they process orders in batches, enabling mass data export, import,
field manipulation, status changes, and other automated workflows.
The key distinction of macros plugins from other types is batch processing: the ability to iterate over a set of orders (defined by filters, sort, and pagination) and apply operations to each one, tracking progress, errors, and results.
This package extends the base salesrender/plugin-core by:
- Adding batch HTTP routes (prepare, configure, run, status) via
WebAppFactory - Adding batch CLI commands (queue processing, batch handling) via
ConsoleAppFactory - Enabling CORS support by default in
WebAppFactory - Requiring the developer to implement
BatchHandlerInterfacefor order processing logic
composer require salesrender/plugin-core-macrosRequirements:
- PHP >= 7.4
- Extensions:
ext-json
Dependencies:
salesrender/plugin-core^0.4.1salesrender/plugin-component-purpose^2.0
plugin-core-macros provides two factory classes in the SalesRender\Plugin\Core\Macros\Factories namespace:
namespace SalesRender\Plugin\Core\Macros\Factories;
class WebAppFactory extends \SalesRender\Plugin\Core\Factories\WebAppFactory
{
public function build(): App
{
$this
->addCors()
->addBatchActions();
return parent::build();
}
}The macros WebAppFactory automatically adds two feature sets before building:
- CORS support (
addCors()) -- enables cross-origin requests for all routes - Batch actions (
addBatchActions()) -- registers all batch-related HTTP routes
The addBatchActions() method (defined in the parent WebAppFactory) registers the following routes:
| Method | Path | Action | Description |
|---|---|---|---|
POST |
/protected/batch/prepare |
BatchPrepareAction |
Creates a new batch with filters, sort, and arguments |
GET |
/protected/forms/batch/{number} |
GetBatchFormAction |
Returns batch step form (1-10) |
PUT |
/protected/data/batch/{number} |
PutBatchOptionsAction |
Saves batch step options |
POST |
/protected/batch/run |
BatchRunAction |
Starts batch execution |
GET |
/process |
ProcessAction |
Returns batch process status |
GET |
/protected/autocomplete/{name} |
AutocompleteAction |
Autocomplete suggestions |
GET |
/protected/preview/table/{name} |
TablePreviewAction |
Table preview data |
GET |
/protected/preview/markdown/{name} |
MarkdownPreviewAction |
Markdown preview |
namespace SalesRender\Plugin\Core\Macros\Factories;
class ConsoleAppFactory extends \SalesRender\Plugin\Core\Factories\ConsoleAppFactory
{
public function build(): Application
{
$this->addBatchCommands();
return parent::build();
}
}The macros ConsoleAppFactory adds batch processing commands via addBatchCommands():
| Command | Class | Description |
|---|---|---|
batch:queue |
BatchQueueCommand |
Picks up queued batches and spawns handler processes |
batch:handle |
BatchHandleCommand |
Executes the batch handler for a specific batch |
These commands are also automatically registered as cron tasks (every minute) by the base ConsoleAppFactory when
a BatchContainer handler is configured.
The batch processing lifecycle in a macros plugin follows these steps:
1. PREPARE POST /protected/batch/prepare -- Create batch with FSP (Filters, Sort, Pagination)
2. CONFIGURE GET /protected/forms/batch/{n} -- Get configuration form for step N
PUT /protected/data/batch/{n} -- Submit configuration for step N
(repeat for steps 1-10 as needed)
3. RUN POST /protected/batch/run -- Start processing
4. PROCESS (async via CLI) -- BatchHandler iterates orders
5. STATUS GET /process?id={id} -- Check progress
BatchHandlerInterface-- the core processing logic that iterates over orders and performs operations- Settings Form -- a class extending
Formfor plugin configuration - Batch Option Forms -- one or more
Formclasses for batch step configuration (up to 10 steps) - bootstrap.php -- configuration file wiring everything together
mkdir my-macros-plugin && cd my-macros-plugin
composer init --name="myvendor/my-macros-plugin" --type="project"
composer require salesrender/plugin-core-macros
composer require salesrender/plugin-component-purposeSet up PSR-4 autoloading in composer.json:
{
"autoload": {
"psr-4": {
"MyVendor\\Plugin\\Instance\\Macros\\": "src/"
}
}
}Create the project directory structure:
mkdir -p src/Components src/Forms db public runtimeCreate bootstrap.php in the project root:
<?php
use SalesRender\Plugin\Components\Batch\BatchContainer;
use SalesRender\Plugin\Components\Db\Components\Connector;
use SalesRender\Plugin\Components\Info\Developer;
use SalesRender\Plugin\Components\Info\Info;
use SalesRender\Plugin\Components\Info\PluginType;
use SalesRender\Plugin\Components\Purpose\MacrosPluginClass;
use SalesRender\Plugin\Components\Purpose\PluginEntity;
use SalesRender\Plugin\Components\Purpose\PluginPurpose;
use SalesRender\Plugin\Components\Settings\Settings;
use SalesRender\Plugin\Components\Translations\Translator;
use SalesRender\Plugin\Core\Actions\Upload\LocalUploadAction;
use SalesRender\Plugin\Core\Actions\Upload\UploadersContainer;
use Medoo\Medoo;
use MyVendor\Plugin\Instance\Macros\Components\MyHandler;
use MyVendor\Plugin\Instance\Macros\Forms\BatchOptionsForm;
use MyVendor\Plugin\Instance\Macros\Forms\SettingsForm;
use XAKEPEHOK\Path\Path;
require_once __DIR__ . '/vendor/autoload.php';
# 1. Configure DB
Connector::config(new Medoo([
'database_type' => 'sqlite',
'database_file' => Path::root()->down('db/database.db')
]));
# 2. Set plugin default language
Translator::config('ru_RU');
# 3. Set permitted file extensions and max sizes (optional)
UploadersContainer::addDefaultUploader(new LocalUploadAction([
'jpg' => 1 * 1024 * 1024,
'png' => 2 * 1024 * 1024,
]));
# 4. Configure info about plugin
Info::config(
new PluginType(PluginType::MACROS),
fn() => Translator::get('info', 'My Macros Plugin'),
fn() => Translator::get('info', 'Description of my macros plugin'),
new PluginPurpose(
new MacrosPluginClass(MacrosPluginClass::CLASS_HANDLER),
new PluginEntity(PluginEntity::ENTITY_ORDER)
),
new Developer(
'My Company',
'support@example.com',
'example.com',
)
);
# 5. Configure settings form
Settings::setForm(fn($context) => new SettingsForm($context));
# 6. Configure batch forms and handler
BatchContainer::config(
function (int $number) {
switch ($number) {
case 1: return new BatchOptionsForm();
default: return null;
}
},
new MyHandler()
);Key points:
- Plugin type must be
PluginType::MACROS - The fourth argument to
Info::config()is aPluginPurposeobject specifying the plugin class and entity BatchContainer::config()accepts a callable returning batch step forms and aBatchHandlerInterfaceimplementation- Available
MacrosPluginClassvalues:CLASS_EXPORTER,CLASS_HANDLER,CLASS_IMPORTER - Available
PluginEntityvalues:ENTITY_ORDER,ENTITY_UNSPECIFIED
Create src/Components/MyHandler.php:
<?php
namespace MyVendor\Plugin\Instance\Macros\Components;
use SalesRender\Plugin\Components\Batch\Batch;
use SalesRender\Plugin\Components\Batch\BatchHandlerInterface;
use SalesRender\Plugin\Components\Batch\Process\Error;
use SalesRender\Plugin\Components\Batch\Process\Process;
use SalesRender\Plugin\Components\Settings\Settings;
class MyHandler implements BatchHandlerInterface
{
public function __invoke(Process $process, Batch $batch)
{
// Guard: ensure settings are valid
Settings::guardIntegrity();
// Read settings
$settings = Settings::find()->getData();
// Read batch options from step 1
$options = $batch->getOptions(1);
// Get API client and FSP (Filters, Sort, Pagination) from the batch
$apiClient = $batch->getApiClient();
$fsp = $batch->getFsp();
// Create an iterator to fetch orders from the API
$iterator = new OrdersFetcherIterator(
['id', 'createdAt', 'status.id'],
$apiClient,
$fsp
);
// Initialize the process with the total count
$process->initialize(count($iterator));
// Process each order
foreach ($iterator as $order) {
try {
// Your processing logic here
$this->processOrder($order, $settings, $options);
$process->handle();
} catch (\Throwable $e) {
$process->addError(new Error($e->getMessage(), $order['id']));
}
$process->save();
}
// Optional: post-processing phase
$process->setState(Process::STATE_POST_PROCESSING);
$process->save();
// Finish with result (true = success, false = error, string = download URL)
$process->finish(true);
$process->save();
}
private function processOrder(array $order, $settings, $options): void
{
// Implement your order processing logic
}
}The BatchHandlerInterface has a single method:
interface BatchHandlerInterface
{
public function __invoke(Process $process, Batch $batch);
}Process lifecycle methods:
| Method | Description |
|---|---|
$process->initialize(?int $count) |
Set total order count (null for unknown). Transitions state to STATE_PROCESSING |
$process->handle() |
Increment the handled counter |
$process->skip() |
Increment the skipped counter |
$process->addError(Error $error) |
Record an error (increments failed counter, stores last 20 errors) |
$process->setState(string $state) |
Set process state (STATE_PROCESSING, STATE_POST_PROCESSING) |
$process->finish($value) |
Mark as complete. true = success, false = error, string = result URL |
$process->terminate(Error $error) |
Terminate with a fatal error |
$process->save() |
Persist current state to database |
Process states: STATE_SCHEDULED -> STATE_PROCESSING -> STATE_POST_PROCESSING -> STATE_ENDED
Batch provides:
| Method | Description |
|---|---|
$batch->getApiClient() |
Returns the API client for querying SalesRender |
$batch->getFsp() |
Returns Filters, Sort, Pagination configuration |
$batch->getOptions(int $number) |
Returns form data for batch step N |
Create public/index.php:
<?php
use SalesRender\Plugin\Core\Macros\Factories\WebAppFactory;
require_once __DIR__ . '/../vendor/autoload.php';
$factory = new WebAppFactory();
$application = $factory->build();
$application->run();Note: Unlike integration plugins, macros plugins typically do not need to add custom routes. The WebAppFactory
automatically registers all necessary batch routes.
Create console.php:
#!/usr/bin/env php
<?php
use SalesRender\Plugin\Core\Macros\Factories\ConsoleAppFactory;
require __DIR__ . '/vendor/autoload.php';
$factory = new ConsoleAppFactory();
$application = $factory->build();
$application->run();Create src/Forms/SettingsForm.php:
<?php
namespace MyVendor\Plugin\Instance\Macros\Forms;
use SalesRender\Plugin\Components\Form\FieldDefinitions\BooleanDefinition;
use SalesRender\Plugin\Components\Form\FieldDefinitions\ListOfEnum\Limit;
use SalesRender\Plugin\Components\Form\FieldDefinitions\ListOfEnum\Values\StaticValues;
use SalesRender\Plugin\Components\Form\FieldDefinitions\ListOfEnumDefinition;
use SalesRender\Plugin\Components\Form\FieldDefinitions\StringDefinition;
use SalesRender\Plugin\Components\Form\FieldGroup;
use SalesRender\Plugin\Components\Form\Form;
use SalesRender\Plugin\Components\Translations\Translator;
class SettingsForm extends Form
{
public function __construct(array $context)
{
$this->setContext($context);
parent::__construct(
Translator::get('settings', 'Settings'),
Translator::get('settings', 'Configure your macros plugin'),
[
'group_1' => new FieldGroup(
Translator::get('settings', 'General'),
null,
[
'fields' => new ListOfEnumDefinition(
Translator::get('settings', 'Columns'),
Translator::get('settings', 'Select data columns'),
function ($values) {
$errors = [];
if (!is_array($values) || count($values) < 1) {
$errors[] = Translator::get('errors', 'Select at least one field');
}
return $errors;
},
new StaticValues([
'id' => ['title' => 'ID', 'group' => 'Order'],
'createdAt' => ['title' => 'Created At', 'group' => 'Order'],
]),
new Limit(1, null),
['id', 'createdAt']
),
]
),
],
Translator::get('settings', 'Save')
);
}
}Create src/Forms/BatchOptionsForm.php:
<?php
namespace MyVendor\Plugin\Instance\Macros\Forms;
use SalesRender\Plugin\Components\Form\FieldDefinitions\IntegerDefinition;
use SalesRender\Plugin\Components\Form\FieldDefinitions\BooleanDefinition;
use SalesRender\Plugin\Components\Form\FieldGroup;
use SalesRender\Plugin\Components\Form\Form;
use SalesRender\Plugin\Components\Translations\Translator;
class BatchOptionsForm extends Form
{
public function __construct()
{
parent::__construct(
Translator::get('batch_options', 'Processing Options'),
Translator::get('batch_options', 'Configure processing parameters'),
[
'options' => new FieldGroup(
Translator::get('batch_options', 'Options'),
null,
[
'skipErrors' => new BooleanDefinition(
Translator::get('batch_options', 'Skip errors'),
Translator::get('batch_options', 'Continue processing on error'),
function ($value) {
$errors = [];
if (!is_bool($value)) {
$errors[] = 'Value must be boolean';
}
return $errors;
},
false
),
]
),
],
Translator::get('batch_options', 'Start')
);
}
}Batch option forms are displayed before running the batch (steps 1-10). Return null from the BatchContainer::config
callable for steps that do not require configuration.
Create .env:
LV_PLUGIN_DEBUG=1
LV_PLUGIN_PHP_BINARY=/usr/bin/php
LV_PLUGIN_QUEUE_LIMIT=1
LV_PLUGIN_SELF_URI=https://my-plugin.example.com/# Create the database
php console.php db:create-tables
# Set up cron (required for batch processing)
# Add to crontab:
# * * * * * /usr/bin/php /path/to/my-plugin/console.php cron:runThe cron system is essential for macros plugins because batch processing runs asynchronously via the
batch:queue and batch:handle commands, which are automatically scheduled every minute.
These routes are added by the macros WebAppFactory in addition to the base plugin-core routes:
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/protected/batch/prepare |
JWT | Create a new batch with FSP. Returns 409 if batch already exists |
GET |
/protected/forms/batch/{number} |
JWT | Get batch step form (1-10). Returns 425 if previous step incomplete |
PUT |
/protected/data/batch/{number} |
JWT | Save batch step options. Returns 400 on validation errors |
POST |
/protected/batch/run |
JWT | Start batch execution (sync in debug mode, async otherwise) |
GET |
/process |
No | Get batch process status by ?id={processId} |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/info |
No | Plugin metadata |
PUT |
/registration |
No | Plugin registration |
GET |
/robots.txt |
No | Robots exclusion |
GET |
/protected/forms/settings |
JWT | Settings form definition |
GET |
/protected/data/settings |
JWT | Current settings data |
PUT |
/protected/data/settings |
JWT | Save settings data |
GET |
/protected/autocomplete/{name} |
JWT | Autocomplete handler |
GET |
/protected/preview/table/{name} |
JWT | Table preview |
GET |
/protected/preview/markdown/{name} |
JWT | Markdown preview |
POST |
/protected/upload |
JWT | File upload |
CORS headers are enabled on all routes by default.
| Command | Description |
|---|---|
batch:queue |
Picks up queued batches and spawns handler processes (runs every minute via cron) |
batch:handle |
Executes the BatchHandlerInterface for a specific batch |
| Command | Description |
|---|---|
cron:run |
Runs all scheduled cron tasks |
directory:clean |
Cleans temporary directories |
db:create-tables |
Creates database tables |
db:clean-tables |
Cleans old database records |
lang:add |
Adds a new language |
lang:update |
Updates translations |
specialRequest:queue |
Processes special request queue |
specialRequest:handle |
Handles a special request |
The macros ConsoleAppFactory automatically registers these cron tasks (every minute):
batch:queue-- polls for queued batches and spawns handler processesspecialRequest:queue-- polls for queued special requests
namespace SalesRender\Plugin\Components\Batch;
interface BatchHandlerInterface
{
public function __invoke(Process $process, Batch $batch);
}This is the central interface every macros plugin must implement. The handler receives:
Process $process-- tracks progress, errors, and result of the batch operationBatch $batch-- provides access to the API client, FSP (Filters/Sort/Pagination), and batch step options
The handler must:
- Call
$process->initialize($count)to set the total order count - Iterate over orders, calling
$process->handle(),$process->skip(), or$process->addError()for each - Call
$process->save()after processing each order to persist progress - Call
$process->finish($result)when done
namespace SalesRender\Plugin\Components\Batch\Process;
class Process extends Model implements JsonSerializable
{
const STATE_SCHEDULED = 'scheduled';
const STATE_PROCESSING = 'processing';
const STATE_POST_PROCESSING = 'post_processing';
const STATE_ENDED = 'ended';
public function initialize(?int $init): void;
public function handle(): void;
public function skip(): void;
public function addError(Error $error): void;
public function setState(string $state): void;
public function finish($value): void; // bool|int|string
public function terminate(Error $error): void;
public function save(): void;
public function getHandledCount(): int;
public function getSkippedCount(): int;
public function getFailedCount(): int;
public function getState(): string;
public function getResult();
}namespace SalesRender\Plugin\Components\Batch;
class Batch extends Model
{
public function getApiClient(): ApiClient;
public function getFsp(): FSP;
public function getOptions(int $number): Dot; // Returns batch step options as Dot notation object
}namespace SalesRender\Plugin\Components\Batch;
final class BatchContainer
{
public static function config(callable $forms, BatchHandlerInterface $handler): void;
public static function getForm(int $number, array $context = []): ?Form;
public static function getHandler(): BatchHandlerInterface;
}namespace SalesRender\Plugin\Components\Purpose;
class PluginPurpose implements JsonSerializable
{
public function __construct(PluginClass $class, PluginEntity $entity);
}MacrosPluginClass values:
MacrosPluginClass::CLASS_EXPORTER-- plugin exports order dataMacrosPluginClass::CLASS_HANDLER-- plugin processes/modifies ordersMacrosPluginClass::CLASS_IMPORTER-- plugin imports external data into orders
PluginEntity values:
PluginEntity::ENTITY_ORDER-- plugin operates on ordersPluginEntity::ENTITY_UNSPECIFIED-- entity type is unspecified
namespace SalesRender\Plugin\Core\Actions;
interface ActionInterface
{
public function __invoke(ServerRequest $request, Response $response, array $args): Response;
}Used for custom HTTP action handlers (not typically needed in macros plugins, but available).
The plugin-macros-example is a comprehensive example demonstrating all features of a macros plugin.
plugin-macros-example/
bootstrap.php # Full configuration with batch, autocomplete, previews
console.php # CLI entry point
example.env # Environment variable template
composer.json
db/
public/
index.php # Web entry point
icon.png # Plugin icon
iframe/ # Static files for IFrame fields
runtime/
translations/ # Translation files (en_US.json, ru_RU.json)
src/
Autocomplete/
Example.php # AutocompleteInterface implementation
ExampleWithDeps.php # Autocomplete with dependencies
Components/
Columns.php # Column definitions for order data
ExampleHandler.php # BatchHandlerInterface implementation
FieldParser.php # Field parsing utility
OrdersFetcherIterator.php # Order fetching via API
Forms/
SettingsForm.php # Plugin settings form
ResponseOptionsForm.php # Batch step 1 form
SecondResponseOptionsForm.php # Batch step 2 form
PreviewOptionsForm.php # Batch step 3 (preview) form
MarkdownPreviewAction/
MarkdownPreviewExample.php # Markdown preview implementation
TablePreviewAction/
TablePreviewExample.php # Table preview implementation
TablePreviewExcel.php # Excel table preview
tests/ # HTTP test files
From ExampleHandler.php:
class ExampleHandler implements BatchHandlerInterface
{
public function __invoke(Process $process, Batch $batch)
{
Settings::guardIntegrity();
// Read batch step options
$delay = $batch->getOptions(1)->get('response_options.delay');
// Create order iterator
$iterator = new OrdersFetcherIterator(
Columns::getQueryColumns($fields),
$batch->getApiClient(),
$batch->getFsp()
);
// Initialize with total count
$process->initialize(count($iterator));
// Process each order
foreach ($iterator as $field) {
$process->handle(); // or skip() or addError()
$process->save();
sleep($delay);
}
// Post-processing phase
$process->setState(Process::STATE_POST_PROCESSING);
$process->save();
// Finish (true = success, string = URL, false = error)
$process->finish(true);
$process->save();
}
}| Package | Version | Purpose |
|---|---|---|
salesrender/plugin-core |
^0.4.1 | Base plugin framework (Slim 4 + Symfony Console) |
salesrender/plugin-component-purpose |
^2.0 | Plugin purpose/class/entity definitions |
All transitive dependencies (Slim, Symfony Console, Medoo, batch components, etc.) come from plugin-core.
- plugin-core -- Base framework for all SalesRender plugins
- plugin-core-integration -- Core for integration plugins
- plugin-core-logistic -- Core for logistic plugins
- plugin-core-chat -- Core for chat plugins
- plugin-core-pbx -- Core for PBX plugins
- plugin-macros-example -- Example macros plugin
- plugin-component-batch -- Batch processing components
- plugin-component-purpose -- Plugin purpose definitions
- plugin-component-form -- Form definitions and field types
- plugin-component-settings -- Settings storage
- plugin-component-db -- Database abstraction
- plugin-component-api-client -- SalesRender API client