Skip to content

Commit

Permalink
Merge pull request #20 from genius257/2.x
Browse files Browse the repository at this point in the history
2.x
  • Loading branch information
genius257 authored Sep 24, 2023
2 parents 5c4da62 + 90dc6a2 commit cdca7d4
Show file tree
Hide file tree
Showing 16 changed files with 636 additions and 322 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/codacy-coverage-reporter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ jobs:
- name: Composer (php-actions)
uses: php-actions/composer@v6
with:
php_version: "7.2"
php_version: "7.4"
version: 2.x
- name: PHPUnit (php-actions)
uses: php-actions/phpunit@v3
with:
configuration: phpunit.xml
version: "8.5"
php_version: "7.2"
version: "9.6"
php_version: "7.4"
php_extensions: "xdebug mbstring"
args: --coverage-clover phpunit-clover.xml
env:
Expand Down
8 changes: 6 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
}
],
"require": {
"php": ">=7.2",
"php": ">=7.4",
"paquettg/php-html-parser": "~3.1.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"phpunit/phpunit": "^9.0",
"phpstan/phpstan": "^1.9",
"squizlabs/php_codesniffer": "^3.7"
},
Expand All @@ -31,5 +31,9 @@
"App\\": "example/app/",
"Tests\\": "tests/"
}
},
"scripts": {
"phpunit-coverage": "XDEBUG_MODE=coverage phpunit --coverage-text",
"phpunit-coverage-html": "XDEBUG_MODE=coverage phpunit --coverage-html=coverage"
}
}
25 changes: 10 additions & 15 deletions docs/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,28 @@

## Usage

To use components, simply create a new class that extends from `Genius257\View\Component` and implement the `_render` method.
To use components, simply create a new class that extends from `Genius257\View\Component` and implement the `render` method.

### Attributes

Attributes for a view component are set via the protected property `$properties`.
Attributes for a view component are set via public properties.

It contains an associative array with the key being the attribute name and the value being the default value.

Components inherit attributes from parents, even when the `$properties` class property value is changed on the child class definition.

All components have a reserved property key named `"children"`. It contains an array of nested elements within the component html tag.
All components have a reserved property named `"trim"`. It is a boolean value, indicating if the render post process should trim the output.

### Render

The component render output is given via the `_render` method.
The component render output is given via the `render` method.

The method can return one of:
* a string
* Nothing but write directly to PHP output, for example via the echo function.
The method can give output via:
* A return statement.
* Writing to the output buffer, for example via the echo function.

But NOT both.
If both a return and content in the output buffer is given, the final render will be the a concatinated string, with output buffer first, return output second.

#### Stringable
casting this class to string, will result in the same as a `render` method call on the component
casting this class to string, will result in the same as making the View class render the component.

#### Nested render

The component MAY return or output html with another component tag.

That component tag will also be processed in the same view render call, until all component tags are processed.
Expand All @@ -37,7 +32,7 @@ That component tag will also be processed in the same view render call, until al

Component tags are html elements, where the HTML tag are the entire class reference string (Full namespace included)

Component tags MAY have children, if supported by the component referenced.
Component tags MAY have children, if they extend from the ComponentWithChildren class.

example:

Expand Down
142 changes: 9 additions & 133 deletions src/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

namespace Genius257\View;

use PHPHtmlParser\Dom\Node\HtmlNode;
use Genius257\View\Dom\Node\RootNode;
use Stringable;

/**
* @method $this setChildren(array $value)
*/
Expand All @@ -16,17 +12,7 @@ abstract class Component
*
* @var boolean
*/
protected $trim = true;

/**
* The supported attributes on the component.
*
* All keys in this list will have a setter method available
* named set followed by the property name with first letter uppercase.
*
* @var array<string, mixed>
*/
protected $properties = ['children' => null];
public $trim = true;

