diff --git a/README.md b/README.md index 8b327e4..6217001 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,16 @@ The `/comparisons` folder can be used to run performance tests locally to compar 4. PHP HTML Builder only (the Document class), and 5. PHP HTML Builder only using PSR-4 autoloading. +These results are from August 22nd, 2026 (running on PHP 8.5): + +|Configuration |Average time in milliseconds |Median time in milliseconds |Transfer size in kilobytes | +|:---|---:|---:|---:| +|1 |0.0022 |0.0020 |2.7 | +|2 |0.0151 |0.0125 |1.99 | +|3 |0.4314 |0.3025 |1.82 | +|4 |1.4446 |0.3672 |1.74 | +|5 |0.9059 |0.6750 |1.74 | + These results are from February 26th, 2024: |Configuration |Average time in milliseconds |Median time in milliseconds |Transfer size in kilobytes | diff --git a/comparisons/php-html-builder-document-psr4/index.php b/comparisons/php-html-builder-document-psr4/index.php index 5ccbc1a..7568b70 100644 --- a/comparisons/php-html-builder-document-psr4/index.php +++ b/comparisons/php-html-builder-document-psr4/index.php @@ -95,22 +95,22 @@ $sections = []; foreach ($posts as $post) { - $sections[] = \Eightfold\HTMLBuilder\Element::section( - \Eightfold\HTMLBuilder\Element::h2($post['title']), - \Eightfold\HTMLBuilder\Element::p($post['description']), - \Eightfold\HTMLBuilder\Element::p( - \Eightfold\HTMLBuilder\Element::a($post['url']['text']) + $sections[] = \Eightfold\HtmlBuilder\Element::section( + \Eightfold\HtmlBuilder\Element::h2($post['title']), + \Eightfold\HtmlBuilder\Element::p($post['description']), + \Eightfold\HtmlBuilder\Element::p( + \Eightfold\HtmlBuilder\Element::a($post['url']['text']) ->props('href ' . $post['url']['href']) ) ); } -print \Eightfold\HTMLBuilder\Document::create( +print \Eightfold\HtmlBuilder\Document::create( $pageTitle )->body( - \Eightfold\HTMLBuilder\Element::h1($pageTitle), - \Eightfold\HTMLBuilder\Element::a('time')->props('href #bottom'), - \Eightfold\HTMLBuilder\Element::article(...$sections) + \Eightfold\HtmlBuilder\Element::h1($pageTitle), + \Eightfold\HtmlBuilder\Element::a('time')->props('href #bottom'), + \Eightfold\HtmlBuilder\Element::article(...$sections) ); $end = hrtime(true); diff --git a/comparisons/php-html-builder-document/index.php b/comparisons/php-html-builder-document/index.php index 8bb82a3..493d6f5 100644 --- a/comparisons/php-html-builder-document/index.php +++ b/comparisons/php-html-builder-document/index.php @@ -6,16 +6,6 @@ $start = hrtime(true); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Contracts/Contentable.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Contracts/ContentWithoutElement.php'); - -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Implementations/Properties.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Implementations/Contentable.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Implementations/ContentWithoutElement.php'); - -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Concatenate.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Element.php'); - require_once(__DIR__ . '/../../src/Document.php'); require_once(__DIR__ . '/../../src/Element.php'); @@ -106,22 +96,22 @@ $sections = []; foreach ($posts as $post) { - $sections[] = \Eightfold\HTMLBuilder\Element::section( - \Eightfold\HTMLBuilder\Element::h2($post['title']), - \Eightfold\HTMLBuilder\Element::p($post['description']), - \Eightfold\HTMLBuilder\Element::p( - \Eightfold\HTMLBuilder\Element::a($post['url']['text']) + $sections[] = \Eightfold\HtmlBuilder\Element::section( + \Eightfold\HtmlBuilder\Element::h2($post['title']), + \Eightfold\HtmlBuilder\Element::p($post['description']), + \Eightfold\HtmlBuilder\Element::p( + \Eightfold\HtmlBuilder\Element::a($post['url']['text']) ->props('href ' . $post['url']['href']) ) ); } -print \Eightfold\HTMLBuilder\Document::create( +print \Eightfold\HtmlBuilder\Document::create( $pageTitle )->body( - \Eightfold\HTMLBuilder\Element::h1($pageTitle), - \Eightfold\HTMLBuilder\Element::a('time')->props('href #bottom'), - \Eightfold\HTMLBuilder\Element::article(...$sections) + \Eightfold\HtmlBuilder\Element::h1($pageTitle), + \Eightfold\HtmlBuilder\Element::a('time')->props('href #bottom'), + \Eightfold\HtmlBuilder\Element::article(...$sections) ); $end = hrtime(true); diff --git a/comparisons/php-html-builder-element/index.php b/comparisons/php-html-builder-element/index.php index 126045a..a8a0939 100644 --- a/comparisons/php-html-builder-element/index.php +++ b/comparisons/php-html-builder-element/index.php @@ -6,16 +6,6 @@ $start = hrtime(true); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Contracts/Contentable.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Contracts/ContentWithoutElement.php'); - -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Implementations/Properties.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Implementations/Contentable.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Implementations/ContentWithoutElement.php'); - -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Concatenate.php'); -require_once(__DIR__ . '/../../vendor/8fold/php-xml-builder/src/Element.php'); - require_once(__DIR__ . '/../../src/Element.php'); $pageTitle = "Page title here"; @@ -105,11 +95,11 @@ $sections = []; foreach ($posts as $post) { - $sections[] = \Eightfold\HTMLBuilder\Element::section( - \Eightfold\HTMLBuilder\Element::h2($post['title']), - \Eightfold\HTMLBuilder\Element::p($post['description']), - \Eightfold\HTMLBuilder\Element::p( - \Eightfold\HTMLBuilder\Element::a($post['url']['text']) + $sections[] = \Eightfold\HtmlBuilder\Element::section( + \Eightfold\HtmlBuilder\Element::h2($post['title']), + \Eightfold\HtmlBuilder\Element::p($post['description']), + \Eightfold\HtmlBuilder\Element::p( + \Eightfold\HtmlBuilder\Element::a($post['url']['text']) ->props('href ' . $post['url']['href']) ) ); @@ -125,7 +115,7 @@

time

- + content . ' -->' . "\n"; + } +} diff --git a/src/Components/Copyright.php b/src/Components/Copyright.php index 985c8ac..0588ffd 100644 --- a/src/Components/Copyright.php +++ b/src/Components/Copyright.php @@ -1,11 +1,11 @@ appName() ); } - return (string) Concatenate::create(...$elements); + return (string) implode('', $elements); } } diff --git a/src/Components/PageTitle.php b/src/Components/PageTitle.php index 1e42a42..1cc2f8a 100644 --- a/src/Components/PageTitle.php +++ b/src/Components/PageTitle.php @@ -1,11 +1,11 @@ title(); if ( @@ -79,6 +79,11 @@ public function __toString(): string return $doctype . $html; } + public function __toString(): string + { + return $this->toString(); + } + private function title(): string|Stringable { return $this->title; diff --git a/src/Element.php b/src/Element.php index 45c0383..c5da44c 100644 --- a/src/Element.php +++ b/src/Element.php @@ -1,13 +1,11 @@ $content + */ + public static function __callStatic(string $name, array $content = []): static + { + return new static($name, $content); + } + + /** + * @param array $content + * @param string[] $properties + */ + final private function __construct( + private string $name, + private array $content = [], + private array $properties = [], + private bool $omitEndTag = false, + ) { + } + + public function props(string ...$properties): static + { + $this->properties = $properties; + return $this; + } + + public function prop(string $prop): static + { + $this->properties[] = $prop; + return $this; + } + + public function omitEndTag(): self + { + $this->omitEndTag = true; + return $this; + } + + public function toString(): string { - return '>'; + return $this->opening() + . implode('', array_map('strval', $this->content)) + . $this->closing(); + } + + public function __toString(): string + { + return $this->toString(); + } + + private function opening(): string + { + if ($this->omitEndTag) { + return '<' . $this->name . $this->propertiesString() . '>'; + } + + return '<' . $this->name . $this->propertiesString() . '>'; + } + + private function closing(): string + { + return $this->omitEndTag ? '' : 'name . '>'; } protected function propertiesString(): string { - if ($this->properties() === []) { + if ($this->properties === []) { return ''; } $ordered = []; - $other = []; + $other = []; $boolean = []; - foreach ($this->properties() as $property) { + foreach ($this->properties as $property) { [$attr, $value] = array_pad(explode(' ', $property, 2), 2, ''); if ($value === '') { @@ -75,10 +137,13 @@ protected function propertiesString(): string if (in_array($attr, self::ORDERED, true)) { $ordered[$attr] = $value; + } elseif ($attr === $value) { $boolean[$attr] = true; + } else { $other[$attr] = $value; + } } diff --git a/src/Forms/Select.php b/src/Forms/Select.php index fa4d951..84be7e5 100644 --- a/src/Forms/Select.php +++ b/src/Forms/Select.php @@ -1,11 +1,13 @@ $options @@ -51,22 +51,19 @@ public function wrapperProps(string ...$properties): self public function dropdown(): self { - $this->dropdown = true; - $this->checkbox = false; + $this->type = SelectType::Dropdown; return $this; } public function radio(): self { - $this->dropdown = false; - $this->checkbox = false; + $this->type = SelectType::Radio; return $this; } public function checkbox(): self { - $this->checkbox = true; - $this->dropdown = false; + $this->type = SelectType::Checkbox; return $this; } @@ -88,7 +85,7 @@ private function hasSelected(): bool */ private function selected(): string|array { - if ($this->checkbox) { + if ($this->type === SelectType::Checkbox) { if (is_array($this->selected)) { return $this->selected; } @@ -115,7 +112,7 @@ private function isSelected(string $value): bool public function __toString(): string { - if ($this->dropdown) { + if ($this->type === SelectType::Dropdown) { return (string) $this->selectDropdown(); } return (string) $this->selectOther(); @@ -146,16 +143,16 @@ private function selectDropdown(): Element private function selectOther(): Element { $elements = []; - $type = 'radio'; - if ($this->checkbox) { - $type = 'checkbox'; - } + $type = $this->type === SelectType::Checkbox ? 'checkbox' : 'radio'; foreach ($this->options as $value => $content) { $value = (string) $value; $id = $this->name . '-' . $value; $label = Element::label($content)->props('for ' . $id); $input = Element::input()->omitEndTag()->props( 'id ' . $id, + ($this->type === SelectType::Checkbox) + ? 'name ' . $this->name . '[]' + : 'name ' . $this->name, 'type ' . $type, 'value ' . $value ); diff --git a/src/Forms/SelectType.php b/src/Forms/SelectType.php new file mode 100644 index 0000000..7547dc0 --- /dev/null +++ b/src/Forms/SelectType.php @@ -0,0 +1,9 @@ +Select your option
+
Select your option
html; $result = (string) Select::create( @@ -34,7 +34,7 @@ public function can_be_checkboxes(): void // phpcs: ignore public function can_be_radio_buttons(): void // phpcs: ignore { $expected = <<Select your option
+
Select your option
html; $result = (string) Select::create( @@ -49,6 +49,43 @@ public function can_be_radio_buttons(): void // phpcs: ignore $this->assertSame($expected, $result); } + #[Test] + public function dropdown_mode_is_explicit(): void // phpcs: ignore + { + $expected = << + html; + + $result = (string) Select::create( + 'Select your option', + 'select', + [ + 'value' => 'display' + ] + )->radio()->dropdown(); + + $this->assertSame($expected, $result); + } + + #[Test] + public function checkbox_mode_is_explicit(): void // phpcs: ignore + { + $expected = <<Select your option
+ html; + + $result = (string) Select::create( + 'Select your option', + 'select', + [ + 'value' => 'display' + ], + 'value' + )->radio()->checkbox(); + + $this->assertSame($expected, $result); + } + #[Test] public function can_add_properties_to_wrapper(): void // phpcs: ignore { diff --git a/tests/PerformanceTest.php b/tests/PerformanceTest.php index ddb7d0a..cd9291a 100644 --- a/tests/PerformanceTest.php +++ b/tests/PerformanceTest.php @@ -1,17 +1,16 @@ assertLessThan(1.5, $ms); + $this->assertLessThan(0.75, $ms); } #[Test]