u/Ilia0001

fastchart 0.2.0: native PHP charting extension with 19 chart types, plus Code 128 and QR codes
▲ 46 r/PHP

fastchart 0.2.0: native PHP charting extension with 19 chart types, plus Code 128 and QR codes

I maintain a handful of native PHP extensions. fastchart is the newest. 0.2.0 just landed.

The problem. PHP server-side charting is in rough shape. JpGraph hasn't seen meaningful work in years. pChart is abandoned. The common workaround is a Node or Python sidecar microservice that exists just to render PNGs. For OHLC plus indicator panes there isn't a serious PHP-native option at all.

Some history. In 2006 Rasmus and I shipped PECL/GDChart, a binding for the gdchart library. It died with its upstream in 2007. Since then I've built about six private PHP chart extensions, each solving exactly one need (a QR variant, OHLC for a dashboard, a couple of chart types). None shipped. fastchart is the consolidation.

What's in it:

  • 19 chart classes: Line, Area, Bar, Scatter, Bubble, Pie, Stock, Radar, Polar, Surface, Contour, Gauge, Gantt, BoxPlot, Treemap, Funnel, Waterfall, Heatmap, LinearMeter
  • StockChart with 7 candle styles (CANDLE / BAR / DIAMOND / I_CAP / HOLLOW / VOLUME / VECTOR), SMA/EMA/WMA overlays, plus RSI / MACD / Bollinger Bands / Parabolic SAR / Stochastic / OBV indicator panes
  • A parallel Symbol family (new in 0.2.0): Code 128 (ISO/IEC 15417, auto subset switching, mod-103 checksum) and QR Code (ISO/IEC 18004, ECC L/M/Q/H, versions 1-40, vendored nayuki encoder)
  • Output to PNG, JPEG, WebP, AVIF, GIF
  • 105 public methods, 86 phpt tests, PHP 8.3+ (NTS or ZTS), BSD 3-Clause

Install via PIE:

pie install iliaal/fastchart

