Skip to content

Commit a051297

Browse files
committed
update
1 parent 68bda94 commit a051297

5 files changed

Lines changed: 121 additions & 9 deletions

File tree

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"require": {
1212
"php": ">=8.0",
1313
"ext-memcache": "*",
14-
"ext-mysqli": "*"
14+
"ext-mysqli": "*",
15+
"mintyphp/pathql": "*"
1516
},
1617
"require-dev": {
1718
"phpunit/phpunit": "*",

src/Core/Orm.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace MintyPHP\Core;
44

55
use MintyPHP\Core\DB;
6+
use MintyPHP\Core\Cache;
7+
use MintyPHP\PathQL\PathQL;
68

79
/**
810
* ORM (Object-Relational Mapping) layer for MintyPHP
@@ -11,12 +13,16 @@
1113
*/
1214
class Orm
1315
{
16+
private PathQL $pathQL;
17+
1418
/**
1519
* Constructor for the Orm class.
1620
* @param DB $db Database instance for executing queries.
21+
* @param Cache $cache Cache instance for storing schema metadata.
1722
*/
18-
public function __construct(private DB $db)
23+
public function __construct(private DB $db, Cache $cache)
1924
{
25+
$this->pathQL = new PathQL($db, $cache);
2026
}
2127

2228
/**
@@ -106,4 +112,22 @@ public function delete(string $tableName, string|int $id, string $idField = 'id'
106112
$sql = "DELETE FROM `$tableName` WHERE `$idField` = ?";
107113
return $this->db->delete($sql, $id) ? true : false;
108114
}
115+
116+
/**
117+
* Execute a query with automatic path inference for hierarchical results using PathQL.
118+
*
119+
* Automatically infers the structure of the result set based on SQL JOINs and
120+
* foreign key relationships, returning nested arrays/objects instead of flat rows.
121+
*
122+
* @param string $sql The SQL query to execute
123+
* @param array<int|string,mixed> $params Parameters for prepared statement
124+
* @param array<string,string> $pathHints Optional path mappings for table aliases
125+
* Format: ['alias' => '$.path', 'other' => '$.parent.child[]']
126+
* @return array<int|string,mixed> Hierarchical result structure based on inferred paths
127+
* @throws \RuntimeException If query execution fails
128+
*/
129+
public function path(string $sql, array $params = [], array $pathHints = []): array
130+
{
131+
return $this->pathQL->execute($sql, $params, $pathHints);
132+
}
109133
}

src/Error/TemplateError.php

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/Orm.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class Orm
3333
public static function getInstance(): CoreOrm
3434
{
3535
return self::$instance ??= new CoreOrm(
36-
DB::getInstance()
36+
DB::getInstance(),
37+
Cache::getInstance()
3738
);
3839
}
3940

@@ -102,4 +103,23 @@ public static function delete(string $tableName, string|int $id, string $idField
102103
$instance = self::getInstance();
103104
return $instance->delete($tableName, $id, $idField);
104105
}
106+
107+
/**
108+
* Execute a query with automatic path inference for hierarchical results using PathQL.
109+
*
110+
* Automatically infers the structure of the result set based on SQL JOINs and
111+
* foreign key relationships, returning nested arrays/objects instead of flat rows.
112+
*
113+
* @param string $sql The SQL query to execute
114+
* @param array<int|string,mixed> $params Parameters for prepared statement
115+
* @param array<string,string> $pathHints Optional path mappings for table aliases
116+
* Format: ['alias' => '$.path', 'other' => '$.parent.child[]']
117+
* @return array<int|string,mixed> Hierarchical result structure based on inferred paths
118+
* @throws \RuntimeException If query execution fails
119+
*/
120+
public static function path(string $sql, array $params = [], array $pathHints = []): array
121+
{
122+
$instance = self::getInstance();
123+
return $instance->path($sql, $params, $pathHints);
124+
}
105125
}

