MarkupFlow Class
Flow multiple elements together to be rendered within a single wrapper.
Table of Contents
Overview
The MarkupFlow class allows you to flow multiple elements (strings, Markup instances, or callables) together to be rendered within a single children wrapper. This is particularly useful when you need to combine different types of content in a natural flow, such as mixing text with inline elements (like <strong> or <a> tags) within a paragraph.
Key Features
- Mixed content - Combine strings, Markup instances, and callables
- Single wrapper - All items flow together within the same parent wrapper
- Natural flow - Perfect for mixing text and inline elements
- Flexible composition - Build complex nested structures
- Chainable API - Fluent method chaining for easy construction
- Type-agnostic - Works with any renderable content type
When to Use MarkupFlow
Use MarkupFlow when:
- You need to combine text and Markup objects in the same list item or paragraph
- You have mixed content flowing together (text + inline elements)
- You want multiple elements to share a single wrapper (like a
<li>) - Youβre building complex nested structures with mixed content types
- You need fine-grained control over content flow
Donβt use MarkupFlow when:
- You have simple string content (use arrays directly)
- Each item needs its own wrapper (use regular children)
- Youβre working with a single element type
Constructor
public function __construct(array $items = [])
Creates a new MarkupFlow with optional initial items.
Parameters
| Parameter | Type | Description |
|---|---|---|
$items | array | Optional. Array of items to flow together (strings, Markup instances, or callables). Default: empty array |
Examples
use MaxPertici\Markup\Markup;
use MaxPertici\Markup\MarkupFlow;
// Empty flow
$flow = new MarkupFlow();
// Flow with initial items
$flow = new MarkupFlow([
'Text content',
new Markup('<strong>%children%</strong>', children: ['Bold text']),
function() { echo 'From callback'; }
]);
// Flowing text and markup together
$link = new Markup('<a href="/about">%children%</a>', children: ['Learn more']);
$flow = new MarkupFlow([
'Read our story and ',
$link,
' about what we do.'
]);
Managing Items
items()
Retrieves all items in the flow.
items(): array
Return
array- Array of items in the flow
Examples
$flow = new MarkupFlow([
'Item 1',
new Markup('<span>%children%</span>', children: ['Item 2'])
]);
$items = $flow->items();
// Returns: ['Item 1', Markup instance]
// Count items
$count = count($flow->items());
// Iterate over items
foreach ($flow->items() as $item) {
// Process each item
}
add()
Adds one or more items to the flow.
add(mixed ...$items): self
Parameters
| Parameter | Type | Description |
|---|---|---|
...$items | mixed | Items to add to the flow (strings, Markup instances, or callables) |
Return
self- Returns the instance for method chaining
Examples
$flow = new MarkupFlow(['Initial item']);
// Add a single item
$flow->add('New item');
// Add multiple items
$flow->add(
'Text',
new Markup('<em>%children%</em>', children: ['Emphasized']),
'More text'
);
// Chaining
$flow = new MarkupFlow()
->add('First')
->add('Second')
->add('Third');
// Add conditionally
$flow->add('Base content');
if ($showExtra) {
$flow->add($extraContent);
}
isEmpty()
Checks if the flow contains no items.
isEmpty(): bool
Return
bool- True if the flow has no items, false otherwise
Examples
$empty = new MarkupFlow();
$empty->isEmpty(); // true
$filled = new MarkupFlow(['Content']);
$filled->isEmpty(); // false
// Conditional rendering
if (!$flow->isEmpty()) {
// Render the flow
}
// Guard clause
if ($flow->isEmpty()) {
return 'No content available';
}
count()
Gets the number of items in the flow.
count(): int
Return
int- The number of items in the flow
Examples
$flow = new MarkupFlow([
'Item 1',
'Item 2',
'Item 3'
]);
$count = $flow->count(); // 3
// Display count
echo "Flow contains {$flow->count()} items";
// Conditional logic
if ($flow->count() > 5) {
// Handle large flows
}
// Loop with count
for ($i = 0; $i < $flow->count(); $i++) {
// Process items by index
}
Usage with Markup
MarkupFlow is designed to work seamlessly with the Markup classβs childrenWrapper feature.
Basic Integration
use MaxPertici\Markup\Markup;
use MaxPertici\Markup\MarkupFlow;
// Create a list with flowing content
$list = new Markup(
wrapper: '<ul>%children%</ul>',
childrenWrapper: '<li>%child%</li>',
children: [
'Simple item',
new MarkupFlow([
'Complex item with ',
new Markup('<strong>%children%</strong>', children: ['bold text']),
' flowing together.'
]),
'Another simple item'
]
);
echo $list->render();
/*
<ul>
<li>Simple item</li>
<li>Complex item with <strong>bold text</strong> flowing together.</li>
<li>Another simple item</li>
</ul>
*/
Multiple Markup Instances Flowing Together
$icon = new Markup('<i class="icon">%children%</i>', children: ['π ']);
$text = new Markup('<span>%children%</span>', children: ['Home']);
$menuItem = new MarkupFlow([$icon, ' ', $text]);
$menu = new Markup(
wrapper: '<ul class="menu">%children%</ul>',
childrenWrapper: '<li>%child%</li>',
children: [$menuItem]
);
echo $menu->render();
/*
<ul class="menu">
<li><i class="icon">π </i> <span>Home</span></li>
</ul>
*/
Practical Examples
Navigation Menu with Icons
function createMenuItem(string $icon, string $label, string $url): MarkupFlow
{
$iconMarkup = new Markup(
'<i class="menu-icon">%children%</i>',
children: [$icon]
);
$labelMarkup = new Markup(
'<span class="menu-label">%children%</span>',
children: [$label]
);
$link = new Markup(
'<a href="%url%" class="menu-link">%children%</a>',
children: [new MarkupFlow([$iconMarkup, ' ', $labelMarkup])]
);
$link->setAttribute('href', $url);
return new MarkupFlow([$link]);
}
// Build menu
$menu = new Markup(
wrapper: '<nav><ul class="main-menu">%children%</ul></nav>',
childrenWrapper: '<li class="menu-item">%child%</li>',
children: [
createMenuItem('π ', 'Home', '/'),
createMenuItem('π', 'Blog', '/blog'),
createMenuItem('π§', 'Contact', '/contact')
]
);
echo $menu->render();
/*
<nav>
<ul class="main-menu">
<li class="menu-item"><a href="/" class="menu-link"><i class="menu-icon">π </i> <span class="menu-label">Home</span></a></li>
<li class="menu-item"><a href="/blog" class="menu-link"><i class="menu-icon">π</i> <span class="menu-label">Blog</span></a></li>
<li class="menu-item"><a href="/contact" class="menu-link"><i class="menu-icon">π§</i> <span class="menu-label">Contact</span></a></li>
</ul>
</nav>
*/
Product List with Badges
function createProduct(string $name, float $price, array $badges = []): MarkupFlow
{
$flow = new MarkupFlow();
// Product name
$nameMarkup = new Markup(
'<span class="product-name">%children%</span>',
children: [$name]
);
$flow->add($nameMarkup);
// Badges
if (!empty($badges)) {
foreach ($badges as $badge) {
$badgeMarkup = new Markup(
'<span class="badge">%children%</span>',
children: [$badge]
);
$flow->add(' ', $badgeMarkup);
}
}
// Price
$priceMarkup = new Markup(
'<span class="product-price">%children%</span>',
children: ['$' . number_format($price, 2)]
);
$flow->add(' - ', $priceMarkup);
return $flow;
}
// Build product list
$products = new Markup(
wrapper: '<ul class="product-list">%children%</ul>',
childrenWrapper: '<li class="product-item">%child%</li>',
children: [
createProduct('Premium Widget', 99.99, ['New', 'Popular']),
createProduct('Standard Widget', 49.99, ['Sale']),
createProduct('Basic Widget', 29.99)
]
);
echo $products->render();
/*
<ul class="product-list">
<li class="product-item">
<span class="product-name">Premium Widget</span>
<span class="badge">New</span>
<span class="badge">Popular</span>
- <span class="product-price">$99.99</span>
</li>
<li class="product-item">
<span class="product-name">Standard Widget</span>
<span class="badge">Sale</span>
- <span class="product-price">$49.99</span>
</li>
<li class="product-item">
<span class="product-name">Basic Widget</span>
- <span class="product-price">$29.99</span>
</li>
</ul>
*/
Timeline with Mixed Content
function createTimelineEvent(
string $date,
string $title,
?Markup $content = null
): MarkupFlow
{
$flow = new MarkupFlow();
// Date badge
$dateMarkup = new Markup(
'<span class="timeline-date">%children%</span>',
children: [$date]
);
$flow->add($dateMarkup);
// Title
$titleMarkup = new Markup(
'<h3 class="timeline-title">%children%</h3>',
children: [$title]
);
$flow->add($titleMarkup);
// Optional content
if ($content) {
$flow->add($content);
}
return $flow;
}
// Create timeline
$eventContent = new Markup(
'<div class="timeline-content">%children%</div>',
children: [
new Markup('<p>%children%</p>', children: ['Event description here.']),
new Markup('<a href="/details">%children%</a>', children: ['Learn more β'])
]
);
$timeline = new Markup(
wrapper: '<div class="timeline">%children%</div>',
childrenWrapper: '<div class="timeline-item">%child%</div>',
children: [
createTimelineEvent('2024-01', 'Project Launch', $eventContent),
createTimelineEvent('2024-02', 'Version 2.0 Released'),
createTimelineEvent('2024-03', 'Major Update')
]
);
echo $timeline->render();
Breadcrumb Navigation
class Breadcrumb
{
private Markup $markup;
public function __construct()
{
$this->markup = new Markup(
wrapper: '<nav aria-label="breadcrumb"><ol class="breadcrumb">%children%</ol></nav>',
childrenWrapper: '<li class="breadcrumb-item">%child%</li>'
);
}
public function addItem(string $label, ?string $url = null): self
{
if ($url) {
$link = new Markup(
'<a href="%url%">%children%</a>',
children: [$label]
);
$link->setAttribute('href', $url);
$flow = new MarkupFlow([$link]);
} else {
$flow = new MarkupFlow([
new Markup(
'<span class="active">%children%</span>',
children: [$label]
)
]);
}
$this->markup->children($flow);
return $this;
}
public function addSeparator(string $separator = '/'): self
{
$sep = new Markup(
'<span class="breadcrumb-separator">%children%</span>',
children: [' ' . $separator . ' ']
);
// Get last child and convert to flow if needed
$children = $this->markup->getChildren();
if (!empty($children)) {
$lastChild = array_pop($children);
if ($lastChild instanceof MarkupFlow) {
$lastChild->add($sep);
} else {
$lastChild = new MarkupFlow([$lastChild, $sep]);
}
$children[] = $lastChild;
$this->markup->setChildren($children);
}
return $this;
}
public function render(): string
{
return $this->markup->render();
}
}
// Usage
$breadcrumb = new Breadcrumb();
$breadcrumb->addItem('Home', '/')
->addSeparator()
->addItem('Products', '/products')
->addSeparator()
->addItem('Electronics', '/products/electronics')
->addSeparator()
->addItem('Current Product');
echo $breadcrumb->render();
/*
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a><span class="breadcrumb-separator"> / </span></li>
<li class="breadcrumb-item"><a href="/products">Products</a><span class="breadcrumb-separator"> / </span></li>
<li class="breadcrumb-item"><a href="/products/electronics">Electronics</a><span class="breadcrumb-separator"> / </span></li>
<li class="breadcrumb-item"><span class="active">Current Product</span></li>
</ol>
</nav>
*/
Tag Cloud with Counts
function createTagCloud(array $tags): Markup
{
$tagFlows = [];
foreach ($tags as $tag => $count) {
$tagLink = new Markup(
'<a href="/tag/%slug%" class="tag-link">%children%</a>',
children: [$tag]
);
$tagLink->setAttribute('href', '/tag/' . sanitize_title($tag));
$countBadge = new Markup(
'<span class="tag-count">%children%</span>',
children: [(string)$count]
);
$tagFlows[] = new MarkupFlow([$tagLink, ' ', $countBadge]);
}
return new Markup(
wrapper: '<div class="tag-cloud">%children%</div>',
childrenWrapper: '<span class="tag-item">%child%</span>',
children: $tagFlows
);
}
// Usage
$tags = [
'PHP' => 42,
'JavaScript' => 38,
'HTML' => 56,
'CSS' => 45,
'WordPress' => 33
];
$cloud = createTagCloud($tags);
echo $cloud->render();
/*
<div class="tag-cloud">
<span class="tag-item"><a href="/tag/php" class="tag-link">PHP</a> <span class="tag-count">42</span></span>
<span class="tag-item"><a href="/tag/javascript" class="tag-link">JavaScript</a> <span class="tag-count">38</span></span>
<span class="tag-item"><a href="/tag/html" class="tag-link">HTML</a> <span class="tag-count">56</span></span>
<span class="tag-item"><a href="/tag/css" class="tag-link">CSS</a> <span class="tag-count">45</span></span>
<span class="tag-item"><a href="/tag/wordpress" class="tag-link">WordPress</a> <span class="tag-count">33</span></span>
</div>
*/
Advanced Techniques
Dynamic Flow Building
function buildComplexItem(array $data): MarkupFlow
{
$flow = new MarkupFlow();
// Always add title
if (isset($data['title'])) {
$title = new Markup(
'<h4>%children%</h4>',
children: [$data['title']]
);
$flow->add($title);
}
// Conditionally add description
if (!empty($data['description'])) {
$desc = new Markup(
'<p class="description">%children%</p>',
children: [$data['description']]
);
$flow->add($desc);
}
// Add metadata if available
if (!empty($data['meta'])) {
$metaItems = [];
foreach ($data['meta'] as $key => $value) {
$metaItems[] = new Markup(
'<span class="meta-item">%children%</span>',
children: ["{$key}: {$value}"]
);
}
$flow->add(new MarkupFlow($metaItems));
}
// Add actions
if (!empty($data['actions'])) {
foreach ($data['actions'] as $action) {
$button = new Markup(
'<button>%children%</button>',
children: [$action['label']]
);
$button->addClass('btn', 'btn-sm')
->setAttribute('data-action', $action['id']);
$flow->add($button);
}
}
return $flow;
}
// Usage
$items = [
[
'title' => 'Item One',
'description' => 'First item description',
'meta' => ['Author' => 'John', 'Date' => '2024-01-15'],
'actions' => [
['id' => 'edit', 'label' => 'Edit'],
['id' => 'delete', 'label' => 'Delete']
]
],
[
'title' => 'Item Two',
'actions' => [
['id' => 'view', 'label' => 'View']
]
]
];
$list = new Markup(
wrapper: '<div class="items">%children%</div>',
childrenWrapper: '<div class="item">%child%</div>',
children: array_map('buildComplexItem', $items)
);
echo $list->render();
Nested Flows
// Flows within flows for complex layouts
$header = new MarkupFlow([
new Markup('<img src="/logo.png" alt="Logo" />'),
new Markup('<h1>%children%</h1>', children: ['Site Title'])
]);
$nav = new MarkupFlow([
new Markup('<nav>%children%</nav>', children: [
new Markup('<a href="/">%children%</a>', children: ['Home']),
new Markup('<a href="/about">%children%</a>', children: ['About'])
])
]);
$topBar = new MarkupFlow([
$header,
$nav
]);
$page = new Markup(
wrapper: '<div class="page">%children%</div>',
childrenWrapper: '<div class="section">%child%</div>',
children: [$topBar]
);
Conditional Content Assembly
function assembleContent(
string $text,
bool $includeBadge = false,
bool $includeIcon = false,
bool $includeLink = false
): MarkupFlow
{
$flow = new MarkupFlow();
// Icon first if requested
if ($includeIcon) {
$icon = new Markup('<i class="icon">%children%</i>', children: ['β
']);
$flow->add($icon, ' ');
}
// Main text
$flow->add($text);
// Badge if requested
if ($includeBadge) {
$badge = new Markup(
'<span class="badge">%children%</span>',
children: ['NEW']
);
$flow->add(' ', $badge);
}
// Wrap in link if requested
if ($includeLink) {
$link = new Markup(
'<a href="/details">%children%</a>',
children: $flow->items()
);
return new MarkupFlow([$link]);
}
return $flow;
}
// Usage - different variations
$list = new Markup(
wrapper: '<ul>%children%</ul>',
childrenWrapper: '<li>%child%</li>',
children: [
assembleContent('Basic text'),
assembleContent('With badge', includeBadge: true),
assembleContent('With icon', includeIcon: true),
assembleContent('Complete', true, true, true)
]
);
Best Practices
1. Use Flows for Mixed Content Types
// β
Good - flow when mixing types
$flow = new MarkupFlow([
'Text',
new Markup('<strong>%children%</strong>', children: ['Bold']),
'More text'
]);
// β Not needed - simple array works fine
$items = ['Item 1', 'Item 2', 'Item 3']; // Use this directly
2. Keep Flows Focused
// β
Good - focused flow for a single purpose
$menuItem = new MarkupFlow([
$icon,
$label
]);
// β Too complex - consider separate flows or restructuring
$everything = new MarkupFlow([
$header,
$subheader,
$content,
$metadata,
$footer
]);
3. Leverage Chainable Methods
// β
Good - fluent chaining
$flow = new MarkupFlow()
->add($icon)
->add(' ')
->add($label);
// β
Also good - constructor with array
$flow = new MarkupFlow([$icon, ' ', $label]);
4. Check Before Processing
// β
Good - check if flow has content
if (!$flow->isEmpty()) {
$list->children($flow);
}
// β
Good - count-based logic
if ($flow->count() > 3) {
// Handle large flows differently
}
Common Patterns
Pattern: Icon + Text
function iconText(string $icon, string $text): MarkupFlow
{
return new MarkupFlow([
new Markup('<i>%children%</i>', children: [$icon]),
' ',
$text
]);
}
$list = new Markup(
wrapper: '<ul>%children%</ul>',
childrenWrapper: '<li>%child%</li>',
children: [
iconText('π§', 'Email us'),
iconText('π', 'Call us'),
iconText('π¬', 'Chat with us')
]
);
Pattern: Label + Value Pairs
function labelValue(string $label, string $value): MarkupFlow
{
return new MarkupFlow([
new Markup('<dt>%children%</dt>', children: [$label]),
new Markup('<dd>%children%</dd>', children: [$value])
]);
}
$dl = new Markup(
wrapper: '<dl>%children%</dl>',
children: [
labelValue('Name', 'John Doe'),
labelValue('Email', 'john@example.com'),
labelValue('Phone', '+1 234 567 8900')
]
);
Pattern: Prefix + Content + Suffix
function wrapContent(
string $content,
?string $prefix = null,
?string $suffix = null
): MarkupFlow
{
$flow = new MarkupFlow();
if ($prefix) {
$flow->add(new Markup('<span class="prefix">%children%</span>', children: [$prefix]));
}
$flow->add($content);
if ($suffix) {
$flow->add(new Markup('<span class="suffix">%children%</span>', children: [$suffix]));
}
return $flow;
}
Performance Notes
Memory Efficiency
// MarkupFlow is lightweight - just wraps an array
$flow = new MarkupFlow(); // ~200 bytes
// Adding items doesn't create additional wrappers
$flow->add('Item 1', 'Item 2', 'Item 3'); // ~200 bytes + item sizes
Rendering Performance
// MarkupFlow has no rendering overhead
// Items are processed directly during parent rendering
// This:
$flow = new MarkupFlow([$item1, $item2]);
$markup->children($flow);
// Is just as fast as:
$markup->children($item1, $item2);
// The difference is in how items flow together in the output
Troubleshooting
Problem: Flow Not Rendering
// β Wrong: Adding flow to wrong place
$flow = new MarkupFlow(['Content']);
// Forgot to add to markup
// β
Correct: Add flow as child
$markup->children($flow);
Problem: Items Not Flowing Together
// β Wrong: Adding items separately
$markup->children('Item 1', 'Item 2'); // Each gets own wrapper
// β
Correct: Flow items together
$flow = new MarkupFlow(['Item 1', 'Item 2']);
$markup->children($flow); // Flow together in one wrapper
Problem: Empty Wrappers Rendering
// β Wrong: Always adding flow
$flow = new MarkupFlow();
$markup->children($flow); // Renders empty wrapper
// β
Correct: Check before adding
if (!$flow->isEmpty()) {
$markup->children($flow);
}
See Also
- Markup Class - Main markup building class
- MarkupFactory - Quick creation methods
- MarkupSlot - Slot system for named placeholders
- MarkupQueryBuilder - Fluent query builder
- Getting Started - Introduction and first steps