pull/710/merge
fadrian06 3 weeks ago committed by GitHub
commit ebc928ec8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -11,5 +11,5 @@ insert_final_newline = true
[*.md]
indent_size = 2
[tests/views/*.php]
[tests/views/**/*]
insert_final_newline = false

@ -7,7 +7,7 @@
{
"name": "Mike Cao",
"email": "mike@mikecao.com",
"homepage": "http://www.mikecao.com/",
"homepage": "https://mikecao.com",
"role": "Original Developer"
},
{

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace flight\template;
abstract class Component
{
abstract public function html(): string;
public function css(): string
{
return '';
}
public function js(): string
{
return '';
}
}

@ -4,47 +4,59 @@ declare(strict_types=1);
namespace flight\template;
use Closure;
use Exception;
use ReflectionFunction;
/**
* The View class represents output to be displayed. It provides
* methods for managing view data and inserts the data into
* view templates upon rendering.
*
* @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license [MIT](https://docs.flightphp.com/license)
* @copyright 2011 [Mike Cao](https://mikecao.com)
*/
class View
{
/** Location of view templates. */
/** Location of view templates */
public string $path;
/** File extension. */
/** File extension */
public string $extension = '.php';
public bool $preserveVars = true;
/**
* View variables.
*
* @var array<string, mixed> $vars
*/
/** @var array<string, mixed> View variables */
protected array $vars = [];
/** Template file. */
private string $template;
private string $componentPrefix;
private string $componentsPath;
private int $fetchDepth = 0;
/** @var array<string, bool> */
private array $styles = [];
/** @var array<string, bool> */
private array $scripts = [];
/**
* Constructor.
*
* @param string $path Path to templates directory
* @param string $componentPrefix Prefix for component tags.
* For example, if the prefix is `f`, then a component tag would look like `<f-component-name />`
* @param string $componentsPath Path to components directory.
* If is a relative path, it will be relative to the `$path` property.
* **We recomment that you always use absolute paths for explicitness**.
*/
public function __construct(string $path = '.')
{
public function __construct(
string $path = ".",
string $componentPrefix = 'f',
string $componentsPath = 'components'
) {
$this->path = $path;
$this->componentPrefix = $componentPrefix;
$this->componentsPath = $componentsPath;
}
/**
* Gets a template variable.
*
* Gets a template variable
* @return mixed Variable value or `null` if doesn't exists
*/
public function get(string $key)
@ -53,16 +65,14 @@ class View
}
/**
* Sets a template variable.
*
* @param string|iterable<string, mixed> $key
* Sets a template variable
* @param string|array<string, mixed> $key
* @param mixed $value Value
*
* @return self
* @return $this
*/
public function set($key, $value = null): self
public function set($key, $value = null)
{
if (\is_iterable($key)) {
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->vars[$k] = $v;
}
@ -74,9 +84,8 @@ class View
}
/**
* Checks if a template variable is set.
*
* @return bool If key exists
* Checks if a template variable is set
* @return bool If key exists and is not `null`
*/
public function has(string $key): bool
{
@ -84,11 +93,10 @@ class View
}
/**
* Unsets a template variable. If no key is passed in, clear all variables.
*
* Unsets a template variable. If no key is passed in, clear all variables
* @return $this
*/
public function clear(?string $key = null): self
public function clear(?string $key = null)
{
if ($key === null) {
$this->vars = [];
@ -100,104 +108,242 @@ class View
}
/**
* Renders a template.
*
* Renders a template
* @param string $file Template file
* @param ?array<string, mixed> $templateData Template data
*
* @throws \Exception If template not found
* @throws Exception If template not found
*/
public function render(string $file, ?array $templateData = null): void
{
$this->template = $this->getTemplate($file);
echo $this->fetch($file, $templateData);
}
/**
* Gets the output of a template
* @param string $file Template file
* @param ?array<string, mixed> $data Template data
* @return string Output of template
*/
public function fetch(string $file, ?array $data = null): string
{
if ($this->fetchDepth === 0) {
$this->styles = [];
$this->scripts = [];
}
$this->fetchDepth++;
try {
$template = $this->getTemplate($file);
if (!\file_exists($this->template)) {
$normalized_path = self::normalizePath($this->template);
throw new \Exception("Template file not found: {$normalized_path}.");
if (!$this->exists($file)) {
throw new Exception("Template file not found: $template.");
}
\extract($this->vars);
extract($this->vars);
if (\is_array($templateData) === true) {
\extract($templateData);
if (is_array($data)) {
extract($data);
if ($this->preserveVars === true) {
$this->vars = \array_merge($this->vars, $templateData);
if ($this->preserveVars) {
$this->vars += $data;
}
}
include $this->template;
ob_start();
$component = include $template;
switch (true) {
case is_callable($component):
$arguments = $this->getCallableArguments($component, $data);
$view = $component(...$arguments);
ob_end_clean();
break;
case $component instanceof Component:
$view = $component->html();
$css = $component->css();
$js = $component->js();
if ($css && !array_key_exists($template, $this->styles)) {
$view .= $this->renderComponentStyle($css);
$this->styles[$template] = true;
}
/**
* Gets the output of a template.
*
* @param string $file Template file
* @param ?array<string, mixed> $data Template data
*
* @return string Output of template
*/
public function fetch(string $file, ?array $data = null): string
{
\ob_start();
if ($js && !array_key_exists($template, $this->scripts)) {
$view .= $this->renderComponentScript($js);
$this->scripts[$template] = true;
}
$this->render($file, $data);
ob_end_clean();
return \ob_get_clean();
break;
default:
$view = ob_get_clean();
}
preg_match_all(
"/<$this->componentPrefix-(?<component>[a-z-]+)\s*(?<props>([a-z]+=\"[^\"]+\"\s*)*)?\s*\/>/",
$view,
$tagsMatches,
);
$tagsMatches = array_filter($tagsMatches);
foreach ($tagsMatches[0] ?? [] as $tagIndex => $match) {
$tag = $match;
$component = $tagsMatches['component'][$tagIndex];
$props = $tagsMatches['props'][$tagIndex] ?? '';
preg_match_all(
'/(?<name>[a-z]+)="(?<value>[^"]+)"/',
$props,
$propsMatches,
);
$propsMatches = array_filter($propsMatches);
if ($propsMatches) {
$props = [];
foreach (array_keys($propsMatches[0]) as $propIndex) {
$name = $propsMatches['name'][$propIndex];
$value = $propsMatches['value'][$propIndex];
$props[$name] = $value;
}
} else {
$props = [];
}
$component = $this->fetch("$this->componentsPath/$component", $props);
$tagPosition = strpos($view, $tag);
if ($tagPosition === false) {
continue;
}
$view = substr_replace($view, $component, $tagPosition, strlen($tag));
}
return $view;
} finally {
$this->fetchDepth--;
}
}
/**
* Checks if a template file exists.
*
* Checks if a template file exists
* @param string $file Template file
*
* @return bool Template file exists
*/
public function exists(string $file): bool
{
return \file_exists($this->getTemplate($file));
return file_exists($this->getTemplate($file));
}
/**
* Gets the full path to a template file.
*
* Gets the full path to a template file
* @param string $file Template file
*
* @return string Template file location
*/
public function getTemplate(string $file): string
{
$ext = $this->extension;
$fileDoesNotHaveExtension = substr($file, -strlen($this->extension)) !== $this->extension;
if (!empty($ext) && (\substr($file, -1 * \strlen($ext)) != $ext)) {
$file .= $ext;
if ($fileDoesNotHaveExtension) {
$file .= $this->extension;
}
$is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN';
$isLinuxAbsolutePath = $file[0] === '/';
$isWindowsAbsolutePath = PHP_OS === 'WINNT' && $file[1] === ':';
if ((\substr($file, 0, 1) === '/') || ($is_windows && \substr($file, 1, 1) === ':')) {
return $file;
if ($isLinuxAbsolutePath || $isWindowsAbsolutePath) {
return $this::normalizePath($file);
}
return $this->path . DIRECTORY_SEPARATOR . $file;
return $this::normalizePath("$this->path/$file");
}
/**
* Displays escaped output.
*
* Displays escaped output
* @param string $str String to escape
*
* @return string Escaped string
*/
public function e(string $str): string
{
$value = \htmlentities($str);
$value = htmlentities($str);
echo $value;
return $value;
}
protected static function normalizePath(string $path, string $separator = DIRECTORY_SEPARATOR): string
protected static function normalizePath(
string $path,
string $separator = DIRECTORY_SEPARATOR
): string {
return str_replace(['\\', '/'], $separator, $path);
}
/**
* @param callable $component
* @param ?array<string, mixed> $data
* @return array<int, mixed>
*/
private function getCallableArguments(callable $component, ?array $data): array
{
if (!is_array($data) || !$data) {
return [];
}
$arguments = [];
$remainingData = $data;
$reflection = new ReflectionFunction(Closure::fromCallable($component));
foreach ($reflection->getParameters() as $parameter) {
if ($parameter->isVariadic()) {
foreach ($remainingData as $value) {
$arguments[] = $value;
}
break;
}
$name = $parameter->getName();
if (array_key_exists($name, $remainingData)) {
$arguments[] = $remainingData[$name];
unset($remainingData[$name]);
}
}
return $arguments;
}
private function renderComponentStyle(string $css): string
{
return \str_replace(['\\', '/'], $separator, $path);
if (preg_match('/^\s*<style\b[^>]*>.*<\/style>\s*$/is', $css) === 1) {
return $css;
}
return <<<html
<style>
$css
</style>
html;
}
private function renderComponentScript(string $js): string
{
if (preg_match('/^\s*<script\b[^>]*>.*<\/script>\s*$/is', $js) === 1) {
return $js;
}
return <<<html
<script>
$js
</script>
html;
}
}

@ -14,101 +14,83 @@ class ViewTest extends TestCase
protected function setUp(): void
{
$this->view = new View();
$this->view->path = __DIR__ . '/views';
$this->view = new View(__DIR__ . '/views');
}
// Set template variables
public function testVariables(): void
{
$this->view->set('test', 123);
$this->assertEquals(123, $this->view->get('test'));
$this->assertTrue($this->view->has('test'));
$this->assertTrue(!$this->view->has('unknown'));
self::assertSame(123, $this->view->get('test'));
self::assertTrue($this->view->has('test'));
self::assertFalse($this->view->has('unknown'));
$this->view->clear('test');
$this->assertNull($this->view->get('test'));
self::assertNull($this->view->get('test'));
}
public function testMultipleVariables(): void
{
$this->view->set([
'test' => 123,
'foo' => 'bar'
]);
$this->view->set(['test' => 123, 'foo' => 'bar']);
$this->assertEquals(123, $this->view->get('test'));
$this->assertEquals('bar', $this->view->get('foo'));
self::assertSame(123, $this->view->get('test'));
self::assertSame('bar', $this->view->get('foo'));
$this->view->clear();
$this->assertNull($this->view->get('test'));
$this->assertNull($this->view->get('foo'));
self::assertNull($this->view->get('test'));
self::assertNull($this->view->get('foo'));
}
// Check if template files exist
public function testTemplateExists(): void
{
$this->assertTrue($this->view->exists('hello.php'));
$this->assertTrue(!$this->view->exists('unknown.php'));
self::assertTrue($this->view->exists('hello.php'));
self::assertFalse($this->view->exists('unknown.php'));
}
// Render a template
public function testRender(): void
{
$this->view->render('hello', ['name' => 'Bob']);
$this->expectOutputString('Hello, Bob!');
self::expectOutputString('Hello, Bob!');
}
public function testRenderBadFilePath(): void
{
$this->expectException(Exception::class);
$exception_message = sprintf(
'Template file not found: %s%sviews%sbadfile.php',
__DIR__,
DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR
DIRECTORY_SEPARATOR,
);
$this->expectExceptionMessage($exception_message);
self::expectException(Exception::class);
self::expectExceptionMessage($exception_message);
$this->view->render('badfile');
}
// Fetch template output
public function testFetch(): void
{
$output = $this->view->fetch('hello', ['name' => 'Bob']);
$this->assertEquals('Hello, Bob!', $output);
self::assertSame('Hello, Bob!', $output);
}
// Default extension
public function testTemplateWithExtension(): void
{
$this->view->set('name', 'Bob');
$this->view->render('hello.php', ['name' => 'Bob']);
$this->view->render('hello.php');
$this->expectOutputString('Hello, Bob!');
self::expectOutputString('Hello, Bob!');
}
// Custom extension
public function testTemplateWithCustomExtension(): void
{
$this->view->set('name', 'Bob');
$this->view->extension = '.html';
ob_start();
$this->view->render('world');
$html = ob_get_clean();
$html = str_replace(["\r\n", "\n"], '', $html);
echo $html;
self::expectOutputString('Hello world, Bob!');
$this->expectOutputString("Hello world, Bob!");
$this->view->extension = '.html';
$this->view->render('world', ['name' => 'Bob']);
}
public function testGetTemplateAbsolutePath(): void
@ -116,48 +98,64 @@ class ViewTest extends TestCase
$tmpfile = tmpfile();
$this->view->extension = '';
$file_path = stream_get_meta_data($tmpfile)['uri'];
$this->assertEquals($file_path, $this->view->getTemplate($file_path));
self::assertSame($file_path, $this->view->getTemplate($file_path));
}
public function testE(): void
{
$this->expectOutputString('&lt;script&gt;');
$expectedString = '&lt;script&gt;';
self::expectOutputString($expectedString);
$result = $this->view->e('<script>');
$this->assertEquals('&lt;script&gt;', $result);
self::assertSame($expectedString, $result);
}
public function testeNoNeedToEscape(): void
{
$this->expectOutputString('script');
$result = $this->view->e('script');
$this->assertEquals('script', $result);
$expectedString = 'script';
self::expectOutputString($expectedString);
$result = $this->view->e($expectedString);
self::assertSame($expectedString, $result);
}
public function testNormalizePath(): void
{
$viewMock = new class extends View
{
public static function normalizePath(string $path, string $separator = DIRECTORY_SEPARATOR): string
{
public static function normalizePath(
string $path,
string $separator = DIRECTORY_SEPARATOR
): string {
return parent::normalizePath($path, $separator);
}
};
$this->assertSame(
self::assertSame(
'C:/xampp/htdocs/libs/Flight/core/index.php',
$viewMock::normalizePath('C:\xampp\htdocs\libs\Flight/core/index.php', '/')
);
$this->assertSame(
self::assertSame(
'C:\xampp\htdocs\libs\Flight\core\index.php',
$viewMock::normalizePath('C:/xampp/htdocs/libs/Flight\core\index.php', '\\')
);
$this->assertSame(
self::assertSame(
'C:°xampp°htdocs°libs°Flight°core°index.php',
$viewMock::normalizePath('C:/xampp/htdocs/libs/Flight\core\index.php', '°')
);
}
/** @dataProvider renderDataProvider */
/**
* @dataProvider renderDataProvider
* @param array{string, array<string, mixed>} $renderParams
*/
public function testDoesNotPreserveVarsWhenFlagIsDisabled(
string $output,
array $renderParams,
@ -165,11 +163,11 @@ class ViewTest extends TestCase
): void {
$this->view->preserveVars = false;
$this->expectOutputString($output);
self::expectOutputString($output);
$this->view->render(...$renderParams);
set_error_handler(function (int $code, string $message) use ($regexp): void {
$this->assertMatchesRegularExpression($regexp, $message);
set_error_handler(static function (int $code, string $message) use ($regexp): void {
self::assertMatchesRegularExpression($regexp, $message);
});
$this->view->render($renderParams[0]);
@ -179,17 +177,14 @@ class ViewTest extends TestCase
public function testKeepThePreviousStateOfOneViewComponentByDefault(): void
{
$html = <<<'html'
$html = self::removeLineEndings(<<<'html'
<div>Hi</div>
<div>Hi</div>
<input type="number" />
<input type="number" />
html; // phpcs:ignore
// if windows replace \n with \r\n
$html = str_replace(["\n", "\r"], '', $html);
html);
$this->expectOutputString($html);
self::expectOutputString($html);
$this->view->render('myComponent', ['prop' => 'Hi']);
$this->view->render('myComponent');
@ -200,17 +195,14 @@ class ViewTest extends TestCase
public function testKeepThePreviousStateOfDataSettedBySetMethod(): void
{
$this->view->preserveVars = false;
$this->view->set('prop', 'bar');
$html = <<<'html'
$html = self::removeLineEndings(<<<'html'
<div>qux</div>
<div>bar</div>
html; // phpcs:ignore
$html = str_replace(["\n", "\r"], '', $html);
html);
$this->expectOutputString($html);
self::expectOutputString($html);
$this->view->render('myComponent', ['prop' => 'qux']);
$this->view->render('myComponent');
@ -218,18 +210,15 @@ class ViewTest extends TestCase
public static function renderDataProvider(): array
{
$html1 = <<<'html'
$html1 = self::removeLineEndings(<<<'html'
<div>Hi</div>
<div></div>
html; // phpcs:ignore
html);
$html2 = <<<'html'
$html2 = self::removeLineEndings(<<<'html'
<input type="number" />
<input type="text" />
html; // phpcs:ignore
$html1 = str_replace(["\n", "\r"], '', $html1);
$html2 = str_replace(["\n", "\r"], '', $html2);
html);
return [
[
@ -244,4 +233,250 @@ class ViewTest extends TestCase
],
];
}
/** @dataProvider pagesDataProvider */
public function testItRendersComponent(
string $page,
string $expected,
array $props = []
): void {
$view = new View(__DIR__ . '/views');
$view->preserveVars = false;
$actual = $view->fetch("pages/$page", $props);
self::assertSame(
self::removeIndentation(self::removeLineEndings($expected)),
self::removeIndentation(self::removeLineEndings($actual)),
);
}
public function testItRendersComponentFromAnotherPath(): void
{
$view = new View(__DIR__ . '/views', 'f', __DIR__ . '/components');
$view->preserveVars = false;
$actual = $view->fetch('pages/page-with-component-from-another-path');
$expected = 'my-component-from-another-path';
self::assertSame(
self::removeIndentation(self::removeLineEndings($expected)),
self::removeIndentation(self::removeLineEndings($actual)),
);
}
public static function pagesDataProvider(): array
{
return [
[
'page-with-component-with-old-syntax',
<<<'html'
my-component
html,
],
[
'page-with-component-with-new-syntax',
<<<'html'
my-component
html,
],
[
'page-with-component-with-subcomponent',
<<<'html'
<div>
my-component-with-subcomponent
subcomponent
</div>
html,
],
[
'page-with-multiple-components',
<<<'html'
<ul>
<li>my-component</li>
<li>my-component</li>
</ul>
html,
],
[
'page-with-functional-component',
<<<'html'
my-functional-component
html,
],
[
'page-with-class-component',
<<<'html'
my-class-component
html,
],
[
'page-with-functional-component-with-props',
<<<'html'
functional-component-with-props: Astronaut Victoria
html,
['name' => 'Victoria', 'occupation' => 'Astronaut'],
],
[
'page-with-functional-component-with-individual-props',
<<<'html'
functional-component-with-individual-props: Astronaut Victoria
html,
['name' => 'Victoria', 'occupation' => 'Astronaut'],
],
[
'page-with-class-component-with-props',
<<<'html'
class-component-with-props: Astronaut Victoria
html,
['name' => 'Victoria', 'occupation' => 'Astronaut'],
],
[
'page-with-class-component-with-styles',
<<<'html'
<span class="my-class-component-with-styles">
my-class-component-with-styles
</span>
<style>
.my-class-component-with-styles {
color: red;
}
</style>
html,
],
[
'page-with-class-component-with-custom-style-tag',
<<<'html'
<span class="my-class-component-with-custom-style-tag">
my-class-component-with-custom-style-tag
</span>
<style media="print" data-component="my-class-component-with-custom-style-tag">
.my-class-component-with-custom-style-tag {
color: purple;
}
</style>
html,
],
[
'page-with-class-component-with-scripts',
<<<'html'
my-class-component-with-scripts
<script>console.log('my-class-component-with-scripts')</script>
html,
],
[
'page-with-class-component-with-custom-script-tag',
<<<'html'
my-class-component-with-custom-script-tag
<script type="module" data-component="my-class-component-with-custom-script-tag">
console.log('my-class-component-with-custom-script-tag')
</script>
html,
],
[
'page-with-class-component-that-extends-another-class-component',
<<<'html'
another-class-component extended by my-class-component-that-extends-another-class-component
html,
],
[
'page-with-component-with-one-prop',
<<<'html'
<html>
<body>
<h1>Hello, James</h1>
</body>
</html>
html,
['name' => 'James'],
],
[
'page-with-component-with-one-prop',
<<<'html'
<html>
<body>
<h1>Hello, Victoria</h1>
</body>
</html>
html,
['name' => 'Victoria'],
],
[
'page-with-component-with-two-props',
<<<'html'
<html>
<body>
<h1>Hello, Astronaut Victoria</h1>
</body>
</html>
html,
['name' => 'Victoria', 'occupation' => 'Astronaut'],
],
[
'page-with-three-different-components',
<<<'html'
my-component
my-functional-component
my-class-component
html,
],
[
'page-with-component-with-prop-which-value-contains-spaces-and-numbers',
<<<'html'
<h1>Hello, Constantine 1st the Great</h1>
html,
['name' => 'Constantine 1st the Great'],
],
[
'page-two-same-components-with-one-style-and-script-tag',
<<<'html'
<span class="my-class-component-with-styles">
my-class-component-with-styles
</span>
<style>
.my-class-component-with-styles {
color: red;
}
</style>
<span class="my-class-component-with-styles">
my-class-component-with-styles
</span>
my-class-component-with-scripts
<script>console.log('my-class-component-with-scripts')</script>
my-class-component-with-scripts
html,
],
];
}
/** @dataProvider prefixesDataProvider */
public function testRenderComponentsWithDifferentPrefixes(string $prefix): void
{
$view = new View(__DIR__ . '/views', $prefix);
$view->preserveVars = false;
$actual = $view->fetch('pages/page-with-component-with-custom-prefix', compact('prefix'));
$expected = 'my-component';
self::assertSame(
self::removeIndentation(self::removeLineEndings($expected)),
self::removeIndentation(self::removeLineEndings($actual)),
);
}
public static function prefixesDataProvider(): array
{
return [
['x'],
];
}
private static function removeLineEndings(string $subject): string
{
return str_replace(["\r", "\n"], '', $subject);
}
private static function removeIndentation(string $subject): string
{
return preg_replace('/\s{2,}/', '', $subject);
}
}

@ -0,0 +1 @@
my-component-from-another-path

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace tests\views\components;
use flight\template\Component;
use Override;
if (!class_exists('AnotherClassComponent')) {
class AnotherClassComponent extends Component
{
#[Override]
public function html(): string
{
return 'another-class-component';
}
}
}

@ -0,0 +1 @@
<h1>Hello, <?= $occupation ?> <?= $name ?></h1>

@ -0,0 +1 @@
<h1>Hello, <?= $name ?></h1>

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use tests\views\components\AnotherClassComponent;
require_once __DIR__ . '/another-class-component.php';
return new class extends AnotherClassComponent
{
#[Override]
public function html(): string
{
return parent::html() . ' extended by my-class-component-that-extends-another-class-component';
}
};

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
use flight\template\Component;
return new class extends Component
{
public function html(): string
{
return 'my-class-component-with-custom-script-tag';
}
public function js(): string
{
return <<<'js'
<script type="module" data-component="my-class-component-with-custom-script-tag">
console.log('my-class-component-with-custom-script-tag')
</script>
js;
}
};

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use flight\template\Component;
return new class extends Component
{
public function html(): string
{
return <<<'html'
<span class="my-class-component-with-custom-style-tag">
my-class-component-with-custom-style-tag
</span>
html;
}
public function css(): string
{
return <<<'css'
<style media="print" data-component="my-class-component-with-custom-style-tag">
.my-class-component-with-custom-style-tag {
color: purple;
}
</style>
css;
}
};

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
use flight\template\Component;
return new class($name, $occupation) extends Component
{
private string $name;
private string $occupation;
public function __construct(string $name, string $occupation)
{
$this->name = $name;
$this->occupation = $occupation;
}
public function html(): string
{
return "class-component-with-props: $this->occupation $this->name";
}
};

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
use flight\template\Component;
return new class extends Component
{
#[Override]
public function html(): string
{
return 'my-class-component-with-scripts';
}
#[Override]
public function js(): string
{
return <<<'js'
console.log('my-class-component-with-scripts')
js;
}
};

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use flight\template\Component;
return new class extends Component
{
#[Override]
public function html(): string
{
return <<<'html'
<span class="my-class-component-with-styles">
my-class-component-with-styles
</span>
html;
}
#[Override]
public function css(): string
{
return <<<'css'
.my-class-component-with-styles {
color: red;
}
css;
}
};

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
use flight\template\Component;
return new class extends Component
{
#[Override]
public function html(): string
{
return 'my-class-component';
}
};

@ -0,0 +1,4 @@
<div>
my-component-with-subcomponent
<f-subcomponent />
</div>

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
return fn (string $name, string $occupation): string =>
"functional-component-with-individual-props: $occupation $name";

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
return fn (...$props): string => sprintf(
'functional-component-with-props: %s %s',
$props[1] ?? '',
$props[0] ?? ''
);

@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
return fn (): string => 'my-functional-component';

@ -1 +1 @@
Hello, <?php echo $name; ?>!
Hello, <?= $name ?>!

@ -0,0 +1,5 @@
<f-my-class-component-with-styles />
<f-my-class-component-with-styles />
<f-my-class-component-with-scripts />
<f-my-class-component-with-scripts />

@ -0,0 +1 @@
<f-my-class-component-that-extends-another-class-component />

@ -0,0 +1 @@
<f-my-class-component-with-custom-script-tag />

@ -0,0 +1 @@
<f-my-class-component-with-custom-style-tag />

@ -0,0 +1 @@
<f-my-class-component-with-props name="<?= $name ?>" occupation="<?= $occupation ?>" />

@ -0,0 +1 @@
<f-my-class-component-with-scripts />

@ -0,0 +1 @@
<f-my-class-component-with-styles />

@ -0,0 +1 @@
<f-my-component-from-another-path />

@ -0,0 +1 @@
<?php $this->render('components/my-component') ?>

@ -0,0 +1,5 @@
<html>
<body>
<f-greeting name="<?= $name ?>" />
</body>
</html>

@ -0,0 +1 @@
<f-my-component-with-subcomponent />

@ -0,0 +1,5 @@
<html>
<body>
<f-greeting-with-two-props name="<?= $name ?>" occupation="<?= $occupation ?>" />
</body>
</html>

@ -0,0 +1 @@
<f-my-functional-component-with-individual-props occupation="<?= $occupation ?>" name="<?= $name ?>" />

@ -0,0 +1 @@
<f-my-functional-component-with-props name="<?= $name ?>" occupation="<?= $occupation ?>" />

@ -0,0 +1,7 @@
<ul>
<?php for ($i = 1; $i <= 2; ++$i) : ?>
<li>
<f-my-component />
</li>
<?php endfor ?>
</ul>

@ -0,0 +1,3 @@
<f-my-component />
<f-my-functional-component />
<f-my-class-component />

@ -1 +1 @@
Hello world, <?php echo $name; ?>!
Hello world, <?= $name ?>!
Loading…
Cancel
Save