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

Oxymel Laravel Package

nb/oxymel

Oxymel is a lightweight, chainable XML builder for PHP. Build well-formed XML documents with a fluent API instead of string concatenation, handling elements, attributes, text, and nesting cleanly.

Deep Wiki
Context7

Getting Started

Install and locate the source.

composer require nb/oxymel

The package is tiny — read README.md first, then src/Oxymel.php. It is a single-class fluent XML builder, not a parser, not a DOM, and not a serializer for arbitrary objects. There is nothing to configure, no service provider, no config file. If you are in a Laravel app you can use Oxymel\Oxymel; anywhere, or bind a fresh instance through the container if you want it injected.

First use case: an XML response / feed.

use Oxymel\Oxymel;

$xml = Oxymel::init('catalog')
    ->tag('book', ['isbn' => '978-0-00-000000-0'])
        ->tag('title', 'The Book')
        ->close()
        ->tag('author', 'Jane Doe')
        ->close()
    ->close()
    ->xml();

return response($xml, 200, ['Content-Type' => 'application/xml']);

init() opens the root tag, tag() opens a child, close() pops the stack, and xml() (or the object's __toString()) renders the result. That is the entire mental model — if you can picture a stack of open tags, you can use Oxymel.

Implementation Patterns

Nesting is manual, not structural. Every tag() you do not immediately self-close must be paired with a close(). Indent your PHP to mirror the XML tree so the pairing is visually obvious.

Attributes vs. content. Pass attributes as an array; pass scalar content as a string. A scalar in the attribute position is treated as content, which is why tag('title', 'The Book') works:

->tag('item', ['id' => 42, 'lang' => 'en'], 'Content goes here')
->close()

Mixing typed content. Use text() for escaped text, cdata() for CDATA blocks, comment() for <!-- -->, and raw() for pre-built XML you have already validated.

->tag('description')
    ->cdata('Contains <html> & "quotes" safely')
->close()
->tag('payload')
    ->raw($alreadyEscapedXml)
->close()

Sitemaps and RSS are the sweet spot. Generating a sitemap from Eloquent results reads almost like the output itself:

$oxymel = Oxymel::init('urlset', ['xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9']);
foreach (Post::cursor() as $post) {
    $oxymel->tag('url')
        ->tag('loc', route('posts.show', $post))
        ->close()
        ->tag('lastmod', $post->updated_at->toAtomString())
        ->close()
    ->close();
}
return response($oxymel->close()->xml(), 200, ['Content-Type' => 'application/xml']);

Streaming large documents. Because Oxymel accumulates a string internally, do not build a 200k-row export in memory. Either chunk it (write the head with Oxymel, loop with fwrite, write the tail) or switch to XMLWriter for that one job. Keep Oxymel for the 80% case where the document is small and readability matters more than throughput.

Testing. Assert against the rendered string, or better, simplexml_load_string($oxymel->xml()) in a test so you assert structure rather than whitespace-sensitive text. That decouples your tests from Oxymel's exact formatting.

Wrapping it. If you produce the same shape in several places (e.g. an XML error envelope), wrap it in a small dedicated class or a XmlResponse helper rather than repeating chains of tag()/close() across controllers.

Gotchas and Tips

Unbalanced tags are the #1 bug. A forgotten close() does not throw; it silently produces malformed XML that only fails downstream in the consumer. When something is off, dd($oxymel->xml()) and count open/close tags — the stack is your friend, and nesting mistakes are always visible there.

raw() is an injection vector. It writes exactly what you give it. Never pass user input to raw(). Use text() or cdata() — they escape. Treat raw() like {!! !!} in Blade: acceptable only for trusted, already-validated fragments.

Escaping is done for you — don't double-escape. text() and attribute values are escaped by the library. If you htmlspecialchars() first and then hand it to text(), consumers will see &amp;amp;. Pass raw values in and let Oxymel do the encoding.

No pretty-printing and no XML declaration by default. Output is compact and (depending on version) lacks <?xml version="1.0"?>. If a consumer requires the prolog, prepend it yourself or check whether the version you pinned emits it — many SOAP/feed consumers are strict about this.

No namespaces, schema validation, or DTD support. The xmlns example above "works" only because it is just another attribute — Oxymel does not understand prefixes or namespace scoping. If you need real namespace-aware documents, use DOMDocument or XMLWriter. Likewise, Oxymel will happily build invalid XML; validate against an XSD in a test if the contract matters.

Whitespace in content is preserved literally. If you build content across multiple lines with heredocs, the leading indentation becomes part of the text node. Trim or concatenate deliberately.

Check the actual tag() signature against the source. The parameter order between attributes and content is the one place people trip up, and the scalar-shortcut behavior is easy to misremember — a one-minute read of src/Oxymel.php prevents a confusing debugging session.

Maintenance signal is weak. 22 stars and a low activity score means you should pin the version in composer.json and not expect fixes. The flip side is that the surface area is so small you can vendor or fork it in an afternoon if you need a bug fixed or a prettyPrint() extension. Extension points are limited to subclassing Oxymel or wrapping it in your own builder — there is no plugin system.

The honest verdict for day-to-day Laravel work: reach for Oxymel when you need to emit a small, hand-shaped XML document (sitemap, RSS, a legacy API response) and want the code to read like the XML. Skip it for parsing, for large streaming exports, for namespace-heavy standards (SOAP, OOXML), or anywhere the schema is strict enough that you want a validating serializer.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums