Every rich-text field rebuilds the workflow placeholder list: one query per attribute
Bug report
Issue Description
Every rich-text field in the admin panel costs one query per attribute in the installation,
because <x-admin::tinymce> rebuilds the workflow placeholder list on each render.
Where the queries come from.
packages/Webkul/Automation/src/Helpers/Entity/AbstractEntity.php:63 — getAttributes()
walks every attribute of an entity type and reads $attribute->options, a lazy hasMany on
attribute_options, so one SELECT per attribute:
foreach ($this->attributeRepository->findByField('entity_type', $entityType) as $attribute) {
...
$options = $attribute->options; // one query, per attributeEntity::getEmailTemplatePlaceholders() (Entity.php:89-101) runs that for all eight
entities in config('workflows.trigger_entities'), and the TinyMCE component calls it on the
first line of its template
(packages/Webkul/Admin/src/Resources/views/components/tinymce/index.blade.php:1):
@php($placeholders = app('\Webkul\Automation\Helpers\Entity')->getEmailTemplatePlaceholders())so the whole walk repeats for every rich-text field rendered on the page — lead views, person views, the mail composer, email templates, webhooks and workflows.
Measured (MySQL 8.0, queries per getEmailTemplatePlaceholders() call):
| queries | |
|---|---|
| stock installation | 19 |
| after adding 10 custom lead attributes | 29 |
One extra query per attribute — and adding attributes is what the attribute system is for, so the cost is lowest on a fresh install and grows with real use.
Preconditions
- Krayin
master@2c12091(Core::KRAYIN_VERSION2.2.6), also present in v2.2.5. - PHP 8.3.33, Laravel 12, MySQL 8.0.
Steps to reproduce
- In
php artisan tinker:
DB::enableQueryLog();
app(\Webkul\Automation\Helpers\Entity::class)->getEmailTemplatePlaceholders();
count(DB::getQueryLog()); // 19 on a fresh install- Add ten custom attributes under Settings → Attributes (any entity type, type
select). - Repeat step 1: the count is now 29 — one more query per attribute added.
- Open any admin screen with a rich-text field and read Laravel Debugbar's Queries tab:
the same
select * from attribute_options where attribute_id = ?series appears once per editor on the page.
Expected result
- Building the placeholder list costs a constant, small number of queries, and does not repeat for each editor on a page.
Eager-loading the options in the one place that reads them is enough — measured 10 → 2 on
the leads entity type alone:
foreach ($this->attributeRepository->with('options')->findByField('entity_type', $entityType) as $attribute) {Since the placeholder list is identical for every editor on a page, memoising
getEmailTemplatePlaceholders() per request would remove the repetition as well.
Actual result
Each rendered rich-text field issues one query per attribute across all eight trigger entities, and the total rises by one for every attribute an admin configures.
This may be the mechanism behind #980 ("Create WorkFlow Query Way Too Much!"), which was closed as not reproducible on an empty dataset — the cost scales with configured attributes, and a fresh install has almost none.
How this was found
Found with query-guard — a PHPUnit extension
that traces the SQL a test run produces, fingerprints every query by call site and shape,
and flags a call site when the same-shaped query runs 3+ times with differing bound values
inside one test (batched IN (...) lookups are excluded, since those are the cure rather
than the disease).
The Pest suite could not surface this on its own, since these screens are covered by the Playwright suite rather than by PHPUnit. So the numbers above come from driving admin screens through Laravel's HTTP kernel — real route, middleware, controller and Blade — and then measuring the helper directly with the number of configured attributes varied.
You do not need that tool to check any of this: the reproduction above uses nothing but
DB::enableQueryLog() in Tinker, and Laravel Debugbar's Queries tab shows the same series
in the browser.
Source: krayin/laravel-crm