StormQueries – A Query Builder with ORM Superpowers
Over the years working in web development, I’ve tried countless tools for working with databases.
Most of the time, they fell into one of two categories:
- Query Builders (QB) – lightweight, fast, no schema configuration required. Great for quick queries. The downside? Once you need hierarchical data (1 invoice → many items), you’re on your own.
- ORMs (Object-Relational Mappers) – powerful for handling relationships, but heavy and often require models, annotations, or schema definitions.
And I kept thinking: why isn’t there something in between?
So I built StormQueries 🚀
Why StormQueries?
StormQueries combines the simplicity of a Query Builder with the power of an ORM.
Here’s what it gives you out of the box:
- ✅ Build dynamic queries depending on runtime parameters.
- ✅ Reusable query structures.
- ✅ Hierarchical data fetching (one-to-many).
- ✅ Map results to your own objects without modifying models.
- ✅ Zero schema configuration – just connect to the database.
- ✅ Support for subqueries.
- ✅ Intuitive syntax.
One example:
With StormQueries, you can run an incremental update (field = field + 1
) — something that’s oddly tricky or impossible in many other tools.
Quick examples
Basic CRUD:
$product = $queries->find('products', ['id' => 5]); $id = $queries->insert('products', [ 'name' => 'Golden watch', 'price' => 465 ]); $queries->update('products', ['id' => $id], ['name' => 'Renamed product']); $queries->delete('products', ['id' => $id]);
Dynamic filtering:
$query = $queries ->select('products') ->join('product_photos', ['product_photos.product_id' => 'products.id']) ->where('is_in_sale', true); if ($criteria->hasCategory()) { $query->where('category_id', $criteria->getCategoryId()); } $products = $query->findAll();
Subqueries:
$queries ->select(SubQuery::create($queries->select('products'), 'p')) ->where('p.product_id', 7) ->find();
ORM-style relationships (without schema config):
$customer = $queries ->select('customers c', Map::select([ 'customer_id', 'customer_name' ], Customer::class, classId: 'customer_id')) ->leftJoin('orders o', 'o.customer_id = c.customer_id', Map::many("orders", [ 'order_id' ], Order::class, classId: 'order_id')) ->where('c.customer_id', 90) ->find(); foreach ($customer->orders as $order) { echo $customer->customer_name . " - " . $order->id; }
How is it different?
- Doctrine / Eloquent (PHP) – require schema definitions and models. StormQueries works right after connecting.
- SQLAlchemy, Hibernate – powerful but heavy, with lots of boilerplate. StormQueries is lightweight and fast.
- Pure Query Builders – flexible, but no hierarchical mapping. StormQueries bridges the gap.
Try it out
🔗 GitHub: storm-php-queries
📖 Docs & examples: stormmoredev.github.io/storm-php-queries
Final thoughts
StormQueries was born out of real-world needs: I wanted the simplicity of Query Builders with the convenience of ORMs, without their usual trade-offs.
If this sounds useful, check it out, give it a ⭐ on GitHub, or drop me some feedback.
Issues, discussions, and pull requests are more than welcome 🙌
This is just the beginning — I’m planning more features inspired by real application use cases. Stay tuned!
Top comments (2)
While I applaud your effort creating the library, I think Doctrine DBAL provides a more extensive base to build upon than starting from scratch.
Things you are missing, compared to Doctrine DBAL:
I think you created a smart solution for the relationship object result. I would suggest to add the configuration to the
find
orfindAll
method instead.I could look like this
This configuration makes the query builder flow more readable. And when union is added you could add
MapFrequency::OnColumnValues
.Nice post! StormQueries hits a sweet spot, powerful query features without all the heavy ORM setup