Skip to content

Commit 66b1cfc

Browse files
committed
Add HtmlContentPlaceholder for seeding fixture HTML content
Moves the placeholder HTML generator from components-web-app into the bundle as Silverback\ApiComponentsBundle\Fixture\Placeholder\HtmlContentPlaceholder. Supports paragraphs, headings, lists, quotes, code, links, and plaintext output via a simple options array.
1 parent e1671d3 commit 66b1cfc

2 files changed

Lines changed: 494 additions & 0 deletions

File tree

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Silverback API Components Bundle Project
5+
*
6+
* (c) Daniel West <daniel@silverback.is>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Silverback\ApiComponentsBundle\Fixture\Placeholder;
13+
14+
/**
15+
* Generates structured placeholder HTML (or plain text) for seeding HtmlContent entities in fixtures.
16+
*
17+
* Usage:
18+
* $html = (new HtmlContentPlaceholder())->generate(['paragraphs' => 3, 'includeHeadings' => true]);
19+
* $htmlContent->html = $html;
20+
*/
21+
class HtmlContentPlaceholder
22+
{
23+
public const FORMAT_HTML = 'html';
24+
public const FORMAT_PLAINTEXT = 'plaintext';
25+
26+
public const LENGTH_SHORT = 'short';
27+
public const LENGTH_MEDIUM = 'medium';
28+
public const LENGTH_LONG = 'long';
29+
30+
protected array $options = [
31+
'paragraphs' => 3,
32+
'paragraphLength' => self::LENGTH_MEDIUM,
33+
'includeHeadings' => false,
34+
'includeLists' => false,
35+
'includeQuotes' => false,
36+
'includeCode' => false,
37+
'includeLinks' => true,
38+
'format' => self::FORMAT_HTML,
39+
];
40+
41+
protected array $paragraphTemplates = [
42+
'Our Custom Web Application (CWA) empowers businesses to take control of their online presence with scalable, intuitive tools.',
43+
'Built with flexibility in mind, the CWA supports dynamic modules tailored to the unique workflows of growing companies.',
44+
'From rapid deployment to ongoing iteration, our system enables teams to manage content, customer interactions, and analytics — all from one place.',
45+
'Security, performance, and usability drive the foundation of the platform, ensuring peace of mind for clients and their users.',
46+
'The admin interface is designed to be user-friendly and accessible, so teams can get started with minimal training.',
47+
'We integrate seamlessly with third-party services, making it easy to connect your CRM, email platform, and more.',
48+
'With customizable UI components and branding options, your CWA truly reflects your company\'s identity.',
49+
'Performance optimization is built-in, including server-side rendering and responsive design out of the box.',
50+
];
51+
52+
protected array $headings = [
53+
'Why Choose Our CWA?',
54+
'Key Benefits',
55+
'How It Works',
56+
'Tailored for Growth',
57+
'Modular Architecture',
58+
'Effortless Content Management',
59+
];
60+
61+
protected array $listItems = [
62+
'Drag-and-drop page builder',
63+
'Role-based access control',
64+
'SEO-friendly routing',
65+
'Real-time notifications',
66+
'Analytics dashboard',
67+
'API-first design',
68+
'Live preview mode',
69+
'Multilingual support',
70+
'Component library with dark mode',
71+
'Automated deployment pipeline',
72+
];
73+
74+
protected array $codeSnippets = [
75+
"fetch('/api/v1/content', { method: 'GET' })",
76+
'<component is="UserCard" :user="user" />',
77+
'const user = await auth.login(email, password);',
78+
"cwa.renderComponent('Dashboard', userContext);",
79+
];
80+
81+
protected array $quotes = [
82+
'We switched to the CWA and reduced deployment time by 40%.',
83+
'The flexibility of the system let us scale without rewriting our stack.',
84+
'Our marketing team actually enjoys using the CMS now.',
85+
'Clients have praised the speed and responsiveness of the new site.',
86+
'We feel supported — not just technically, but strategically too.',
87+
];
88+
89+
protected array $links = [
90+
'here is a link' => 'https://cwa.rocks',
91+
'welcome to the link world' => 'https://cwa.rocks',
92+
'link me up Scotty' => 'https://cwa.rocks',
93+
'linky mc link face' => 'https://cwa.rocks',
94+
];
95+
96+
public function __construct(array $options = [])
97+
{
98+
$this->setOptions($options);
99+
}
100+
101+
public function setOptions(array $options): void
102+
{
103+
$this->options = array_merge($this->options, $options);
104+
}
105+
106+
public function generate(array $options = []): string
107+
{
108+
$options = array_merge($this->options, $options);
109+
110+
$output = [];
111+
112+
$totalParagraphs = $options['paragraphs'];
113+
for ($i = 0; $i < $totalParagraphs; ++$i) {
114+
$output[] = $this->renderParagraph($options);
115+
}
116+
117+
if ($options['includeHeadings']) {
118+
array_splice($output, 0, 0, $this->renderHeading($options));
119+
$output = $this->insertNewOutput($output, $totalParagraphs - 1, fn () => $this->renderHeading($options));
120+
}
121+
122+
$insertables = [
123+
'includeLists' => fn () => $this->renderList($options),
124+
'includeQuotes' => fn () => $this->renderQuote($options),
125+
'includeCode' => fn () => $this->renderCode($options),
126+
];
127+
128+
foreach ($insertables as $flag => $callback) {
129+
if ($options[$flag]) {
130+
$output = $this->insertNewOutput($output, $totalParagraphs, $callback);
131+
}
132+
}
133+
134+
return implode("\n\n", $output);
135+
}
136+
137+
private function insertNewOutput(array $output, int $maxInserts, callable $callback): array
138+
{
139+
if ($maxInserts < 1) {
140+
return $output;
141+
}
142+
$toInsert = random_int(1, $maxInserts);
143+
for ($i = 0; $i < $toInsert; ++$i) {
144+
$index = \count($output) > 0 ? random_int(0, \count($output) - 1) : 0;
145+
array_splice($output, $index, 0, $callback());
146+
}
147+
148+
return $output;
149+
}
150+
151+
private function renderHeading(array $options): string
152+
{
153+
$heading = $this->randomElement($this->headings);
154+
155+
return $this->format("<h2>{$heading}</h2>", $heading, $options);
156+
}
157+
158+
private function renderParagraph(array $options): string
159+
{
160+
$sentences = $this->randomSelection($this->paragraphTemplates, $this->getParagraphSentenceCount($options));
161+
$text = implode(' ', $sentences);
162+
163+
if ($options['includeLinks']) {
164+
$text = $this->insertLinks($text, $options);
165+
}
166+
167+
return $this->format("<p>{$text}</p>", $text, $options);
168+
}
169+
170+
private function getParagraphSentenceCount(array $options): int
171+
{
172+
return match ($options['paragraphLength']) {
173+
self::LENGTH_SHORT => random_int(1, 2),
174+
self::LENGTH_LONG => random_int(5, 7),
175+
default => random_int(3, 4),
176+
};
177+
}
178+
179+
private function insertLinks(string $text, array $options): string
180+
{
181+
$phrases = array_keys($this->links);
182+
shuffle($phrases);
183+
$numLinks = random_int(1, min(2, \count($phrases)));
184+
185+
for ($i = 0; $i < $numLinks; ++$i) {
186+
$phrase = $phrases[$i];
187+
$url = $this->links[$phrase];
188+
189+
if (!str_contains($text, $phrase)) {
190+
$words = explode(' ', $text);
191+
$insertAt = random_int(0, \count($words) - 1);
192+
$linkedPhrase = self::FORMAT_HTML === $options['format']
193+
? "<a href=\"{$url}\">{$phrase}</a>"
194+
: "{$phrase} ({$url})";
195+
array_splice($words, $insertAt, 0, $linkedPhrase);
196+
$text = implode(' ', $words);
197+
}
198+
}
199+
200+
return $text;
201+
}
202+
203+
private function renderList(array $options): string
204+
{
205+
$items = $this->randomSelection($this->listItems, random_int(3, 6));
206+
$tags = ['ul', 'ol'];
207+
$tag = $tags[array_rand($tags)];
208+
$html = "<{$tag}>\n";
209+
foreach ($items as $item) {
210+
$html .= "<li>{$item}</li>\n";
211+
}
212+
$html .= "</{$tag}>";
213+
214+
$plain = '- ' . implode("\n- ", $items);
215+
216+
return $this->format($html, $plain, $options);
217+
}
218+
219+
private function renderQuote(array $options): string
220+
{
221+
$quote = $this->randomElement($this->quotes);
222+
223+
return $this->format("<blockquote>{$quote}</blockquote>", "\"{$quote}\"", $options);
224+
}
225+
226+
private function renderCode(array $options): string
227+
{
228+
$code = $this->randomElement($this->codeSnippets);
229+
230+
return $this->format("<pre><code>{$code}</code></pre>", $code, $options);
231+
}
232+
233+
private function format(string $html, string $plain, array $options): string
234+
{
235+
return self::FORMAT_PLAINTEXT === $options['format'] ? $plain : $html;
236+
}
237+
238+
private function randomElement(array $array): string
239+
{
240+
return $array[array_rand($array)];
241+
}
242+
243+
private function randomSelection(array $array, int $count): array
244+
{
245+
shuffle($array);
246+
247+
return \array_slice($array, 0, $count);
248+
}
249+
}

0 commit comments

Comments
 (0)