Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cssxpath Laravel Package

phpgt/cssxpath

Lightweight, dependency-free PHP library that translates CSS selectors into XPath queries, bringing querySelectorAll-style DOM selection to PHP. Works with HTML and, via htmlMode: false, case-sensitive XML attributes.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require phpgt/cssxpath:^1.6
    

    No service provider or config file is required. After upgrading an existing install, run composer dump-autoload (and php artisan optimize:clear if you deploy with --optimize-autoloader) so the new namespace mapping is picked up.

  2. Namespace (changed in v1.6.0)

    • New canonical namespace: GT\CssXPath
    • Legacy namespace: Gt\CssXPath (still shipped for backwards compatibility)
    • Use the new one in all new code; existing imports keep working but should be migrated opportunistically.
  3. First Use Case Convert a CSS selector to an XPath expression and run it against a document:

    use GT\CssXPath\Translator;
    use DOMDocument;
    use DOMXPath;
    
    $dom = new DOMDocument();
    $dom->loadHTML('<div class="content"><p>Hello</p></div>');
    
    $xpath = new DOMXPath($dom);
    $expression = (string) new Translator('div.content > p');
    
    foreach ($xpath->query($expression) as $node) {
        echo $node->textContent; // Hello
    }
    
  4. Where to Look First

    • composer.json autoload / autoload-dev blocks — this is where the GT\CssXPath ↔ Gt\CssXPath compatibility mapping lives, so it tells you exactly which namespaces are safe to import.
    • CHANGELOG.md and the tagged diff for 1.6.0 (the release is namespace + tooling only; no behavioural changes).
    • tests/ for selector-coverage examples you can copy into your own test suite.

Implementation Patterns

Core Workflow

  1. Wrap the converter in one place Don't scatter new Translator(...) across controllers/jobs — wrap it so a future namespace or API change is a one-file edit:

    namespace App\Support;
    
    use GT\CssXPath\Translator;
    
    class CssToXPath
    {
        public function __invoke(string $selector): string
        {
            return (string) new Translator($selector);
        }
    }
    

    Bind it in a service provider if you want it injected:

    $this->app->singleton(\App\Support\CssToXPath::class);
    
  2. Integration with DOM parsing

    use App\Support\CssToXPath;
    use DOMDocument;
    use DOMXPath;
    
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    
    $xpath = new DOMXPath($dom);
    $nodes = $xpath->query(app(CssToXPath::class)('div.user span'));
    
  3. Batch extraction (scraper / importer)

    use App\Support\CssToXPath;
    
    $fields = [
        'title' => 'h1.product-title',
        'price' => 'span.price',
        'stock' => 'div.availability',
    ];
    
    $convert = app(CssToXPath::class);
    $xpath = new DOMXPath($dom);
    
    $result = [];
    foreach ($fields as $key => $selector) {
        $node = $xpath->query($convert($selector))->item(0);
        $result[$key] = $node?->textContent;
    }
    

    Convert each selector once, reuse the resulting expression across documents when the HTML structure is stable.

  4. Blade / view helper

    // AppServiceProvider::boot()
    Blade::directive('css', fn ($selector) => "<?php echo e((string) new \\GT\\CssXPath\\Translator($selector)); ?>");
    

Advanced Patterns

  • Queue-safe value objects: pass the selector string into jobs, not the Translator instance. The object is not meant to be serialized; reconstitute it in the worker.
  • Test assertions: convert once in a data provider and assert against generated XPath strings — that gives you a regression net if you ever swap the implementation.
    public static function selectorProvider(): array
    {
        return [['div.content > p'], ['ul li:first-child'], ['a[href^="https"]']];
    }
    
  • Gradual namespace migration: rather than a mass find/replace, add a php-cs-fixer / Rector rule (or a simple grep -rl 'Gt\\CssXPath' app/ job) that rewrites imports to GT\CssXPath and lets normal PR flow handle the rest.

