Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic elements #7

Draft
wants to merge 36 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a4ab395
Add Element interface
dingo-d Nov 19, 2022
a627c92
Add abstract element class
dingo-d Nov 19, 2022
1f93550
Initial implementation of the mj-text element
dingo-d Nov 19, 2022
5728363
Update base test class
dingo-d Nov 19, 2022
4ea8626
Add tests for the mj-text element class
dingo-d Nov 19, 2022
312100f
Fix the phpcs issue in the abstract element class
dingo-d Nov 19, 2022
7a3cdd6
Add cleanup for test properties
dingo-d Nov 19, 2022
22a9ffe
Reorder allowed attributes in alphabetical order
dingo-d Nov 19, 2022
d977088
Remove gitkeep
dingo-d Dec 1, 2023
691a20e
Add tests for the parser, renderer and some elements
dingo-d Dec 1, 2023
3a1207a
Change parser to return a node instead of array of nodes
dingo-d Dec 1, 2023
aff8b15
Add renderer code
dingo-d Dec 1, 2023
b42bc18
Add conditional tag helper trait
dingo-d Dec 1, 2023
03fb6f9
Add element factory
dingo-d Dec 1, 2023
1e21128
Add abstract element class and the element class
dingo-d Dec 1, 2023
d8039d0
WIP text element
dingo-d Dec 1, 2023
1084974
Update dependencies
dingo-d Dec 3, 2023
6e4b960
Improve factory create method
dingo-d Dec 3, 2023
16bdc38
Add type validator helper
dingo-d Dec 3, 2023
4d0d579
Allow creation of dynamic properties
dingo-d Dec 3, 2023
b65abf8
Add additional test for the render method
dingo-d Dec 3, 2023
a30ff7d
WIP Modify the getHtmlAttributes and formatAttributes methods
dingo-d Dec 3, 2023
4e4a9fb
Add return type shape to the text element
dingo-d Dec 3, 2023
c1058d5
Add validators
dingo-d Jan 28, 2024
6440e33
Add additional tests for the element formatting and overriding
dingo-d Jan 28, 2024
cd5804b
Minor grammar fix in docblock
dingo-d Jan 28, 2024
dd5ea44
Add types to allowed attributes for validation
dingo-d Jan 28, 2024
07c4901
Update abstract element class
dingo-d Jan 28, 2024
3d92692
Remove renderContent method from the Element interface
dingo-d Jan 28, 2024
5dafac4
Initial addition of body element
dingo-d Jan 28, 2024
890c707
Extract the dependency on the validator out of the validation method
dingo-d Jan 29, 2024
2f1b31c
Refactor validation code
dingo-d Jan 29, 2024
ffce08a
Update the coverage composer command
dingo-d Jan 29, 2024
67a1da3
Update validators
dingo-d Jan 29, 2024
d0c6e8a
Update tests for validators
dingo-d Jan 29, 2024
ff6813b
Fix phpcs issues
dingo-d Jan 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
"ext-simplexml": "*"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.7",
"captainhook/captainhook": "^5.11",
"pestphp/pest": "^1.22",
"phpstan/phpstan": "^1.9",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcompatibility/php-compatibility": "^9.3",
"captainhook/captainhook": "^5.11",
"php-parallel-lint/php-parallel-lint": "^1.3"
"phpcsstandards/php_codesniffer": "^3.7",
"phpstan/phpstan": "^1.9"
},
"autoload": {
"psr-4": {
Expand All @@ -47,7 +47,7 @@
"test:types": "@php ./vendor/bin/phpstan",
"test:style": "@php ./vendor/bin/phpcs",
"test:unit": "@php ./vendor/bin/pest",
"test:coverage": "@php ./vendor/bin/pest --coverage",
"test:coverage": "@php -dxdebug.mode=coverage ./vendor/bin/pest --coverage",
"test": [
"@test:style",
"@test:types",
Expand Down
Empty file removed src/Elements/.gitkeep
Empty file.
341 changes: 341 additions & 0 deletions src/Elements/AbstractElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
<?php

/**
* PHP MJML Renderer library
*
* @package MadeByDenis\PhpMjmlRenderer
* @link https://github.com/dingo-d/php-mjml-renderer
* @license https://opensource.org/licenses/MIT MIT
*/

declare(strict_types=1);

namespace MadeByDenis\PhpMjmlRenderer\Elements;

use MadeByDenis\PhpMjmlRenderer\Validation\TypeValidator;

/**
* Mjml Text Element
*
* @link https://documentation.mjml.io/#mj-text
*
* @since 1.0.0
*/
abstract class AbstractElement implements Element
{
public const TAG_NAME = '';
public const ENDING_TAG = false;

protected bool $rawElement = false;

/**
* @var array<string, string>
*/
protected array $defaultAttributes = [];

/**
* @var array<string, array<string, string>>
*/
protected array $allowedAttributes = [];

/**
* @var array<string, string>
*/
protected array $attributes = [];

protected array $children = [];

Check failure on line 46 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$children type has no value type specified in iterable type array.

Check failure on line 46 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$children type has no value type specified in iterable type array.

Check failure on line 46 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$children type has no value type specified in iterable type array.

Check failure on line 46 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$children type has no value type specified in iterable type array.

protected array $properties = [];

Check failure on line 48 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$properties type has no value type specified in iterable type array.

Check failure on line 48 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$properties type has no value type specified in iterable type array.

Check failure on line 48 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$properties type has no value type specified in iterable type array.

Check failure on line 48 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$properties type has no value type specified in iterable type array.

protected array $globalAttributes = [

Check failure on line 50 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$globalAttributes type has no value type specified in iterable type array.

Check failure on line 50 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$globalAttributes type has no value type specified in iterable type array.

Check failure on line 50 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$globalAttributes type has no value type specified in iterable type array.

Check failure on line 50 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Property MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::$globalAttributes type has no value type specified in iterable type array.
'backgroundColor' => '',
'beforeDoctype' => '',
'breakpoint' => '480px',
'classes' => [],
'classesDefault' => [],
'defaultAttributes' => [],
'htmlAttributes' => [],
'fonts' => '',
'inlineStyle' => [],
'headStyle' => [],
'componentsHeadStyle' => [],
'headRaw' => [],
'mediaQueries' => [],
'preview' => '',
'style' => [],
'title' => '',
'forceOWADesktop' => false,
'lang' => 'und',
'dir' => 'auto',
];

/**
* @var array<mixed, mixed>
*/
protected array $context = [];
protected string $content = '';
protected ?string $absoluteFilePath = null;

public function __construct(?array $attributes = [], string $content = '')

Check failure on line 79 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::__construct() has parameter $attributes with no value type specified in iterable type array.

Check failure on line 79 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::__construct() has parameter $attributes with no value type specified in iterable type array.

Check failure on line 79 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::__construct() has parameter $attributes with no value type specified in iterable type array.

Check failure on line 79 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::__construct() has parameter $attributes with no value type specified in iterable type array.
{
$this->attributes = $this->formatAttributes(
$this->defaultAttributes,
$this->allowedAttributes,
$attributes,
);

$this->content = $content;
}

public function isEndingTag(): bool
{
return static::ENDING_TAG;
}

public function getTagName(): string
{
return static::TAG_NAME;
}

public function isRawElement(): bool
{
return $this->rawElement;
}

/**
* Get the allowed attribute info
*
* @param string $attributeName Name of the attribute.
* @param string $attributeProperty Name of attribute property.
*
* @return array<string, string>|string Array of properties in case the specific property is empty, property value if not.
*
* @throws \OutOfBoundsException In case attribute name is wrong or property doesn't exist.
*/
public function getAllowedAttributeData(string $attributeName, string $attributeProperty = '')
{
if (!isset($this->allowedAttributes[$attributeName])) {
throw new \OutOfBoundsException(
"Attribute {$attributeName} doesn't exist in the allowed attributes array."
);
}

if (empty($attributeProperty)) {
return $this->allowedAttributes[$attributeName];
}

if (!isset($this->allowedAttributes[$attributeName][$attributeProperty])) {
throw new \OutOfBoundsException(
"Property {$attributeProperty} doesn't exist in the {$attributeName} allowed attribute array."
);
}

return $this->allowedAttributes[$attributeName][$attributeProperty];
}

public function getChildContext(): array

Check failure on line 136 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getChildContext() return type has no value type specified in iterable type array.

Check failure on line 136 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getChildContext() return type has no value type specified in iterable type array.

Check failure on line 136 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getChildContext() return type has no value type specified in iterable type array.

Check failure on line 136 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getChildContext() return type has no value type specified in iterable type array.
{
return $this->context;
}

/**
* @param string $attributeName
* @return mixed|null
*/
public function getAttribute(string $attributeName)
{
return $this->attributes[$attributeName] ?? null;
}

/**
* Return the globally set attributes
*
* @return array<string, mixed>
*/
public function getGlobalAttributes(): array
{
return $this->globalAttributes;
}

// To-do: Override the globally set attributes if we override some from the CLI or some options.

protected function getContent(): string
{
return trim($this->content);
}

/**
* @param array<string, string> $attributes
*
* @return string|null
*/
protected function getHtmlAttributes(array $attributes): ?string
{
// $style is fetched from the $attributes array.
// If it's not empty, it's passed to the $this->styles() method.
$style = $attributes['style'] ?? '';

$specialAttributes = [
'style' => $this->styles($style),
'default' => $this->defaultAttributes,
];

$nonEmpty = array_filter($attributes, fn($element) => !empty($element));

$attrOut = '';

array_walk($nonEmpty, function ($val, $key) use (&$attrOut, $specialAttributes) {
$value = !empty($specialAttributes[$key]) ?
$specialAttributes[$key] :
$specialAttributes['default'];

$attrOut .= "$key=\"$value\"";

Check failure on line 192 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Part $value (array<string, string>|non-falsy-string) of encapsed string cannot be cast to string.

Check failure on line 192 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Part $value (array<string, string>|non-falsy-string) of encapsed string cannot be cast to string.

Check failure on line 192 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Part $value (array<string, string>|non-falsy-string) of encapsed string cannot be cast to string.

Check failure on line 192 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Part $value (array<string, string>|non-falsy-string) of encapsed string cannot be cast to string.
});

return trim($attrOut);
}

abstract public function getStyles(): array;

Check failure on line 198 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getStyles() return type has no value type specified in iterable type array.

Check failure on line 198 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getStyles() return type has no value type specified in iterable type array.

Check failure on line 198 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getStyles() return type has no value type specified in iterable type array.

Check failure on line 198 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::getStyles() return type has no value type specified in iterable type array.

protected function styles($styles): string

Check failure on line 200 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::styles() has parameter $styles with no type specified.

Check failure on line 200 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::styles() has parameter $styles with no type specified.

Check failure on line 200 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::styles() has parameter $styles with no type specified.

Check failure on line 200 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::styles() has parameter $styles with no type specified.
{
$stylesArray = [];

if (!empty($styles)) {
if (is_string($styles)) {
$stylesArray = $this->getStyles()[$styles];
} else {
$stylesArray = $styles;
}
}

$styles = '';

array_walk($stylesArray, function ($val, $key) use (&$styles) {
if (!empty($val)) {
$styles .= "$key:$val;";
}
});

return trim($styles);
}

protected function renderChildren($children, $options = [])

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has no return type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.1, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has parameter $children with no type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has no return type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.2, true)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has parameter $children with no type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has no return type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (8.0, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has parameter $children with no type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has no return type specified.

Check failure on line 223 in src/Elements/AbstractElement.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static analysis checks (7.4, false)

Method MadeByDenis\PhpMjmlRenderer\Elements\AbstractElement::renderChildren() has parameter $children with no type specified.
{

$children = $children ?? $this->children;

// const {
// props = {},
// renderer = component => component.render(),
// attributes = {},
// rawXML = false,
// } = options
//
// children = children || this.props.children
//
// if (rawXML) {
// return children.map(child => jsonToXML(child)).join('\n')
// }
//
// const sibling = children.length
//
// const rawComponents = filter(this.context.components, c => c.isRawElement())
// const nonRawSiblings = children.filter(
// child => !find(rawComponents, c => c.getTagName() === child.tagName),
// ).length
//
// let output = ''
// let index = 0
//
// forEach(children, children => {
// const component = initComponent({
// name: children.tagName,
// initialDatas: {
// ...children,
// attributes: {
// ...attributes,
// ...children.attributes,
// },
// context: this.getChildContext(),
// props: {
// ...props,
// first: index === 0,
// index,
// last: index + 1 === sibling,
// sibling,
// nonRawSiblings,
// },
// },
// })
//
// if (component !== null) {
// output += renderer(component)
// }
//
// index++ // eslint-disable-line no-plusplus
// })
return $output;
}

private function formatAttributes(
array $defaultAttributes,
array $allowedAttributes,
?array $passedAttributes = []
): array {
/*
* Check if the attributes are of the proper format based on the allowed attributes.
* For instance, if you pass a non string value to the 'align' attribute, you should get an error.
* Otherwise you'd get an array of attributes like:
*
* [
* 'background-repeat' => 'repeat',
* 'background-size' => 'auto',
* 'background-position' => 'top center',
* 'direction' => 'ltr',
* 'padding' => '20px 0',
* 'text-align' => 'center',
* 'text-padding' => '4px 4px 4px 0'
* ]
*/

// Check if the passedAttributes is empty or not, if it is, return the default attributes.
if (empty($passedAttributes)) {
return $defaultAttributes;
}

// 1. Check if the $passedAttributes are of the proper format based on the $allowedAttributes.
$result = [];

// Append `mj-class` to the allowed attributes.
$allowedAttributes['mj-class'] = [
'unit' => 'string',
'description' => 'class name, added to the root HTML element created',
'default_value' => 'n/a',
];

foreach ($passedAttributes as $attrName => $attrVal) {
if (!isset($allowedAttributes[$attrName])) {
throw new \InvalidArgumentException(
"Attribute {$attrName} is not allowed."
);
}

$typeConfig = $allowedAttributes[$attrName];
$validator = new TypeValidator();

$typeValue = $typeConfig['type'];

if (!$validator->getValidator($typeValue)->isValid($attrVal)) {
throw new \InvalidArgumentException(
"Attribute {$attrName} must be of type {$typeValue}, {$attrVal} given."
);
}

$result[$attrName] = $attrVal;
}

// 2. Check what attributes are the same in the $defaultAttributes and override them, and return them.
return $result + $defaultAttributes;
}
}