Skip to content

Commit 8cdf287

Browse files
authored
Merge pull request #6 from bancer/develop
Enhance README with detailed library information
2 parents c600747 + a005127 commit 8cdf287

1 file changed

Lines changed: 170 additions & 1 deletion

File tree

README.md

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,171 @@
11
# native-sql-mapper
2-
A lightweight extension for the CakePHP ORM that converts prepared PDO statements into fully mapped result sets. It infers entities and associations from CakePHP-style aliases, supports deep and belongsToMany relations, and builds nested entity graphs automatically with strict alias validation.
2+
3+
A lightweight extension for the CakePHP ORM that converts **native SQL queries** (executed through prepared PDO statements) into **fully hydrated CakePHP entity graphs**.
4+
5+
This library allows you to execute raw SQL while still benefiting from CakePHP’s entity system, associations, nested structures, and conventions. It supports **deep associations**, **belongsToMany relations**, **junction data**, **nested mapping**, and **strict alias validation**.
6+
7+
`native-sql-mapper` is ideal when:
8+
9+
- You need SQL performance or features that exceed the ORM’s query builder
10+
- You want complex joins, window functions, CTEs, subqueries, aggregates
11+
- You do not want to spend time on converting your SQL statements to query objects using CakePHP's query builder
12+
- But still want **CakePHP entities**, **patch-like hydration**, and **nested association graphs** automatically built from the result set
13+
14+
Aliases such as:
15+
16+
```
17+
Articles__id,
18+
Articles__title,
19+
Comments__id,
20+
Comments__article_id,
21+
Comments__content
22+
```
23+
24+
will be converted into a fully hydrated entity objects.
25+
26+
---
27+
28+
## 🚀 Features
29+
30+
- **Native SQL → real CakePHP entities**
31+
- **Deep association support** (belongsTo, hasMany, hasOne, belongsToMany)
32+
- **Automatic nested entity graph building**
33+
- **Strict alias validation** based on your ORM associations
34+
- **No configuration required** — conventions are inferred
35+
- **Works with any SQL** (CTEs, window functions, unions, etc.)
36+
37+
---
38+
39+
## 📦 Installation
40+
41+
Install via Composer:
42+
43+
```bash
44+
composer require bancer/native-sql-mapper
45+
```
46+
---
47+
48+
## 🔧 Setup & Usage
49+
50+
### 1. Import the trait in your Table class
51+
52+
```php
53+
use Bancer\NativeQueryMapper\ORM\NativeSQLMapperTrait;
54+
```
55+
56+
### 2. Use the trait
57+
58+
```php
59+
use NativeSQLMapperTrait;
60+
```
61+
62+
### 3. Example usage
63+
64+
```php
65+
$ArticlesTable = $this->fetchTable(ArticlesTable::class);
66+
$stmt = $ArticlesTable->prepareSQL("
67+
SELECT
68+
id AS Articles__id,
69+
title AS Articles__title
70+
FROM articles
71+
WHERE title = :title
72+
");
73+
$stmt->bindValue('title', 'My Article Title');
74+
/** @var \App\Model\Entity\Article[] $entities */
75+
$entities = $ArticlesTable->fromNativeQuery($stmt)->all();
76+
```
77+
78+
`$entities` now contains hydrated `Article` entities based on the SQL result.
79+
80+
---
81+
82+
## 🔁 hasMany Example Using Minimalistic SQL
83+
84+
```php
85+
$stmt = $ArticlesTable->prepareSQL("
86+
SELECT
87+
a.id AS Articles__id,
88+
title AS Articles__title,
89+
c.id AS Comments__id,
90+
article_id AS Comments__article_id,
91+
content AS Comments__content
92+
FROM articles AS a
93+
LEFT JOIN comments AS c
94+
ON a.id=c.article_id
95+
");
96+
$entities = $ArticlesTable->fromNativeQuery($stmt)->all();
97+
```
98+
`$entities` now contains an array of Article objects with Comment objects as children.
99+
100+
Same as the result of reqular `->find()...->toArray()`:
101+
```php
102+
$entities = $ArticlesTable->find()
103+
->select(['Articles.id', 'Articles.title'])
104+
->contain([
105+
'Comments' => [
106+
'fields' => ['Comments.id', 'Comments.article_id', 'Comments.content'],
107+
],
108+
])
109+
->toArray();
110+
```
111+
Notice that `FROM` and `JOIN` clauses may use short or long aliases or no aliases at all (if the query does not use 'hasMany' or 'belongsToMany' associations) but all fields in `SELECT` clause must use aliases according to CakePHP naming convention `{Alias}__{field_name}`.
112+
113+
## 🔁 belongsToMany Example
114+
115+
```php
116+
$ArticlesTable = $this->fetchTable(ArticlesTable::class);
117+
$stmt = $ArticlesTable->prepareSQL("
118+
SELECT
119+
Articles.id AS Articles__id,
120+
Articles.title AS Articles__title,
121+
Tags.id AS Tags__id,
122+
Tags.name AS Tags__name
123+
FROM articles AS Articles
124+
LEFT JOIN articles_tags AS ArticlesTags
125+
ON Articles.id=ArticlesTags.article_id
126+
LEFT JOIN tags AS Tags
127+
ON Tags.id=ArticlesTags.tag_id
128+
");
129+
$entities = $ArticlesTable->fromNativeQuery($stmt)->all();
130+
```
131+
You can find more examples in tests - https://github.com/bancer/native-sql-mapper/tree/develop/tests/TestCase/ORM.
132+
133+
### Mapping
134+
---
135+
136+
## 🧠 How It Works
137+
138+
- Aliases are parsed using CakePHP’s `Alias__field` naming convention
139+
- Mapping is validated against real your ORM associations
140+
- Deep nested associations are built recursively
141+
- Only entities and associations that exist in your ORM are allowed
142+
143+
---
144+
145+
## ⚠️ Requirements
146+
147+
- Cake ORM **4.x** or **5.x** (or CakePHP **4.x** or **5.x**)
148+
- PHP **7.4+** or **8.0+**
149+
- PDO database driver
150+
151+
---
152+
153+
## 📝 Notes & Limitations
154+
155+
- Aliases **must** follow CakePHP-style naming: `Model__field`.
156+
- If SQL retrieves data from 'hasMany' or 'belongsToMany' associations then all primary columns must be present in `SELECT` clause
157+
- Fields without valid aliases throw exceptions
158+
- Associations must exist in the Table class, incorrect aliases throw exceptions
159+
- Pagination must be handled manually
160+
- This library is not a replacement of CakePHP query builder but a useful addition to it.
161+
162+
---
163+
164+
## ✔️ Summary
165+
166+
`native-sql-mapper` gives you the **freedom** of native SQL with the **structure** of CakePHP entities.
167+
It fills the gap between raw PDO statements and the ORM — allowing complex SQL while preserving the integrity of your entity graphs.
168+
169+
---
170+
```
171+

0 commit comments

Comments
 (0)