final public function __construct()
{
Expand All @@ -49,136 +35,26 @@ public static function make()
*/
public function getProperties()
{
return $this->properties;
}

/**
* Get component property value, or null if non existent.
*
* @return mixed|null
*/
public function getProperty(string $property)
{
return $this->getProperties()[$property] ?? null;
}
$reflectionClass = new \ReflectionClass($this);
$properties = $reflectionClass->getProperties(\ReflectionProperty::IS_PUBLIC);

/**
* Check if the component supports a property by name.
*
* @param string $property
*
* @return boolean
*/
public function hasProperty(string $property)
{
return array_key_exists($property, $this->properties);
}
$result = [];

/**
* Magic method for property setters support.
*
* @param string $method
* @param array<int, mixed> $arguments
*
* @return $this
*/
public function __call(string $method, $arguments)
{
if (!preg_match('/^set([A-Z].*)$/', $method, $matches)) {
throw new \Exception("Call to undefined method \"$method\"");
foreach ($properties as $property) {
$result[$property->getName()] = $property->getValue($this);
}

$property = lcFirst($matches[1]);

if (!array_key_exists($property, $this->properties)) {
$className = get_class($this);
throw new \Exception("Component property \"$property\" is not defined on \"$className\"");
}

$this->properties[$property] = $arguments[0] ?? null;

return $this;
}

/**
* Get *REAL* HTMLNode children.
*
* *REAL*, meaning that child components would produce a root HTMLNode,
* making changes to the child data appear as not working in some cases.
*
* @return HtmlNode[]
*/
public function getHTMLNodeChildren()
{
$HTMLNodes = [];
$children = $this->properties['children'] ?? [];
foreach ($children as $child) {
if ($child instanceof RootNode) {
foreach ($child->getChildren() as $child) {
if ($child instanceof HtmlNode) {
$HTMLNodes[] = $child;
}
}
} elseif ($child instanceof HtmlNode) {
$HTMLNodes[] = $child;
}
}

return $HTMLNodes;
}

/**
* Renders each child and returns the concatenated strings.
*
* @return string
*/
public function renderChildren(): string
{
return implode(
'',
array_map(
function ($child) {
return strval($child);
},
$this->properties['children'] ?? []
)
);
}

/**
* Renders the component and returns the resulting string.
*
* @return string|Stringable
*/
public function render()
{
ob_start();
//TODO: support the _render also being able to return value, but throw warning if neither ob_get_contents or the return value are empty!
$return = $this->_render();
$result = ob_get_contents();
ob_end_clean();
if ($return !== null) {
if ($result !== "" && $result !== false) {
$className = str_replace("\0", "", get_class($this));
throw new \ErrorException("component $className::_render produced content to the output buffer AND returned a non null value", 0, E_WARNING);
} else {
$result = $return;
}
}
if ($this->trim) {
$result = trim($result);
}
return $result;
}

/**
* Render component content.
* @return \Stringable|string|void
* @return Stringable|string|void
*/
abstract protected function _render();
abstract public function render();

public function __toString(): string
{
return $this->render();
return View::renderComponent($this);
}
}
76 changes: 76 additions & 0 deletions src/ComponentWithChildren.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace Genius257\View;

use Genius257\View\Dom\Node\RootNode;
use PHPHtmlParser\Dom\Node\HtmlNode;

abstract class ComponentWithChildren extends Component
{
/** @var array<int, HtmlNode|Component|Stringable|string> */
public array $children = [];

/**
* @param HtmlNode|Component|Stringable|string $child
*/
public static function childToString($child): string
{
if ($child instanceof Stringable) {
return $child->__toString();
}

if ($child instanceof HtmlNode) {
return $child->__toString();
}

if ($child instanceof Component) {
return View::renderComponent($child);
}

return strval($child);
}

/**
* Renders each child and returns the concatenated strings.
*
* @return string
*/
public function renderChildren(): string
{
return implode(
'',
array_map(
function ($child) {
return strval($child);
},
$this->children
)
);
}

/**
* Get *REAL* HTMLNode children.
*
* *REAL*, meaning that child components would produce a root HTMLNode,
* making changes to the child data appear as not working in some cases.
*
* @return HtmlNode[]
*/
public function getHTMLNodeChildren()
{
$HTMLNodes = [];
foreach ($this->children as $child) {
if ($child instanceof RootNode) {
foreach ($child->getChildren() as $child) {
if ($child instanceof HtmlNode) {
$HTMLNodes[] = $child;
}
}
} elseif ($child instanceof HtmlNode) {
$HTMLNodes[] = $child;
}
}

return $HTMLNodes;
}
}
20 changes: 12 additions & 8 deletions src/Dom/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@ public function parse(Options $options, Content $content, int $size): AbstractNo

/** @var \PHPHtmlParser\Dom\Node\HtmlNode|null $node */
$node = $tagDTO->getNode();
$activeNode->addChild($node);
if ($node !== null) {
$activeNode->addChild($node);

// check if node is self closing
if (!$node->getTag()->isSelfClosing()) {
$activeNode = $node;
// check if node is self closing
if (!$node->getTag()->isSelfClosing()) {
$activeNode = $node;
}
}
} elseif (
$options->isWhitespaceTextNode() ||
Expand Down Expand Up @@ -182,7 +184,9 @@ private function parseTag(Options $options, Content $content, int $size): TagDTO
// Should be a self closing tag, check if we are strict
if ($options->isStrict()) {
$character = $content->getPosition();
throw new StrictException("Tag '" . $node->getTag()->name() . "' is not self closing! (character #$character)");
throw new StrictException(
"Tag '" . $node->getTag()->name() . "' is not self closing! (character #$character)"
);
}

// We force self closing on this tag.
Expand Down Expand Up @@ -211,8 +215,6 @@ private function parseTag(Options $options, Content $content, int $size): TagDTO
* @param HtmlNode $node
* @param Options $options
* @param string|Tag $tag
*
* @return void
*/
private function setUpAttributes(Content $content, int $size, HtmlNode $node, Options $options, $tag): void
{
Expand Down Expand Up @@ -275,7 +277,9 @@ private function setUpAttributes(Content $content, int $size, HtmlNode $node, Op
if ($options->isStrict()) {
// can't have this in strict html
$character = $content->getPosition();
throw new StrictException("Tag '$tag' has an attribute '$name' with out a value! (character #$character)");
throw new StrictException(
"Tag '$tag' has an attribute '$name' without a value! (character #$character)"
);
}
$node->getTag()->setAttribute($name, null);
if ($content->char() != '>') {
Expand Down
Loading

0 comments on commit cdca7d4

Please sign in to comment.