Requires ext-gd (PHP's bundled GD extension); fastchart renders through gd.

Repo: https://github.com/iliaal/fastchart

Full writeup with the StockChart indicator stack and the composition pattern: https://ilia.ws/blog/fastchart-0-2-0-native-php-charts-barcodes-and-qr-codes-in-one-extension

Open to feedback on chart types worth adding next and on the StockChart indicator set.

u/Ilia0001 — 2 days ago

I built whetstone to stop Claude Code from declaring "done" before verifying anything

Six months of daily Claude Code use convinced me the agent doesn't lack capability. It lacks process. It can write a React component or trace a segfault, but it won't ask "did I verify this actually works?" before declaring victory, and it won't split a 400-line diff into reviewable chunks.

whetstone is a plugin that fixes that. 30 skills, 19 agents, 22 commands. Skills are compact instruction sets that load on keyword match, so the agent picks up the relevant discipline for the task it's actually running.

The debugging skill blocks a fix until the root cause is identified with file-and-line evidence two levels deep in the call chain. Reproduce first, one hypothesis at a time, escalate after three failed attempts instead of guessing forever. The code-review skill runs spec-compliance first, then quality, and dispatches parallel specialist agents (security, performance, database) when a diff crosses the size threshold. The writing skill keeps a banned-vocab list and a five-dimension rubric every piece of prose passes through.

The workflow is five commands. /ia-brainstorm interviews you to surface hidden requirements. /ia-plan turns that into atomic tasks with file paths. /ia-work executes with task tracking and verification gates. /ia-review runs the multi-agent review. /ia-compound captures what you solved as searchable docs the next session can find. Each works standalone.

https://github.com/iliaal/whetstone

reddit.com
u/Ilia0001 — 4 days ago
▲ 19 r/PHP

mdparser 0.3.0: native PHP CommonMark + GFM parser, 15-30× faster than pure-PHP

I posted this in r/laravel last week. u/equilni's reply:

> Looking at the repo, this looks like it could be used for plain PHP too. I suggest posting in that sub as well.

So here it is. Original thread: https://www.reddit.com/r/laravel/comments/1t84fu4/mdparser_030_native_php_commonmark_gfm_parser/

I build native PHP extensions when pure-PHP solutions become a bottleneck. mdparser is the markdown one. It wraps embedded cmark-gfm (CommonMark 0.31, all 652 spec examples pass) and ships as a single .so on Linux/macOS or .dll on Windows. PHP 8.3 minimum.

If your app renders markdown on every request, comment threads, docs, CMS pages, forum threads, transactional mail, pure-PHP parsers become a measurable fraction of request time. mdparser is for that hot path.

What's in it:

  • GFM extensions: tables, strikethrough, task lists, autolinks, tagfilter (XSS-safe HTML sanitization)
  • Smart punctuation, footnotes, safe mode, heading anchors, nofollow links
  • Three output formats from one parser: HTML, CommonMark XML, and a PHP AST (nested arrays). AST output is rare in PHP markdown libraries; useful if you want to walk the tree before rendering, or sanitize at the structural level instead of post-hoc on HTML.

Performance against the major pure-PHP parsers, on PHP 8.4 with each parser in its default configuration:

Parser Small (200 B) Medium (1.8 KB) Large (200 KB)
mdparser 30,447 ops/s 5,697 ops/s 105 ops/s
Parsedown 1,651 ops/s (18x slower) 325 ops/s (17x) 6 ops/s (17x)
cebe/markdown (GFM) 1,350 ops/s (22x) 374 ops/s (15x) 6 ops/s (16x)
michelf (Markdown Extra) 1,006 ops/s (30x) 209 ops/s (27x) 5 ops/s (19x)

15-30× faster across the board, from short messages up to full 200 KB spec documents. league/commonmark is the closest competitor and has slightly different positioning (more extensions via opt-in); numbers and methodology in bench/README.md.

Install:

pie install iliaal/mdparser

API:

use MdParser\Parser;
$parser = new Parser();
$html = $parser->toHtml($markdown);
$ast  = $parser->toAst($markdown);

Blog post with the full benchmark methodology and comparison data: https://ilia.ws/blog/mdparser-a-native-commonmark-gfm-parser-for-php Repo: https://github.com/iliaal/mdparser

Happy to answer questions, especially about the AST output, the cmark-gfm postprocess interactions (heading-anchor positioning under raw HTML, nofollow-aware HTML scanning), or anything PHP-extension-side.

reddit.com
u/Ilia0001 — 4 days ago
▲ 29 r/laravel

mdparser 0.3.0: native PHP CommonMark + GFM parser, 15-30× faster than pure-PHP for high-volume Laravel rendering

I build native PHP extensions when pure-PHP solutions become a bottleneck. mdparser is the markdown one.

If your Laravel app renders markdown on every page load (comment threads, mailables, Filament fields, content pages) pure-PHP parsers like league/commonmark and Parsedown become a measurable share of request time. mdparser is a C extension that parses CommonMark and GFM 15-30× faster on the same documents. league/commonmark is a fine default for most apps; the pain shows up when markdown rendering is on the hot path.

What it does:

  • GFM extensions: tables, strikethrough, task lists, autolinks, tagfilter (XSS-safe HTML sanitization)
  • Smart punctuation, footnotes, safe mode
  • Output as HTML, XML, or PHP AST (the AST output is rare in markdown libraries; useful if you want to walk the tree before rendering)

Where it slots into a Laravel codebase:

  • Mailable rendering. The path that ships with Laravel goes through league/commonmark under the hood, so swapping in mdparser for high-volume transactional mail is a one-line change in the renderer binding.
  • Filament markdown fields, rendered on the backend.
  • Forum or comment rendering middleware.
  • Documentation or static page generation.

Install:

pie install iliaal/mdparser

API:

$parser = new MarkdownParser();
$html = $parser->toHtml($markdown);
$ast  = $parser->toAst($markdown);

Blog post with the full benchmark methodology and comparison data: https://ilia.ws/blog/mdparser-a-native-commonmark-gfm-parser-for-php

Repo: https://github.com/iliaal/mdparser

Happy to answer questions about Laravel-specific integration, mailables especially.

reddit.com
u/Ilia0001 — 6 days ago
▲ 127 r/PHP

I've maintained php_excel since 2008. 2.0 shipped in April as the first ground-up rewrite, and 2.0.1 just landed on May 3.

The problem it solves: PhpSpreadsheet builds the whole DOM in PHP memory. On a 50K-row spreadsheet you're looking at ~790 MB resident before you've called save(). In a 128 MB FPM pool that means OOM on anything past trivial. OpenSpout streams, but it can't write conditional formatting, formulas, rich text, or .xls.

php_excel wraps LibXL through a native C extension. LibXL (libxl.com) is a commercial C++ library by Andrew Karasyov, not mine; you acquire it separately. php_excel is the PHP binding.

Quick comparison. PhpSpreadsheet is the most feature-complete pure-PHP option. OpenSpout fills a streaming niche. php_excel is the C-extension answer for when you need both speed and full Excel features.

What 2.0 changed:

  • PHP 8.3 / 8.4 / 8.5 / master, dropped older versions
  • LibXL 4.6.0+, newer features gated at compile time
  • 12 classes (6 new: ExcelRichString, ExcelFormControl, ExcelConditionalFormat, ExcelConditionalFormatting, ExcelCoreProperties, ExcelTable)
  • 399 typed parameters, 277 typed return values, full arginfo coverage from a stub-driven build
  • Installable via PIE

Benchmarks against PhpSpreadsheet 5.5.0, PHP 8.4.19 NTS:

Rows Cells php_excel PhpSpreadsheet Speed
1,000 20K 0.05s / 85 MB 0.45s / 162 MB 10×
10,000 200K 0.55s / 153 MB 4.59s / 282 MB
50,000 1M 2.72s / 508 MB 24.7s / 790 MB
100,000 2M 5.37s / 908 MB 51.1s / 1,415 MB 10×

Read perf is similar: 8-9× faster than PhpSpreadsheet, 3× faster than OpenSpout (with proportional memory trade-off vs OpenSpout's flat 130 MB).

2.0.1 is a hardening pass: extensive error checking and input validation across the C/PHP boundary.

Install:

pie install iliaal/php-excel --with-libxl-incdir=/path/to/libxl/include_c --with-libxl-libdir=/path/to/libxl/lib

Full writeup with methodology: https://ilia.ws/blog/php-excel-2-0-the-c-extension-for-excel-that-php-should-have-had-all-along

Repo: https://github.com/iliaal/php_excel

reddit.com
u/Ilia0001 — 11 days ago
▲ 29 r/PHP

Six days ago I tagged 0.6.0 of php_clickhouse, a soft fork of the SeasClick extension (which stopped accepting PRs in 2020). Three releases later (0.7.0, 0.8.0, 0.8.1) I'm calling the extension stable.

The PHP ClickHouse ecosystem has been split between SeasClick (native binary protocol but stalled, no modern types, no ZTS, no TLS in the maintained fork) and HTTP clients like smi2/phpClickHouse (active but ~30-40% slower at high throughput). This fork picks up the native-protocol path with the official clickhouse-cpp v2.6.1 client and brings the modern type surface back.

What landed in the quality cycle:

0.7.0 closed the API gap with smi2/phpClickHouse. Per-call settings, server-side typed parameters via {name:Type} placeholders, progress callback, getStatistics() (rows/bytes/elapsed_ms), structured ClickHouseException with server_code / query_id, insertAssoc(), SQL helpers (databaseSize, showTables, tableSize, etc.), sub-second timeouts. Adopting the native client no longer costs ergonomics.

0.8.0 moved per-Client state from seven file-scope std::map banks onto the zend_object itself. Unblocks ZTS (RoadRunner / FrankenPHP / Swoole / php-pm now work), plugs leaks on script bailout, fixes a refcount bug on the progress callback. Adds streaming reads (selectStream() returns an Iterator + Countable, selectStreamCallback() for unbounded streams), Geo types (Point / Ring / Polygon / MultiPolygon), LowCardinality(Nullable(T)), and Map(K, V) over the full scalar matrix. Pre-built binaries for Linux glibc (x86_64 + arm64) and macOS (x86_64 + arm64) via PIE.

0.8.1 hardened the insert path. The connection now resets on every server-side rejection point (BeginInsert, SendInsertBlock, EndInsert) so a thrown insert no longer wedges the handle with "cannot execute query while inserting". Insert builds native columns one at a time directly from row-major input: peak intermediate PHP memory drops from N_rows × N_cols zvals to one column. Strict full-consumption parsers across Map, narrow-int, Int128 / UInt128, geo, DateTime64 reject coercion-to-zero on bad input. 23 new PHPTs covering all of the above.

One side effect of the new ASan job: it caught a latent UB in clickhouse-cpp's empty-string-view memcpy path. Submitted upstream and merged as ClickHouse/clickhouse-cpp#489 on April 27.

Install on supported platforms (Linux glibc + macOS, NTS, PHP 8.4 / 8.5):

pie install iliaal/php_clickhouse

TLS variant builds from source: pie install iliaal/php_clickhouse --enable-clickhouse-openssl.

Blog post with the full breakdown: https://ilia.ws/blog/php-clickhouse-0-8-1-three-releases-later-stable Repo: https://github.com/iliaal/php_clickhouse

Happy to take questions, especially from anyone running ClickHouse-from-PHP at production volume.

reddit.com
u/Ilia0001 — 14 days ago

Pine Script editing is mostly a manual loop in TradingView's editor: write, compile, read errors, fix, repeat. AI agents can help if you copy-paste the script and errors into Claude or Cursor and paste the fix back, but the agent can't see what the indicator actually plots: labels, lines, boxes, tables, plotshape markers. So visual debugging stays manual.

I built tradingview-mcp to put Pine Script in the agent's hands directly. It's an MCP server (96 tools across the TV surface) plus a tv CLI; both drive your local TradingView Desktop over Chrome DevTools Protocol. The Pine-specific tools:

  • pine_check: server-side compile without putting the script on a chart. Useful for CI-style verification or letting the agent validate a draft before adding it.
  • pine_analyze: offline static analysis (catches typos, unused vars, deprecated patterns) before you compile.
  • pine_smart_compile: auto-detects whether to add or update, returns elapsed_ms.
  • pine_save_as, pine_rename, pine_version_history, pine_delete, pine_switch_script: full lifecycle, no editor clicks.
  • data_get_pine_lines / _labels / _tables / _boxes / _shapes: the agent can read what the indicator actually drew. Horizontal price levels, text annotations, table cells, price zones, plotshape markers. Deduplicates and caps output by default; opt into raw via verbose.

The visual-output readers are the part I keep using most. Agent writes an indicator, compiles it, reads the labels back, decides whether the logic is right.

A few release details: Pine Editor open + symbolInfo fallbacks for TV Desktop 3.1.0 (compile/deploy buttons matched by title attribute). pine_set_source no longer hangs on large scripts. 338 offline tests cover the Pine tooling, multi-timeframe alignment, replay, and CLI routing. The upstream ui_evaluate tool (arbitrary JS in your authenticated TV session) was removed from the surface; everything else is gated through specific tool boundaries.

Install: clone the repo, npm install, add to ~/.claude/.mcp.json, launch TradingView with --remote-debugging-port=9222. README has the paste-into-Claude-Code one-liner.

Repo: https://github.com/iliaal/tradingview-mcp

Happy to answer questions, especially from anyone running heavy indicators with many lines / labels: the dedup defaults are calibrated to my workflow and may need tuning for others.

u/Ilia0001 — 16 days ago
▲ 20 r/PHP

A while back I pushed a small PECL extension that wrapped libstatgrab and exposed CPU, memory, disk I/O, network, and process statistics to PHP. It sat untouched for most of the PHP 5/7 era and stopped building cleanly on PHP 8 a few years back. I shipped 2.2 today, a full modernization of the binding for PHP 8.0 through 8.5 against libstatgrab 0.92+.

The reason to revive it: nothing on the PHP side has replaced it. If you need system stats from PHP, you are typically choosing between three options.

  • Shell out to w, vmstat, df, ps. The output format drifts between OS releases, and you end up writing a per-tool parser.
  • Parse /proc by hand. Linux-only, every file (meminfo, loadavg, diskstats, net/dev) has its own format and edge cases.
  • Run a separate stats daemon and hit it over a socket. Adds a daemon to deploy and keep running.

libstatgrab itself is the right primitive: a cross-platform C library that handles /proc on Linux, kvm on FreeBSD, and the Mach host_* APIs on macOS, and exposes one typed surface. It just needed a PHP binding that worked on a current interpreter.

The 2005 procedural API is preserved (sg_cpu_percent_usage, sg_memory_stats, etc.) for drop-in compatibility, with a new OO surface (Statgrab::cpu(), ::memory(), ::processes()) on top.

While running ASan on the new test suite I caught a memory leak in libstatgrab's shutdown path. Patch submitted upstream; pending review. The repo carries a vendored libstatgrab 0.92.1 with the local fix in the meantime. Build with --with-statgrab=bundled to get a single .so with no runtime dependency on libstatgrab.so. Useful in any deployment where you don't want to require libstatgrab as a system package.

Install:

pie install iliaal/statgrab

Or pecl install statgrab if you are still on the legacy installer. Source build and the bundled-libstatgrab path are in the README.

Repo: https://github.com/iliaal/statgrab Full write-up: https://ilia.ws/blog/its-alive-statgrab-returns-after-20-years

reddit.com
u/Ilia0001 — 17 days ago