PHPackages                             concept-labs/phtmal - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. concept-labs/phtmal

ActiveLibrary[Templating &amp; Views](/categories/templating)

concept-labs/phtmal
===================

(C)oncept-Labs Abstract HTML

1.3.1(7mo ago)09MITPHPPHP &gt;=8.2

Since Aug 8Pushed 7mo ago1 watchersCompare

[ Source](https://github.com/Concept-Labs/phtmal)[ Packagist](https://packagist.org/packages/concept-labs/phtmal)[ RSS](/packages/concept-labs-phtmal/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (6)DependenciesVersions (7)Used By (0)

Phtmal
======

[](#phtmal)

Tiny, fluent HTML node tree with a minimal CSS-like selector engine — built to be lightweight, readable, and extensible. Designed for future integration with a `layout` package (name TBD) and for use in server-side rendering scenarios.

> **Highlights**
>
> - Fluent builder via `__call()` (`$ul->li('A')->end()`), with optional subtree callbacks
> - Pretty vs minified rendering
> - Safe text nodes and explicit `raw()` nodes
> - Normalized attributes (boolean attrs supported)
> - Minimal CSS-like querying (tag, `*`, `#id`, `.class`, `[attr]`, combinators ` ` `>` `+` `~`, `:first-child`, `:last-child`, `:nth-child`)
> - Extensible: subclasses can override behavior and constants; interfaces define the contract
> - **NEW:** Parse raw HTML into a Phtmal tree via `HtmlParserInterface` + `DomHtmlParser`

---

Installation
------------

[](#installation)

Once published to Packagist:

```
composer require concept-labs/phtmal
```

For local/VCS use meanwhile:

**Path repository (local dev):**

```
{
  "repositories": [
    { "type": "path", "url": "../phtmal" }
  ],
  "require": {
    "concept-labs/phtmal": "*"
  }
}
```

**VCS repository (GitHub):**

```
{
  "repositories": [
    { "type": "vcs", "url": "https://github.com/Concept-Labs/phtmal" }
  ],
  "require": {
    "concept-labs/phtmal": "dev-main"
  }
}
```

---

Quick start
-----------

[](#quick-start)

```
use Concept\Phtmal\Phtmal;

$html = (new Phtmal('ul'))
    ->li(['class' => 'item'], function (Phtmal $li) {
        $li->span('A');
    })
    ->li('B')->end()
->top();

echo $html->render();   // pretty
echo (string)$html;     // minified
```

**Attributes &amp; boolean attributes:**

```
$btn = (new Phtmal('button', 'Save'))
    ->class('btn', 'btn-primary')
    ->attr('disabled', ['disabled']); // short boolean form → …
```

**Text vs raw HTML:**

```
$div = (new Phtmal('div'))->text('Safe text'); // escaped
$div->raw('UNSAFE');                           // unescaped (use with care)
```

**Querying:**

```
$items = $html->query('li.item:first-child, li.item:last-child');
$second = $html->queryOne('#main > .card:nth-child(2)');
```

---

HTML parsing (NEW)
------------------

[](#html-parsing-new)

You can parse raw HTML (documents or fragments) into a `Phtmal` tree using the parser interface.

### Interfaces

[](#interfaces)

- `HtmlParserInterface` — contract:
    - `parseDocument(string $html, array $options = []): PhtmalNodeInterface`
    - `parseFragment(string $html, string $containerTag = 'div', array $options = []): PhtmalNodeInterface`
- `DomHtmlParser` — DOMDocument-based implementation (tolerant to malformed HTML).

### Usage

[](#usage)

**Parse a full document:**

```
use Concept\Phtmal\DomHtmlParser;

$parser = new DomHtmlParser();
$root = $parser->parseDocument('t');

// $root is the  node
echo $root->render();       // pretty
echo (string)$root;         // minified
```

**Parse a fragment (no implied `/`):**

```
$parser = new DomHtmlParser();
$list = $parser->parseFragment('AB', 'ul');

echo (string)$list; // AB
```

**Scripts/styles are imported as RAW nodes (not escaped):**

```
$parser = new DomHtmlParser();
$div = $parser->parseFragment('if (a < b) { alert("x"); }', 'div');
echo (string)$div;
// if (a < b) { alert("x"); }
```

**Custom factory (use your subclass of Phtmal):**

```
class MyNode extends Concept\Phtmal\Phtmal {}
$parser = new DomHtmlParser(fn(string $tag, ?string $text, array $attr) => new MyNode($tag, $text, $attr));
$tree = $parser->parseFragment('Hello', 'div');
```

### Options

[](#options)

`parseDocument()` and `parseFragment()` accept the same `$options` array:

OptionTypeDefaultDescription`dropComments``bool``true`Drop HTML comments.`preserveWhitespace``bool``false`Keep whitespace-only text nodes outside `/`.`preservePreWhitespace``bool``true`Preserve whitespace in `` / ``.`encoding``string``'UTF-8'`Input encoding hint for DOMDocument.`rawTextTags``string[]``['script','style']`Treat content of these tags as RAW (unescaped).---

Interfaces (core)
-----------------

[](#interfaces-core)

The library is interface-first. Documentation primarily lives on interfaces; implementations use `{@inheritDoc}`.

- `PhtmalNodeInterface` — node contract (fluent API, rendering, navigation, query integration).
- `SelectorInterface` — static querying: `select(PhtmalNodeInterface $root, string $selector): array`.
- `HtmlParserInterface` — parse raw HTML into a Phtmal tree.

Key guarantees:

- Implementations **escape** text on render (except explicit `#raw` nodes).
- Attributes are normalized to **lists of strings**, enabling predictable rendering and boolean-shortcuts.
- Children order is **stable**.

---

Core API (from `PhtmalNodeInterface`)
-------------------------------------

[](#core-api-from-phtmalnodeinterface)

```
// Builder & structure
__call(string $tag, array $args): PhtmalNodeInterface
end(): PhtmalNodeInterface
top(): PhtmalNodeInterface
append(PhtmalNodeInterface|string $nodeOrText): static
raw(string $html): static

// Content & attributes
text(?string $text): static
attr(string $name, string|array|null $value = null): static
id(string $id): static
class(string ...$class): static
data(string $key, string $value): static
aria(string $key, string $value): static

// Navigation & mutation
parent(): ?PhtmalNodeInterface
firstChild(): ?PhtmalNodeInterface
nextSibling(): ?PhtmalNodeInterface
cloneDeep(): PhtmalNodeInterface
detach(): static
replaceWith(PhtmalNodeInterface $node): PhtmalNodeInterface

// Rendering
render(bool $minify = false, int $indentLevel = 0): string

// Querying
query(string $selector): array
queryOne(string $selector): ?PhtmalNodeInterface

// Meta
getTag(): string
```

Notes:

- `__call('li', [...])` supports either `(text, attrs)` or `(attrs, callback)` — if a **callback** is the last argument, the method returns the **parent** (auto-jump back). Otherwise, it returns the new **child** (you can `->end()` manually).
- Boolean attributes are rendered in short form if normalized as `['disabled' => ['disabled']]`.

---

Extensibility recommendations
-----------------------------

[](#extensibility-recommendations)

- Overridable constants (protected): `VOID_ELEMENTS`, `INDENT`, `NL`.
- Overridable hooks (protected): `newNode()`, `escape()`, `renderAttributes()`, `_childrenRef()`.
- Open state (protected): `parent`, `children`, `tag`, `attributes`, `text`.

---

Testing &amp; QA
----------------

[](#testing--qa)

Install dev tools:

```
composer require --dev phpunit/phpunit:^10 phpstan/phpstan:^1.11
```

Run tests and static analysis:

```
vendor/bin/phpunit
vendor/bin/phpstan analyse
```

---

License
-------

[](#license)

MIT

###  Health Score

33

—

LowBetter than 75% of packages

Maintenance62

Regular maintenance activity

Popularity4

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~7 days

Total

6

Last Release

238d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/39bcfd110a5cc1f2d7ff687193670d00133cf415ad2b9959afaff24ff1e564fd?d=identicon)[vgalitsky](/maintainers/vgalitsky)

---

Top Contributors

[![vgalitsky](https://avatars.githubusercontent.com/u/1241206?v=4)](https://github.com/vgalitsky "vgalitsky (12 commits)")

---

Tags

phphtmlabstract language

### Embed Badge

![Health badge](/badges/concept-labs-phtmal/health.svg)

```
[![Health](https://phpackages.com/badges/concept-labs-phtmal/health.svg)](https://phpackages.com/packages/concept-labs-phtmal)
```

###  Alternatives

[froala/wysiwyg-editor-php-sdk

PHP SDK for Froala WYSIWYG Editor

431.5M](/packages/froala-wysiwyg-editor-php-sdk)[phug/phug

Pug (ex-Jade) facade engine for PHP, HTML template engine structured by indentation

67292.2k12](/packages/phug-phug)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
