piecesphp / html
Clases que generan HTML
Requires
- php: >=8.5 <8.6
- piecesphp/datastructures: ^4.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpunit/phpunit: ^10.5
- rector/rector: ^2.3
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Genera HTML desde PHP con objetos: elementos con atributos e hijos, campos de formulario, tablas, y un formateador que indenta el resultado.
Es parte del framework PiecesPHP, pero no depende de él.
Seguridad: escapa por defecto
El texto de un elemento y el valor de cada atributo se escapan al pintarlos (htmlspecialchars con
ENT_QUOTES | ENT_SUBSTITUTE y UTF-8). Un valor que venga del usuario se puede pasar tal cual:
$p = new HtmlElement('p', $valorDelUsuario, null, ['title' => $valorDelUsuario]);
- No lo escapes tú antes: saldría escapado dos veces (
&amp;). Las versiones anteriores a la 4.0.0 no escapaban y este README pedía escapar antes; ya no. &sale como&y'como'. Es HTML válido y el navegador lo muestra igual.- Los hijos no se escapan otra vez: ya se pintan escapados por su cuenta.
- Si el HTML es tuyo y de confianza (nunca de un usuario), mételo con
setRawContent(), o como hijos. Ver «HTML de confianza». - El nombre de la etiqueta y el de cada atributo no se escapan: se validan. Un nombre raro lanza una excepción (ver «Nombres válidos»).
Requisitos
- PHP
>=8.5 <8.6, conmbstring. piecesphp/datastructures^4.0(se instala solo).
Instalación
composer require piecesphp/html
Qué trae
Todo bajo PiecesPHP\Core\HTML:
| Clase | Qué es |
|---|---|
HtmlElement | Un elemento cualquiera: etiqueta, texto, hijos y atributos |
Attribute | Un atributo con uno o varios valores |
Form, Input, Select, Button | Formularios y sus campos |
Table | Una tabla a partir de nombres de columna y filas |
FormatHtml | Indenta y embellece una cadena HTML |
Collections\AttributeArray, ElementArray, FormElementArray, HtmlElementArray | Colecciones tipadas |
Interfaces\Element, Interfaces\FormElement | Los contratos |
Exceptions\MalformedAttributeException, MalformedChildException, MalformedTagException | Atributo, hijo o etiqueta inválidos |
Uso
Un elemento con atributos e hijos
use PiecesPHP\Core\HTML\HtmlElement;
$enlace = new HtmlElement('a', 'Ver más', null, ['href' => '/articulos', 'class' => 'btn primario']);
$enlace->getAttributeInstance('class')->appendValue('grande');
$caja = HtmlElement::create('div', '', [$enlace]);
echo $caja; // o: $html = $caja->render(false);
render()sin argumento haceechoademás de devolver la cadena. Para solo obtenerla:render(false)o(string) $elemento.- La salida pasa siempre por
FormatHtml::format(), que la indenta.
HTML de confianza
El texto se escapa, así que <b> sale literal. Para HTML que escribes tú, setRawContent():
$p = (new HtmlElement('p', 'a & b '))->setRawContent('<b>negrita</b>');
echo $p->render(false);
// <p>a & b
// <b>negrita</b>
// </p>
// (render() pasa por FormatHtml, que indenta y parte en líneas)
- El contenido crudo sale sin tocar, después del texto y antes de los hijos. Es el orden de
innerHTML(). - Otra forma de meter HTML sin escapar es construirlo con hijos (
appendChild()): cada hijo se pinta bien por sí mismo. getRawContent()lo devuelve.getText()devuelve el texto tal como lo diste, sin escapar.HtmlElement::escape($cadena)expone el mismo escape, por si lo necesitas fuera de un elemento.setRawContent()es deHtmlElement, no de la interfazElement.- Nunca pases a
setRawContent()un valor que venga del usuario: ahí no se escapa nada.
Un elemento sin cierre
$salto = new HtmlElement('br', '', null, null, false); // <br>
Atributos
$caja->setAttribute('data-id', '15');
$enlace->getAttribute('class'); // "btn primario grande", como cadena
$enlace->getAttribute('class', false); // ['btn', 'primario', 'grande'], como lista
$caja->getAttribute('class'); // null: la caja no tiene clase
$caja->removeAttribute('data-id');
Un valor escalar se parte por el separador (un espacio por defecto): 'btn primario' son dos valores.
getAttribute() y Attribute::getValue() devuelven el valor crudo; el escape ocurre solo al pintar
(Attribute::__toString()).
Nombres válidos
- Etiqueta: una letra seguida de letras, dígitos o guiones (
^[A-Za-z][A-Za-z0-9-]*$). Si no, el constructor deHtmlElementlanzaMalformedTagException. - Atributo: no vacío y sin espacios, comillas,
>,/,=ni caracteres de control. Si no,new Attribute(...)(y por tantosetAttribute()) lanzaMalformedAttributeException.
new HtmlElement('div onclick=x'); // MalformedTagException
$caja->setAttribute('x" onclick="y', '1'); // MalformedAttributeException
Tablas
use PiecesPHP\Core\DataStructures\StringArray;
use PiecesPHP\Core\HTML\Table;
$tabla = new Table(new StringArray(['Nombre', 'Acción']));
$tabla->addRow(['Ana & Luis', $enlace]); // texto escapado; un Element entra como hijo de la celda
$tabla->printTable(false);
Una celda que es un Element entra como hijo del td; cualquier otra se convierte a cadena y se escapa.
Formularios
use PiecesPHP\Core\HTML\Form;
$form = new Form('POST', '/guardar');
$form->appendFieldSemantic(Form::text('nombre', null, 'Tu nombre'), 'Nombre', true);
$form->appendFieldSemantic(Form::email('correo'), 'Correo');
echo $form->render(false);
Fábricas estáticas de Form: label(), submit(), text(), hidden(), email(), password() y select().
Formatear HTML existente
use PiecesPHP\Core\HTML\FormatHtml;
echo FormatHtml::format('<div><p>Hola</p></div>');
Referencia de HtmlElement
| Método | Qué hace |
|---|---|
__construct(string $tag, string $text = '', $childs = null, $attributes = null, bool $doubleTag = true) | $childs: un elemento, un array de elementos o una colección. $attributes: ['nombre' => valor], un Attribute o un AttributeArray |
static create(...) | Lo mismo, como fábrica |
appendChild(Element $child) | Añade un hijo. Devuelve $this |
innerHTML(?Element $html = null) | Sin argumento, el texto escapado, luego el contenido crudo y luego los hijos renderizados; con uno, sustituye los hijos |
setRawContent(string $html) / getRawContent() | HTML de confianza, sin escapar. setRawContent() devuelve $this |
getText() | El texto como se dio, sin escapar |
static escape(string $value) | El escape que usa el paquete |
clearChilds() | Quita los hijos |
setAttribute(string $name, $value = null, ?string $separator = null) | Devuelve $this |
getAttribute(string $name, bool $outputAsString = true) | Cadena, lista o null |
getAttributeInstance(string $name) / removeAttribute(string $name) / getAttributes(bool $outputAsString = true) | |
render($echo = true): string | Ver arriba |
- Un hijo de un tipo no admitido lanza
MalformedChildException. - Una etiqueta inválida lanza
MalformedTagException(ver «Nombres válidos»). $attributesde un tipo no admitido en el constructor se ignora en silencio:'x',5, un objeto cualquiera o una lista sin claves (['a', 'b']) dan un elemento sin atributos, sin error.setAttribute()con un valor que no es escalar, array ninull(por ejemplo, un objeto) lanzaMalformedAttributeException. Dentro de un array, los valores no escalares se descartan en silencio.
Demo y pruebas
demo/index.php: un ejemplo mínimo con un enlace.tests/FormatHtmlTest.php: 7 pruebas del formateador.tests/HtmlElementEscapeTest.php: 17 pruebas del escape, el contenido crudo, la validación de nombres y las celdas deTable. Siguen sin pruebas el resto deHtmlElement,Attributey los formularios (más allá del escape deInput,Select,Buttony la etiqueta deForm).
composer install
php examples/readme.php # ejecuta los ejemplos de este README; sale con 1 si alguno no da lo que dice
vendor/bin/phpunit
Cambios
Ver CHANGELOG.md.
Licencia
MIT. Ver LICENSE.