This document covers the impact of migrating from the legacy PHP-based stack to the new Python-based stack across both the Image Processing Tool and the Admin Panel. It covers speed, performance, maintainability, scalability, and operational changes.
| Layer | Legacy Stack | New Stack |
|---|---|---|
| Image Processing Tool | CakePHP 3.x (PHP) | Python (NumPy + OpenCV) |
| Image Processing Engine | ImageMagick (PHP Imagick extension) | OpenCV + NumPy + cairosvg |
| SVG Rendering | Inkscape (CLI subprocess) | cairosvg (native Python library) |
| Admin Panel Backend | Laravel (PHP) + Bagisto | Django (Python) + DRF |
| Admin Panel Frontend | Bootstrap 5 + jQuery | React |
| Product Import | Artisan CLI command (manual SSH) | Django Admin UI + Celery (async) |
| Background Jobs | None (blocking foreground) | Celery + Redis |
| Real-time UI | None (page reload) | WebSocket (Django Channels) |
| ORM | Eloquent (Laravel) | Django ORM |
| Database | MySQL | PostgreSQL / MySQL |
| Caching | None documented | Redis |
| API between systems | CakePHP REST endpoints | Python Django REST Framework |
This is the biggest change. The legacy system used ImageMagick via PHP's Imagick extension, driven by CakePHP. The new system uses OpenCV and NumPy directly in Python.
ImageMagick (Legacy)
exec() — a full OS process launch per renderOpenCV + NumPy (New)
| Operation | Legacy (PHP + Imagick) | New (Python + OpenCV) | Improvement |
|---|---|---|---|
| SVG → PNG conversion | ~800ms (Inkscape subprocess) | ~150ms (cairosvg in-process) | ~5× faster |
| Single composite operation | ~200ms (Imagick pass) | ~20ms (NumPy vectorised) | ~10× faster |
| Full product image render (1 view) | ~3–5 seconds | ~0.5–1 second | ~4–5× faster |
| Full product (4–5 views) | ~15–25 seconds | ~3–5 seconds | ~5× faster |
| 2400 products × 5 views | ~10–16 hours | ~2–3 hours | ~5× faster |
These are estimates based on typical ImageMagick vs OpenCV benchmarks. Actual numbers depend on server specs and image resolution.
In the legacy system, a composite blend like Color Burn was applied as an Imagick operation — a black-box C function called once per image. In the new system:
# Color Burn in NumPy — applied to all pixels simultaneously
# No loop, no per-pixel overhead — entire image processed in one operation
result = 1 - (1 - base) / (texture + epsilon)
result = np.clip(result, 0, 1)
This runs on the entire image array at once using CPU SIMD instructions. The equivalent in PHP/Imagick has no equivalent vectorised path — it is always a sequential per-pixel or per-channel operation internally.
PHP exec("inkscape input.svg --export-png=output.png")
→ OS forks a new process
→ Inkscape loads, initialises its GTK/rendering stack (~400–600ms startup)
→ Renders SVG
→ Writes PNG to disk
→ PHP reads PNG from disk
→ Subprocess exits
Every render paid the full Inkscape startup cost (~400–600ms) even for a tiny SVG. At 2400 products × 5 views = 12,000 renders, this alone adds ~1.5–2 hours of pure startup overhead.
import cairosvg
png_bytes = cairosvg.svg2png(url=svg_path, output_width=1200)
→ Renders SVG using Cairo graphics library
→ Returns PNG bytes directly in memory
→ No subprocess, no disk write, no startup cost
No process forking. No startup cost. Output is already in memory as bytes, directly loadable by OpenCV without a disk round-trip.
Bagisto is a full e-commerce framework built on Laravel. It comes with a large EAV (Entity-Attribute-Value) data model with many indirection layers. Every product write goes through multiple tables (products, product_flat, product_attribute_values) via Bagisto's repository pattern.
The import command had to:
productRepository->update() through Bagisto's abstraction layersproduct_flat across all locale rows manuallyThis meant each product import was 5–10 individual SQL queries through multiple abstraction layers.
The frontend was jQuery — full page reloads for most interactions, no real-time feedback.
New: Django + DRF + React
Django ORM writes directly to your own clean models. No EAV indirection, no Bagisto abstraction layers. A product upsert is one SQL query via bulk_create with update_conflicts.
React gives the admin panel real-time UI — WebSocket progress, live dropdowns, no page reloads.
| Feature | Legacy (Laravel + Bagisto + jQuery) | New (Django + React) |
|---|---|---|
| Product import trigger | SSH terminal command | UI button in admin panel |
| Import feedback | Terminal stdout only | Real-time WebSocket progress |
| Category mapping | CLI positional arguments | Dropdown selectors in UI |
| Import mode | Blocking foreground | Async Celery background task |
| Import scale | Single task, serial | Parallel chunked Celery tasks |
| Re-run on failure | Full re-run from scratch | Retry failed chunks only |
| Product upsert | Per-product repository->update() |
bulk_create with update_conflicts |
| DB writes per 100 products | ~500–1000 queries (EAV layers) | 1 bulk query |
| Frontend interaction | jQuery + full page reloads | React SPA, no reloads |
| Real-time UI | Not available | WebSocket (Django Channels) |
| API between systems | CakePHP REST | Django REST Framework |
Bagisto uses an Entity-Attribute-Value architecture inherited from Magento patterns. A single product's data is spread across:
products → core row
product_flat → denormalised per channel + locale
product_attribute_values → all attributes stored as generic key-value rows
product_images → image paths
attribute_options → option lookup table
Reading a product's full data requires joining 4–5 tables. Writing a product requires coordinated writes to all of them, plus a step to fix text values that Bagisto overwrites with option IDs.
Product → all fields directly on the model
ProductImage → image URLs with hash
ImportJob → import tracking
Category → M2M via product_categories
All product data is on one model. One query to read, one bulk query to write. No abstraction layers between your code and the database.
| Step | Legacy (Bagisto) | New (Django) |
|---|---|---|
| Upsert 100 products | ~500–1000 queries | 1 bulk_create query |
| Assign categories | 100 individual M2M inserts | 1 bulk M2M operation |
| Sync images | 100 delete + 100 insert = 200 queries | 1 hash check + 1 bulk_create |
| Total | ~800–1300 queries | ~5–10 queries |
The PHP system had no background job infrastructure. The import command was run in the foreground in a terminal. If the SSH connection dropped, the import died. No retry, no progress tracking, no partial recovery.
Image generation was also synchronous — clicking "Generate Images" in the CakePHP admin blocked the web request for the duration of all renders.
Celery + Redis provides a proper job queue. Every long-running operation (import, image generation) is dispatched as an async task. The web request returns immediately with a job ID. The admin UI connects via WebSocket and receives real-time push updates as each chunk completes.
Legacy: Click button → wait 10 minutes staring at spinner → done (or connection drops)
New: Click button → instant response → WebSocket shows live progress per chunk
→ chunk 1/24 done (100 products)
→ chunk 2/24 done (100 products)
→ ...
→ Import complete — 2350 imported, 50 skipped
Failed chunks are retried automatically (up to 3×). If some chunks still fail, the job is marked partial and only the failed chunks need to be re-run — not the entire import.
The legacy stack used two languages (PHP for both CakePHP and Laravel) with different frameworks, patterns, and tooling. The new stack uses Python throughout — the Image Processing Tool and the Admin Panel share the same language, the same libraries, and the same developer mental model.
| Factor | Legacy | New |
|---|---|---|
| Languages | PHP (CakePHP) + PHP (Laravel) + JS (jQuery) | Python + Python + JS (React) |
| Shared code between systems | Not possible (separate PHP apps) | Possible (shared Python utilities, models) |
| Testing image processing | Difficult — ImageMagick ops are opaque | Easy — NumPy arrays are inspectable at every step |
| Debugging renders | Write to disk, inspect file | Inspect array values directly in Python debugger |
| Adding a new composite mode | New Imagick call + PHP glue | New NumPy expression, 1–3 lines |
| Frontend state management | jQuery DOM manipulation | React state — predictable, testable |
| Dependency | Legacy | New | Notes |
|---|---|---|---|
| Inkscape | Required (CLI subprocess) | Not needed | Removed entirely |
| ImageMagick / Imagick | Required | Not needed | Replaced by OpenCV |
| PHP | Required (two systems) | Not needed | Removed entirely |
| Composer | Required | Not needed | |
| Node / npm | Optional | Required (React build) | |
| Python | Not needed | Required (both systems) | |
| Redis | Not needed | Required | Celery broker + cache + channels |
| Celery worker | Not needed | Required | Background task processing |
| Requirement | Legacy | New |
|---|---|---|
| PHP runtime | Yes | No |
| Python runtime | No | Yes (both systems) |
| Redis | No | Yes |
| Celery workers | No | Yes (1+ worker processes) |
| Django Channels (ASGI) | No | Yes (for WebSocket) |
| ASGI server (Daphne/Uvicorn) | No | Yes (replaces WSGI) |
| Factor | Direction | Detail |
|---|---|---|
| Image render speed | ✅ ~5× faster | OpenCV + NumPy vs ImageMagick + Inkscape |
| SVG conversion speed | ✅ ~5× faster | cairosvg in-process vs Inkscape subprocess |
| Product import speed | ✅ ~100× faster | Bulk + parallel vs serial per-row |
| DB query count (import) | ✅ ~100× fewer | bulk_create vs EAV repository pattern |
| Admin UX | ✅ Major improvement | Real-time WebSocket vs blocking CLI |
| Failure recovery | ✅ Improved | Chunk-level retry vs full restart |
| System complexity | ➡ Similar | Different complexity — job queue added, EAV removed |
| Server dependencies | ➡ Changed | PHP/Imagick/Inkscape removed, Redis/Celery/ASGI added |
| Codebase language | ✅ Simplified | Single language (Python) across both systems |
| Debuggability | ✅ Improved | NumPy arrays inspectable vs opaque ImageMagick ops |
| Frontend reactivity | ✅ Major improvement | React SPA vs jQuery + page reloads |