Markup Class
The main class for programmatically building and manipulating HTML structures.
Table of Contents
- Overview
- Constructor
- Metadata
- CSS Class Management
- HTML Attribute Management
- Children Management
- Slot System
- Conditional and Iterative Methods
- Search and Query
- Text Extraction
- Rendering Methods
- Internal Accessors
- Template Placeholders
- Practical Examples
- Extending the Markup Class
- Performance Notes
- See Also
Overview
The Markup class is the core of the library. It provides a fluent and chainable API for building complex HTML structures with support for:
- HTML Wrappers - Templates with placeholders for content
- CSS Classes - Dynamic class management
- HTML Attributes - Complete attribute control
- Children - Composition of nested elements
- Slots - Named placeholder system
- Search - Query the generated DOM tree
- Dual rendering mode - Buffer or streaming
Constructor
public function __construct(
string $wrapper = '',
array $wrapperClass = [],
array $wrapperAttributes = [],
string $childrenWrapper = '',
array $children = []
)
Parameters
| Parameter | Type | Description |
|---|---|---|
$wrapper | string | HTML template with %children% placeholder |
$wrapperClass | array | Array of CSS classes |
$wrapperAttributes | array | Associative array of HTML attributes |
$childrenWrapper | string | HTML template with %child% placeholder to wrap each child |
$children | array | Array of initial children |
Examples
use MaxPertici\Markup\Markup;
// Simple creation
$div = new Markup('<div>%children%</div>');
// With classes and attributes
$card = new Markup(
wrapper: '<div class="%classes%" %attributes%>%children%</div>',
wrapperClass: ['card', 'shadow'],
wrapperAttributes: ['id' => 'main-card', 'data-type' => 'product']
);
// With children wrapper
$list = new Markup(
wrapper: '<ul>%children%</ul>',
childrenWrapper: '<li>%child%</li>',
children: ['Item 1', 'Item 2', 'Item 3']
);
Metadata
slug()
Sets or retrieves the element’s slug identifier.
slug(?string $slug = null): self|string|null
Examples:
// Set a slug
$markup->slug('hero-section');
// Get the slug
$slug = $markup->slug(); // 'hero-section'
// Chainable
$markup->slug('header')->addClass('primary');
description()
Sets or retrieves an element description.
description(?string $description = null): self|string|null
Examples:
$markup->description('Main section with call-to-action');
$desc = $markup->description(); // Retrieves the description
CSS Class Management
addClass()
Adds one or more CSS classes to the wrapper.
addClass(string|array ...$classes): self
Examples:
// Single class
$markup->addClass('active');
// Multiple classes
$markup->addClass('btn', 'btn-primary', 'btn-lg');
// Array of classes
$markup->addClass(['container', 'mx-auto']);
// Mixed
$markup->addClass('card', ['shadow', 'rounded']);
removeClass()
Removes one or more CSS classes.
removeClass(string|array ...$classes): self
Examples:
$markup->removeClass('active');
$markup->removeClass('btn-primary', 'btn-lg');
$markup->removeClass(['shadow', 'rounded']);
hasClass()
Checks if a class exists.
hasClass(string $class): bool
Examples:
if ($markup->hasClass('active')) {
// Class exists
}
classes()
Retrieves or replaces all classes.
classes(?array $classes = null): self|array
Examples:
// Get classes
$classes = $markup->classes();
// Replace all classes
$markup->classes(['new-class', 'another-class']);
HTML Attribute Management
setAttribute()
Sets or removes an HTML attribute.
setAttribute(string $name, ?string $value): self
Examples:
// Set an attribute
$markup->setAttribute('id', 'main-content');
$markup->setAttribute('data-user-id', '123');
$markup->setAttribute('role', 'navigation');
// Remove an attribute (null value)
$markup->setAttribute('disabled', null);
removeAttribute()
Removes an HTML attribute.
removeAttribute(string $name): self
Examples:
$markup->removeAttribute('disabled');
$markup->removeAttribute('data-temp');
hasAttribute()
Checks if an attribute exists.
hasAttribute(string $name): bool
Examples:
if ($markup->hasAttribute('disabled')) {
// Attribute exists
}
getAttribute()
Retrieves an attribute value.
getAttribute(string $name): ?string
Examples:
$id = $markup->getAttribute('id');
$role = $markup->getAttribute('role');
attributes()
Retrieves or replaces all attributes.
attributes(?array $attributes = null): self|array
Examples:
// Get attributes
$attrs = $markup->attributes();
// Replace all attributes
$markup->attributes([
'id' => 'new-id',
'class' => 'new-class',
'data-value' => 'test'
]);
Children Management
children() / append()
Adds children to the element.
children(mixed ...$children): self
append(mixed ...$children): self // Alias
Children can be:
- Strings (HTML)
MarkupinstancesMarkupSlotinstances- Callables (anonymous functions)
- Arrays of elements
Examples:
// Strings
$markup->children('Simple text');
// Markup instances
$markup->children(
new Markup('<h1>%children%</h1>', children: ['Title']),
new Markup('<p>%children%</p>', children: ['Paragraph'])
);
// Mixed
$markup->children(
'Text',
new Markup('<div>%children%</div>'),
function() { echo 'From callback'; }
);
// Arrays
$markup->children(['Item 1', 'Item 2', 'Item 3']);
// Using append() alias
$markup->append('More content');
getChildren()
Retrieves the children array.
getChildren(): array
Examples:
$children = $markup->getChildren();
$count = count($children);
setChildren()
Completely replaces the children array.
setChildren(array $children): self
Examples:
$markup->setChildren([
'New child 1',
new Markup('<div>%children%</div>', children: ['New child 2'])
]);
orderChildren()
Reorganizes children with a callback function.
orderChildren(callable $callback): self
Examples:
// Reverse order
$markup->orderChildren(fn($children) => array_reverse($children));
// Sort by custom criteria
$markup->orderChildren(function($children) {
usort($children, function($a, $b) {
// Custom sorting logic
return 0;
});
return $children;
});
Slot System
Slots allow you to define named locations for content, similar to Vue.js or Laravel Blade.
slot()
Adds content to a named slot.
slot(string $name, mixed $items): self
Examples:
// Add content to a slot
$markup->slot('header', [
new Markup('<h1>%children%</h1>', children: ['My Site'])
]);
$markup->slot('content', [
'<p>Main content</p>',
new Markup('<div>%children%</div>', children: ['More content'])
]);
slots()
Retrieves declared slots.
slots(?array $names = null): array
Examples:
// All slots
$allSlots = $markup->slots();
// Specific slots
$selectedSlots = $markup->slots(['header', 'footer']);
getSlot()
Retrieves a specific slot by name.
getSlot(string $name): ?MarkupSlot
Examples:
$headerSlot = $markup->getSlot('header');
if ($headerSlot) {
// Slot exists
}
hasSlot()
Checks if a slot has been declared.
hasSlot(string $name): bool
Examples:
if ($markup->hasSlot('sidebar')) {
// Slot exists
}
isSlotFilled()
Checks if a slot has been filled with content.
isSlotFilled(string $name): bool
Examples:
if ($markup->isSlotFilled('header')) {
// Slot contains content
}
slotNames()
Retrieves the names of all declared slots.
slotNames(): array
Examples:
$names = $markup->slotNames();
// ['header', 'content', 'footer', 'sidebar']
filledSlotNames()
Retrieves the names of slots that have been filled.
filledSlotNames(): array
Examples:
$filled = $markup->filledSlotNames();
// ['header', 'content'] - filled slots only
getSlotsInfo()
Retrieves detailed information about all slots.
getSlotsInfo(): array
Examples:
$info = $markup->getSlotsInfo();
/*
[
'header' => [
'name' => 'header',
'description' => 'Page header',
'wrapper' => '<header>%slot%</header>',
'preserve' => false,
'filled' => true,
'items_count' => 2
],
...
]
*/
Conditional and Iterative Methods
when()
Executes a callback if a condition is true.
when(bool $condition, callable $callback): self
Examples:
$isAdmin = true;
$markup->when($isAdmin, function($m) {
$m->children('<div class="admin-panel">Admin Panel</div>');
});
// Chaining
$markup
->children('Content visible to all')
->when($isAdmin, fn($m) => $m->children('Admin content'))
->when($isLoggedIn, fn($m) => $m->addClass('authenticated'));
each()
Iterates over an array and executes a callback for each element.
each(array $items, callable $callback): self
Examples:
$users = [
['name' => 'Alice', 'email' => 'alice@example.com'],
['name' => 'Bob', 'email' => 'bob@example.com']
];
$list = new Markup('<ul>%children%</ul>');
$list->each($users, function($user, $index, $markup) {
$markup->children(
new Markup(
'<li>%children%</li>',
children: [$user['name'] . ' - ' . $user['email']]
)
);
});
Search and Query
find()
Creates a MarkupQueryBuilder instance for building fluent search queries.
find(): MarkupQueryBuilder
The query builder provides a chainable interface for searching elements. It returns a MarkupCollection of results.
Examples:
// Search by class
$elements = $markup->find()->class('active')->get();
// Search by tag
$divs = $markup->find()->tag('div')->get();
// Search by slug
$header = $markup->find()->slug('main-header')->first();
// Search by attribute
$withRole = $markup->find()->attribute('role', 'navigation')->get();
// CSS selector
$navLinks = $markup->find()->css('nav a')->get();
$activeItems = $markup->find()->css('li.active')->get();
// Multiple criteria (AND logic)
$results = $markup->find()
->class('card')
->hasAttribute('data-id')
->get();
// Custom search with where
$results = $markup->find()
->where(fn($m) => $m->hasClass('card') && $m->hasAttribute('data-id'))
->get();
// Get first result
$first = $markup->find()->class('active')->first();
// Count results
$count = $markup->find()->tag('div')->count();
// Check existence
$exists = $markup->find()->class('error')->exists();
// Collection transformations
$prices = $markup->find()
->class('product')
->get()
->map(fn($el) => $el->getAttribute('price'))
->filter(fn($price) => (float)$price > 50);
Note: For direct access to MarkupFinder methods (like findByClass(), findByTag(), etc.), you can create a MarkupFinder instance directly:
$finder = new \MaxPertici\Markup\MarkupFinder($markup);
$results = $finder->findByClass('active');
See MarkupQueryBuilder for the query builder API and MarkupFinder for the finder API.
Text Extraction
text()
Extracts plain text content from the markup and its children, ignoring all HTML tags.
text(bool $recursive = true, bool $includeSlots = true, bool $executeCallables = true): string
This method recursively traverses the markup tree and extracts all text content while:
- Including string children as-is
- Recursively extracting text from nested Markup instances
- Processing MarkupFlow items
- Optionally including slot content
- Optionally executing callables to capture their output
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$recursive | bool | true | Extract text from nested Markup children |
$includeSlots | bool | true | Include content from filled slots |
$executeCallables | bool | true | Execute callables and capture their output |
Examples:
// Simple text extraction
$markup = new Markup(
wrapper: '<div>%children%</div>',
children: ['Hello ', 'World!']
);
echo $markup->text(); // "Hello World!"
// With nested Markup
$link = new Markup('<a href="#">%children%</a>', children: ['Click here']);
$paragraph = new Markup(
'<p>%children%</p>',
children: ['Text before link, ', $link, ', text after.']
);
echo $paragraph->text(); // "Text before link, Click here, text after."
// With complex structure
$article = new Markup(
'<article>%children%</article>',
children: [
new Markup('<h2>%children%</h2>', children: ['Title']),
new Markup('<p>%children%</p>', children: [
'This is ',
new Markup('<strong>%children%</strong>', children: ['important']),
' content.'
])
]
);
echo $article->text(); // "TitleThis is important content."
// Non-recursive extraction (only direct children)
$text = $markup->text(recursive: false);
// Exclude slots
$text = $markup->text(includeSlots: false);
// Don't execute callables
$text = $markup->text(executeCallables: false);
Practical Use Cases
Meta Description Generation:
$content = new Markup(/* ... */);
$description = substr($content->text(), 0, 160);
echo '<meta name="description" content="' . htmlspecialchars($description) . '">';
Word Count:
$wordCount = str_word_count($markup->text());
echo "Reading time: " . ceil($wordCount / 200) . " minutes";
Search Indexing:
$searchableText = strtolower($markup->text());
$index[$documentId] = $searchableText;
Content Validation:
if (str_contains($markup->text(), $requiredKeyword)) {
echo "Content contains required keyword";
}
SEO Analysis:
$text = $markup->text();
$titleText = $title->text();
if (str_contains($text, $titleText)) {
echo "Title keyword appears in content ✓";
}
Rendering Methods
render()
Generates and returns the HTML as a string.
render(): string
Examples:
$html = $markup->render();
echo $html;
// Or directly
echo $markup->render();
// Store for later use
$cached = $markup->render();
file_put_contents('cached.html', $cached);
Use when:
- You need the HTML in a variable
- You want to manipulate the HTML before display
- You need to test or validate the HTML
- You’re returning HTML from a function
print()
Directly outputs the HTML (streaming mode).
print(): void
Examples:
// Direct output
$markup->print();
// In a template
<?php $header->print(); ?>
Use when:
- Better performance for large documents
- Reduced memory usage
- Direct output in templates
- Streaming responses
Internal Accessors
getWrapper()
Retrieves the element’s wrapper template.
getWrapper(): string
Examples:
$wrapper = $markup->getWrapper();
// '<div class="%classes%" %attributes%>%children%</div>'
This method is primarily used internally and by MarkupFinder to inspect elements.
Template Placeholders
In the Wrapper
| Placeholder | Description | Example |
|---|---|---|
%children% | Children location | <div>%children%</div> |
%classes% | CSS classes | <div class="%classes%"> |
%attributes% | HTML attributes | <div %attributes%> |
In the Children Wrapper
| Placeholder | Description | Example |
|---|---|---|
%child% | Each child individually | <li>%child%</li> |
In the Slot Wrapper
| Placeholder | Description | Example |
|---|---|---|
%slot% | Slot content | <header>%slot%</header> |
Practical Examples
Reusable Card Component
function createCard(string $title, string $content, ?string $imageUrl = null): Markup
{
$card = new Markup(
wrapper: '<div class="%classes%">%children%</div>',
wrapperClass: ['card', 'shadow-lg']
);
if ($imageUrl) {
$img = new Markup('<img %attributes% />');
$img->setAttribute('src', $imageUrl)
->setAttribute('alt', $title)
->addClass('card-img-top');
$card->children($img);
}
$body = new Markup(
wrapper: '<div class="card-body">%children%</div>',
children: [
new Markup('<h5 class="card-title">%children%</h5>', children: [$title]),
new Markup('<p class="card-text">%children%</p>', children: [$content])
]
);
$card->children($body);
return $card;
}
// Usage
$card = createCard('My Title', 'My content', '/image.jpg');
echo $card->render();
Layout with Slots
// Define the layout
$layout = new Markup('<div class="app">%children%</div>');
$layout->children(
new MarkupSlot('header', '<header class="app-header">%slot%</header>'),
new MarkupSlot('content', '<main class="app-content">%slot%</main>'),
new MarkupSlot('footer', '<footer class="app-footer">%slot%</footer>')
);
// Fill the slots
$layout
->slot('header', [
new Markup('<h1>%children%</h1>', children: ['My Application'])
])
->slot('content', [
new Markup('<p>%children%</p>', children: ['Main content'])
])
->slot('footer', [
'<p>© 2024 My Application</p>'
]);
$layout->print();
Dynamic List
$users = [
['id' => 1, 'name' => 'Alice', 'role' => 'Admin'],
['id' => 2, 'name' => 'Bob', 'role' => 'User'],
['id' => 3, 'name' => 'Charlie', 'role' => 'User']
];
$list = new Markup('<ul class="user-list">%children%</ul>');
$list->each($users, function($user, $index, $markup) {
$li = new Markup('<li class="%classes%">%children%</li>');
$li->setAttribute('data-user-id', (string)$user['id'])
->addClass('user-item')
->when($user['role'] === 'Admin', fn($m) => $m->addClass('admin'))
->children($user['name'] . ' - ' . $user['role']);
$markup->children($li);
});
echo $list->render();
Extending the Markup Class
The Markup class can be extended to create your own custom markup libraries with specialized components and behaviors. This allows you to:
- Create domain-specific markup libraries for your application or framework
- Encapsulate common patterns and components in reusable classes
- Add custom methods specific to your use case
- Pre-configure default behaviors for your components
- Build component libraries with consistent styling and structure
Basic Extension Example
namespace MyApp\Components;
use MaxPertici\Markup\Markup;
class Button extends Markup
{
public function __construct(string $text = '', string $type = 'button')
{
parent::__construct(
wrapper: '<button class="%classes%" %attributes%>%children%</button>',
wrapperClass: ['btn'],
children: [$text]
);
$this->setAttribute('type', $type);
}
public function primary(): self
{
return $this->addClass('btn-primary');
}
public function secondary(): self
{
return $this->addClass('btn-secondary');
}
public function large(): self
{
return $this->addClass('btn-lg');
}
public function small(): self
{
return $this->addClass('btn-sm');
}
}
// Usage
$btn = new Button('Click me');
$btn->primary()->large();
echo $btn->render();
// Output: <button class="btn btn-primary btn-lg" type="button">Click me</button>
Advanced Component Library Example
namespace MyApp\Components;
use MaxPertici\Markup\Markup;
use MaxPertici\Markup\MarkupSlot;
class Card extends Markup
{
public function __construct()
{
parent::__construct(
wrapper: '<div class="%classes%" %attributes%>%children%</div>',
wrapperClass: ['card']
);
// Define slots for structured content
$this->children(
new MarkupSlot('header', '<div class="card-header">%slot%</div>'),
new MarkupSlot('body', '<div class="card-body">%slot%</div>'),
new MarkupSlot('footer', '<div class="card-footer">%slot%</div>')
);
}
public function withShadow(): self
{
return $this->addClass('shadow-lg');
}
public function bordered(): self
{
return $this->addClass('border');
}
public function setHeader(mixed ...$content): self
{
return $this->slot('header', $content);
}
public function setBody(mixed ...$content): self
{
return $this->slot('body', $content);
}
public function setFooter(mixed ...$content): self
{
return $this->slot('footer', $content);
}
}
// Usage
$card = new Card();
$card->withShadow()
->bordered()
->setHeader('<h3>Card Title</h3>')
->setBody('<p>Card content goes here</p>')
->setFooter('<button class="btn">Action</button>');
echo $card->render();
Framework Integration Example
namespace MyFramework\UI;
use MaxPertici\Markup\Markup;
abstract class Component extends Markup
{
protected array $props = [];
public function __construct(array $props = [])
{
$this->props = $props;
$this->init();
parent::__construct();
$this->build();
}
/**
* Initialize component properties
*/
protected function init(): void
{
// Override in child classes
}
/**
* Build the component structure
*/
abstract protected function build(): void;
/**
* Get a prop value with default
*/
protected function prop(string $key, mixed $default = null): mixed
{
return $this->props[$key] ?? $default;
}
}
// Example component using the framework
class Alert extends Component
{
protected function init(): void
{
$this->wrapper = '<div class="%classes%" %attributes%>%children%</div>';
$this->wrapperClass = ['alert'];
}
protected function build(): void
{
$type = $this->prop('type', 'info');
$message = $this->prop('message', '');
$dismissible = $this->prop('dismissible', false);
$this->addClass("alert-{$type}")
->setAttribute('role', 'alert')
->children($message);
if ($dismissible) {
$this->addClass('alert-dismissible');
$closeBtn = new Markup(
'<button type="button" class="btn-close" data-bs-dismiss="alert"></button>'
);
$this->children($closeBtn);
}
}
}
// Usage
$alert = new Alert([
'type' => 'warning',
'message' => 'This is a warning message',
'dismissible' => true
]);
echo $alert->render();
Benefits of Extending Markup
- Type Safety: Your IDE can provide autocompletion for custom methods
- Consistency: Enforce consistent patterns across your application
- Reusability: Create once, use everywhere
- Maintainability: Changes to component structure happen in one place
- Documentation: Self-documenting component APIs
- Testing: Easier to test isolated component behavior
Performance Notes
Buffer vs Streaming Mode
// Buffer (render) - Keeps everything in memory
$html = $markup->render(); // ~5MB in memory
echo $html;
// Streaming (print) - Direct output
$markup->print(); // ~0.5MB in memory
Recommendations:
- Use
render()for small documents or when you need to manipulate the HTML - Use
print()for large documents or full pages print()is approximately 2x faster for documents over 100KB
Children Optimization
// ❌ Less performant - creates many objects
$list->children(
new Markup('<li>%children%</li>', children: ['Item 1']),
new Markup('<li>%children%</li>', children: ['Item 2']),
new Markup('<li>%children%</li>', children: ['Item 3'])
);
// ✅ More performant - uses childrenWrapper
$list = new Markup(
wrapper: '<ul>%children%</ul>',
childrenWrapper: '<li>%child%</li>',
children: ['Item 1', 'Item 2', 'Item 3']
);
See Also
- MarkupFactory - Quick creation methods
- MarkupSlot - Slot system
- MarkupFinder - Direct search methods
- MarkupQueryBuilder - Fluent query builder
- MarkupCollection - Collection methods
- Getting Started - Introduction and first steps