mercurysolutions/mathml
A Laravel package for working with MathML, making it easy to render mathematical notation and equations in your PHP applications. Integrates cleanly with Blade views for displaying math content.
Pull the package in with Composer and confirm what PHP versions it actually supports before you build around it:
composer require mercurysolutions/mathml
// composer.json — pin it. Five stars and zero dependents means you are the QA team.
"mercurysolutions/mathml": "dev-master#<commit-sha>"
Then open src/ and read it end to end — it is a small library, and the README is thin. You are looking for two things: the parser (MathML string → element tree) and the calculator/evaluator (tree → value). Typical entry points look like Parser::parse($xml) / Calculator::calculate($mathml), but confirm the exact class names and method signatures with grep -rn "public function" src/ before wiring anything into your app.
The mental model to hold: this library consumes Content MathML — the semantic dialect built from <apply>, <ci>, <cn>, <plus/> — not the presentation dialect made of <mrow>, <msup>, <mo>. Your first end-to-end use case should be a hand-written expression:
use MercurySolutions\MathML\Parser; // verify FQCN
use MercurySolutions\MathML\Calculator; // verify FQCN
$xml = <<<XML
<math xmlns="http://www.w3.org/1998/Math/MathML">
<apply>
<plus/>
<ci>x</ci>
<cn>2</cn>
</apply>
</math>
XML;
$calculator = new Calculator();
$result = $calculator->calculate($xml); // evaluate with whatever binding API exists
Get that running in a scratch script or a single Pest/PHPUnit test before you touch a controller. That one test is your canary for every version bump.
Wrap it in one service class. With 0 dependents, treat the package as a swappable detail. Put a thin adapter between your domain and the library:
final class MathExpressionEvaluator
{
public function __construct(private Calculator $calculator) {}
public function evaluate(string $mathml, array $variables = []): int|float
{
// bind $variables into the tree, or return the raw evaluated value
return $this->calculator->calculate($mathml);
}
}
Bind it in a service provider so controllers, jobs, and Blade components all get the same configured instance.
Store MathML, evaluate on demand. Keep the raw MathML string in a text column (plus a result cache column and an evaluated_at timestamp). Evaluating at write time locks in bugs and stale variable bindings; evaluating at read time lets you fix the interpreter and re-run. Parse trees are deterministic, so memoize by hash('xxh3', $mathml) if hot paths need it.
Bind variables at the edges, not in the tree. Whatever <ci>-resolution mechanism the package offers, prefer a context array keyed by identifier name. If it doesn't offer one, walk the tree yourself and substitute <ci> nodes with <cn> nodes before calculating — that keeps the library stateless and testable.
Generate Content MathML upstream. Formula editors are the usual source. MathType and Wiris export Content MathML natively; MathLive and MathQuill typically emit Presentation MathML or LaTeX and need a conversion step. Validate at the form-request layer by attempting a parse and failing the request with a clear message, rather than letting garbage reach the calculator.
Compare with tolerance. If you're using this for answer-checking (<eq/> or manual comparison), never compare floats with ===. Evaluate both sides, then abs($a - $b) < 1e-9.
Test with fixtures. Drop a handful of .mathml files in tests/fixtures/ covering each operator you rely on, and assert against them. This is your regression suite when a dev-master update lands.
<mrow> or <msup>, you have the wrong dialect. Check for <apply> in the source before blaming the library.<math xmlns="http://www.w3.org/1998/Math/MathML"> — stripping it on the way through a WYSIWYG is a classic silent breakage.<cn> is typed, not just a number. It carries type="integer|real|rational|e-notation|complex-cartesian|complex-polar" and a base attribute, and rationals use <sep/> inside: <cn type="rational">1<sep/>3</cn>. Assume the parser handles the common integer/real cases and verify the exotic ones yourself.<!DOCTYPE before handing it over, or ensure entity loading is disabled (LIBXML_NONET, and libxml_disable_entity_loader on older PHP). MathML arrives from users; treat it as untrusted XML.calculate() in a try/catch and fall back to rendering the MathML visually (MathJax in the browser) instead of showing a number. A 500 on an unknown <log/> variant is a bad look.INF/NAN depending on the code path — trap before the value escapes into your UI or database.<plus/> on two <cn> integers may return int, mixed expressions return float. Normalize at your adapter boundary so downstream code has one contract.eval(), not numerical rigor.<apply> handlers there. If it's a hardcoded match statement, subclass or vendor-copy — do not patch vendor/.How can I help you explore Laravel packages today?