tests/Core/OrmTest.php

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MintyPHP\Tests\Core;
44

55
use MintyPHP\Core\DB;
6+
use MintyPHP\Core\Cache;
67
use MintyPHP\Core\Orm;
78
use PHPUnit\Framework\TestCase;
89

@@ -16,15 +17,19 @@
1617
class OrmTest extends TestCase
1718
{
1819
private static DB $db;
20+
private static Cache $cache;
1921
private static Orm $orm;
2022

2123
public static function setUpBeforeClass(): void
2224
{
2325
// Create database connection
2426
self::$db = new DB(null, 'mintyphp_test', 'mintyphp_test', 'mintyphp_test', null, null);
2527

28+
// Create cache instance
29+
self::$cache = new Cache('mintyphp_test_', 'localhost:11211');
30+
2631
// Create Core ORM instance
27-
self::$orm = new Orm(self::$db);
32+
self::$orm = new Orm(self::$db, self::$cache);
2833
}
2934

3035
public function testDropPostsBefore(): void
@@ -202,6 +207,73 @@ public function testSelectPostNotFound(): void
202207
* @depends testInsertPosts
203208
* @depends testUpdatePosts
204209
*/
210+
public function testPathQueryWithoutHints(): void
211+
{
212+
// Test automatic path inference - posts with their users
213+
$result = self::$orm->path(
214+
'SELECT p.id, p.slug, u.id, u.username
215+
FROM posts p
216+
JOIN users u ON p.user_id = u.id
217+
WHERE p.id <= ?
218+
ORDER BY p.id',
219+
[2]
220+
);
221+
222+
// Should return nested structure with users as objects within posts array
223+
$this->assertCount(2, $result);
224+
assert(is_array($result[0]), 'result[0] is not an array');
225+
$this->assertEquals(1, $result[0]['id']);
226+
$this->assertEquals('2014-08-test1', $result[0]['slug']);
227+
$this->assertArrayHasKey('u', $result[0]);
228+
$this->assertIsArray($result[0]['u']);
229+
$this->assertEquals(1, $result[0]['u']['id']);
230+
$this->assertEquals('test1', $result[0]['u']['username']);
231+
}
232+
233+
/**
234+
* @depends testDropPostsBefore
235+
* @depends testDropUsersBefore
236+
* @depends testCreateUsers
237+
* @depends testCreatePosts
238+
* @depends testInsertUsers
239+
* @depends testInsertPosts
240+
* @depends testUpdatePosts
241+
*/
242+
public function testPathQueryWithHints(): void
243+
{
244+
// Test with custom path hints for cleaner structure
245+
$result = self::$orm->path(
246+
'SELECT p.id, p.slug, p.title, u.id, u.username
247+
FROM posts p
248+
JOIN users u ON p.user_id = u.id
249+
WHERE p.id = ?',
250+
[1],
251+
[
252+
'p' => '$',
253+
'u' => '$.author'
254+
]
255+
);
256+
257+
// Should return single object with custom 'author' field
258+
$this->assertEquals(1, $result['id']);
259+
$this->assertEquals('2014-08-test1', $result['slug']);
260+
$this->assertEquals('test', $result['title']);
261+
$this->assertArrayHasKey('author', $result);
262+
$this->assertIsArray($result['author']);
263+
$this->assertEquals(1, $result['author']['id']);
264+
$this->assertEquals('test1', $result['author']['username']);
265+
}
266+
267+
/**
268+
* @depends testDropPostsBefore
269+
* @depends testDropUsersBefore
270+
* @depends testCreateUsers
271+
* @depends testCreatePosts
272+
* @depends testInsertUsers
273+
* @depends testInsertPosts
274+
* @depends testUpdatePosts
275+
* @depends testDeletePosts
276+
*/
205277
public function testDeletePosts(): void
206278
{
207279
$result = self::$orm->delete('posts', 1);

0 commit comments

Comments
 (0)