cyberwani / attribute-manager
Standalone render-attributes helper (Elementor-compatible API) for building HTML attribute strings safely.
Requires
- php: >=7.4
This package is auto-updated.
Last update: 2025-09-25 15:05:23 UTC
README
A tiny, standalone PHP library that mirrors Elementor’s render attributes API so you can safely build HTML attribute strings anywhere (not just in WordPress/Elementor).
- Familiar API:
add_render_attribute()
,get_render_attribute_string()
, etc. - Safe escaping (
esc_attr
-equivalent). - Multi-value merging for
class
,rel
, and commonaria-*
list attributes. - Singleton handling for
id
(last value wins). - Simple singleton helper:
attribute_manager()
.
Installation
composer require cyberwani/attribute-manager:^2.0
Requires PHP 7.4+.
Composer autoloads the package via PSR-4 (Cyberwani\\RenderAttributes\\
) and loads a small helper from src/helpers.php
.
Quick Start
<?php // Always returns a NEW instance (no shared state). $am = attribute_manager(); $am->add_render_attribute('wrapper', [ 'id' => 'custom-widget-id', 'class' => ['custom-widget-wrapper-class', 'my-extra-class'], 'role' => 'region', 'aria-label' => 'Products', ]); $am->add_render_attribute('inner', [ 'class' => 'custom-widget-inner-class', 'data-custom' => 'custom-widget-information', ]); ?> <div <?php echo $am->get_render_attribute_string('wrapper'); ?>> <div <?php $am->print_render_attribute_string('inner'); ?>></div> </div>
Example output:
<div id="custom-widget-id" class="custom-widget-wrapper-class my-extra-class" role="region" aria-label="Products"> <div class="custom-widget-inner-class" data-custom="custom-widget-information"></div> </div>
Using in a WordPress Shortcode (safe with multiple renders)
Ensures attributes from one shortcode do not leak to another because each call creates a fresh manager.
function my_shortcode_cb($atts = [], $content = null) { $atts = shortcode_atts([ 'element_id' => '', 'css' => '', ], $atts, 'my_shortcode'); // NEW instance per render — prevents attribute leakage between shortcodes $am = attribute_manager(); $am->add_render_attribute('wrapper', [ 'id' => $atts['element_id'], 'class' => [ 'cdhl_panorama-viewer_wrapper', vc_shortcode_custom_css_class($atts['css'], ' '), ], 'role' => 'abc', 'aria-label' => 'xyz', ]); // Add additional classes on the fly $am->add_render_attribute('wrapper', 'class', 'xxxxxx'); ob_start(); ?> <div <?php echo $am->get_render_attribute_string('wrapper'); ?>> <?php cardealer_vehicle_360_view_images_html($vehicle_360_images); ?> </div> <?php return ob_get_clean(); } add_shortcode('my_shortcode', 'my_shortcode_cb');
API
All methods live on Cyberwani\RenderAttributes\AttributeManager
. Use the helper attribute_manager(?string $id = null): AttributeManager
to obtain a new instance for each render/scope.
add_render_attribute(string $element, string|array $key, mixed $value = null, bool $overwrite = false): self
- Add or merge attributes for a logical element alias (e.g.,
wrapper
,inner
). $key
can be a string or an associative array ofname => value
.$value = null
enables a boolean attribute (e.g.,disabled
→disabled
).- Merge behavior:
class
,rel
,aria-labelledby
,aria-describedby
are multi-value keys that merge and de-duplicate.id
is a singleton key; last value wins (overwrite semantics).
- Set
$overwrite = true
to replace instead of merging.
get_render_attributes(string $element): array
- Returns a normalized raw array of attributes for inspection/debugging, e.g.:
[ 'id' => 'custom-widget-id', 'class' => ['a', 'b', 'c'], 'role' => 'region', ]
set_render_attribute(string $element, string|array $key, mixed $value = null): self
- Replace attribute(s) completely (no merge).
$value = null
turns the attribute into a boolean (present without value).
remove_render_attribute(string $element, ?string $key = null, $value = null): self
- Remove everything for
$element
when$key
isnull
. - Remove a single attribute when
$value
isnull
. - Remove a specific value from multi-value attributes when
$value
is provided.
get_render_attribute_string(string $element): string
- Returns an escaped, space-separated attribute string suitable for inlining in HTML, e.g.:
id="x" class="a b" aria-label="Products"
print_render_attribute_string(string $element): void
- Echoes the same string (shortcut for templates).
Escaping & Safety
- Values are escaped with
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
(WordPress-likeesc_attr
). null
/false
values are ignored.true
becomes a boolean attribute (disabled
,required
, etc.).- Attribute names are sanitized (alphanumeric plus
: . _ -
); invalid characters are stripped.
Behavior Details
- Singleton attributes:
id
is treated specially—latest value wins. - Multi-value attributes:
class
,rel
,aria-labelledby
,aria-describedby
are merged and de-duplicated. - Boolean attributes:
$am->add_render_attribute('button', 'disabled'); // or value = null // -> disabled
- Removing values:
$am->add_render_attribute('el', 'class', ['btn', 'btn-primary', 'mt-2']); $am->remove_render_attribute('el', 'class', 'mt-2'); // leaves btn btn-primary
Helper
This package ships a tiny helper:
$am = attribute_manager(); // returns a NEW AttributeManager $am = attribute_manager('debug'); // optional ID, handy in debugging/logging
If you prefer not to use the helper, instantiate directly:
$am = new \Cyberwani\RenderAttributes\AttributeManager();
Upgrading from 1.x
- Breaking: The singleton has been removed in v2.
- Do this now:
- Replace
Cyberwani\RenderAttributes\AttributeManager::instance()
withattribute_manager()
. - Expect a fresh manager each time you call
attribute_manager()
; keep a local$am
if you need to reuse it within a render.
- Replace
Versioning
We follow Semantic Versioning.
- Backward-compatible changes →
MINOR
. - Breaking changes →
MAJOR
.
Changelog (excerpt)
[2.0.0] — 2025-09-25
Breaking
- Each call to
attribute_manager()
now returns a newAttributeManager
instance (no singleton). - Removed
AttributeManager::instance()
.
Fixed
- Attribute/class leakage when rendering the same shortcode multiple times.
Migration
- Replace
Cyberwani\RenderAttributes\AttributeManager::instance()
withattribute_manager()
. - If you previously relied on shared global state, keep a single
$am
instance inside your scope and pass it where needed.
(See full details in CHANGELOG.md
.)
Testing (optional)
composer require --dev pestphp/pest phpunit/phpunit vendor/bin/pest
Security
If you discover a security vulnerability, please contact the maintainer privately before public disclosure.
License
Released under the MIT License.