Implementation Patterns (integration tips)

  • Wrap DOMDocument usage in a small service that also handles libxml error suppression and encoding:
    $dom = new DOMDocument();
    libxml_use_internal_errors(true);
    $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    libxml_clear_errors();
    
  • Cache converted expressions per-process (static array $memo = []) when iterating large documents — conversion is cheap but not free.

Gotchas and Tips

Pitfalls

  1. Trait/string-based class references (v1.6.0 namespace change) PHP class lookups are case-insensitive at runtime, but Composer's PSR-4 autoloading is case-sensitive per prefix. That is why the package ships the compatibility mapping rather than just renaming. Practical consequences:

    • instanceof GT\CssXPath\Translator and instanceof Gt\CssXPath\Translator both pass for the same object.
    • But get_class($obj) returns one canonical name, so string comparisons ($class === 'Gt\CssXPath\Translator') can fail after migration. Prefer instanceof or class-string constants.
    • Laravel container bindings keyed by class string are case-sensitive as array keys: after migrating imports, container lookups under the old key can miss. Bind via SomeClass::class, not hand-written strings.
    • composer dump-autoload is required after upgrade if you run with an optimized/classmap-authoritative autoloader.
  2. Legacy imports still "work" — that's a trap Mixing Gt\CssXPath and GT\CssXPath in the same codebase is accepted but will trip static analysis and confuse reviewers. Pick GT\CssXPath as the standard and flag the old one in code review or CI (! grep -rq 'Gt\\\\CssXPath' app/).

  3. Attribute selectors div[href] maps cleanly, but div[href^="http"], [data-*] and multi-value attribute selectors are where translation gets approximate. Always verify the generated expression against a real document before shipping.

  4. Pseudo-classes Structural pseudo-classes (e.g. :nth-child(2n)) expand to verbose expressions. They're correct but long — cache them, don't rebuild per row.

  5. API surface used in older docs Many snippets online call a static cssToXpath() on a CssXpath class and $dom->evaluate(...). Neither exists: the converter is the Translator (stringable) and querying is done with DOMXPath::query(). Don't copy those snippets verbatim.

  6. Namespaces (XML) No built-in namespace support for CSS like html|div. Register prefixes on DOMXPath (registerNamespace) and adapt the expression, or post-process the generated XPath. This is application-level, not something v1.6.0 changed.

  7. Performance Don't convert selectors inside a per-node loop. Convert up front, reuse the expression.

Debugging

  • Inspect the expression
    $expression = (string) new Translator('div > p:first-of-type');
    logger()->debug('CSS→XPath', ['expression' => $expression]);
    
    There is no debug flag or logging hook in the package — wrap it yourself as above.
  • Invalid expressions DOMXPath::query() returns false (and emits a warning) for a malformed expression rather than throwing. Check the return value:
    $nodes = $xpath->query($expression);
    if ($nodes === false) {
        throw new \RuntimeException("Invalid XPath: {$expression}");
    }
    
  • Silent libxml failures: call libxml_use_internal_errors(true) and inspect libxml_get_errors() when loadHTML() returns odd results — it's usually malformed markup, not the selector.

Extension Points

  1. Keep the converter behind your own abstraction Do not extend the package class. The release shows the namespace is still being settled; an interface/adapter in App\Support insulates you from both the namespace move and any future API change:

    interface SelectorTranslator
    {
        public function toXPath(string $selector): string;
    }
    
  2. Pre/post-processing You can safely compose expressions with your own prefixes when scoping queries to a subtree:

    $scoped = './/' . ltrim($expression, '/');
    
  3. Testing Unit-test your wrapper's output strings, and integration-test against fixture HTML with a real DOMDocument — mocking DOMDocument/DOMXPath produces tests that pass regardless of whether the expression works.

  4. Sanitising user-supplied selectors The library is a translator, not a validator. Never pass raw request input in without an allow-list of permitted selectors/characters, and never build XPath by concatenating user strings.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console