Skip to content

Commit e568f4a

Browse files
claudef3l1x
authored andcommitted
Update to Nextras 4 compatibility
- Update PHP requirement to >=8.1 (required by nette/di 3.x) - Fix deprecated TAG_INJECT constant (now TagInject) - Update PHPStan configuration for proper analysis - Add comprehensive test coverage for QueryObject, ExecutableQueryObject, QueryObjectContextAwareManager, TRepositoryQueryable, and exceptions Closes #9
1 parent 41d1727 commit e568f4a

9 files changed

Lines changed: 440 additions & 4 deletions

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
}
1212
],
1313
"require": {
14-
"php": ">=7.2",
14+
"php": ">=8.1",
1515
"nextras/dbal": "^4.0.0"
1616
},
1717
"require-dev": {

phpstan.neon

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,11 @@ includes:
66

77
parameters:
88
level: 9
9-
phpVersion: 70200
9+
phpVersion: 80100
10+
paths:
11+
- src
12+
scanDirectories:
13+
- vendor/nette/di/src
14+
- vendor/nextras/orm/src
15+
- vendor/nextras/dbal/src
1016

src/DI/NextrasQueryObjectExtension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public function beforeCompile(): void
3232
$builder = $this->getContainerBuilder();
3333

3434
foreach ($builder->findByType(Repository::class) as $name => $def) {
35-
$def->addTag(InjectExtension::TAG_INJECT);
35+
$def->addTag(InjectExtension::TagInject);
3636
}
3737

3838
foreach ($builder->findByType(Mapper::class) as $name => $def) {
39-
$def->addTag(InjectExtension::TAG_INJECT);
39+
$def->addTag(InjectExtension::TagInject);
4040
}
4141
}
4242

tests/Cases/Exceptions.phpt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Contributte\Nextras\Orm\QueryObject\Exception\InvalidHydrationModeException;
4+
use Contributte\Nextras\Orm\QueryObject\Exception\InvalidObjectCreationException;
5+
use Tester\Assert;
6+
7+
require_once __DIR__ . '/../bootstrap.php';
8+
9+
// Test: InvalidHydrationModeException extends LogicException
10+
test(function (): void {
11+
$exception = new InvalidHydrationModeException('Test message');
12+
13+
Assert::type(LogicException::class, $exception);
14+
Assert::same('Test message', $exception->getMessage());
15+
});
16+
17+
// Test: InvalidObjectCreationException extends LogicException
18+
test(function (): void {
19+
$exception = new InvalidObjectCreationException('Creation failed');
20+
21+
Assert::type(LogicException::class, $exception);
22+
Assert::same('Creation failed', $exception->getMessage());
23+
});
24+
25+
// Test: InvalidHydrationModeException can be thrown and caught
26+
test(function (): void {
27+
Assert::exception(function (): void {
28+
throw new InvalidHydrationModeException('Invalid hydration mode "99"');
29+
}, InvalidHydrationModeException::class, 'Invalid hydration mode "99"');
30+
});
31+
32+
// Test: InvalidObjectCreationException can be thrown and caught
33+
test(function (): void {
34+
Assert::exception(function (): void {
35+
throw new InvalidObjectCreationException('Created object must be typed of QueryObject');
36+
}, InvalidObjectCreationException::class, 'Created object must be typed of QueryObject');
37+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Contributte\Nextras\Orm\QueryObject\ExecutableQueryObject;
4+
use Nextras\Dbal\Connection;
5+
use Nextras\Dbal\QueryBuilder\QueryBuilder;
6+
use Nextras\Dbal\Result\Result;
7+
use Tester\Assert;
8+
9+
require_once __DIR__ . '/../bootstrap.php';
10+
11+
/**
12+
* Test ExecutableQueryObject
13+
*/
14+
class TestExecutableQueryObject extends ExecutableQueryObject
15+
{
16+
17+
public function doQuery(QueryBuilder $builder): QueryBuilder
18+
{
19+
return $builder->select('[*]')->from('[test_table]');
20+
}
21+
22+
}
23+
24+
/**
25+
* Test ExecutableQueryObject with custom postResult
26+
*/
27+
class TestExecutableQueryObjectWithPostResult extends ExecutableQueryObject
28+
{
29+
30+
private bool $postResultCalled = false;
31+
32+
public function doQuery(QueryBuilder $builder): QueryBuilder
33+
{
34+
return $builder->select('[id]')->from('[users]');
35+
}
36+
37+
public function postResult(Result $result): Result
38+
{
39+
$this->postResultCalled = true;
40+
return parent::postResult($result);
41+
}
42+
43+
public function wasPostResultCalled(): bool
44+
{
45+
return $this->postResultCalled;
46+
}
47+
48+
}
49+
50+
// Test: ExecutableQueryObject builds query via fetch method
51+
test(function (): void {
52+
$connection = Mockery::mock(Connection::class);
53+
$queryBuilder = Mockery::mock(QueryBuilder::class);
54+
55+
$queryBuilder->shouldReceive('select')
56+
->with('[*]')
57+
->once()
58+
->andReturnSelf();
59+
60+
$queryBuilder->shouldReceive('from')
61+
->with('[test_table]')
62+
->once()
63+
->andReturnSelf();
64+
65+
$qo = new TestExecutableQueryObject($connection);
66+
$result = $qo->fetch($queryBuilder);
67+
68+
Assert::type(QueryBuilder::class, $result);
69+
70+
Mockery::close();
71+
});
72+
73+
// Test: ExecutableQueryObject execute method uses connection
74+
test(function (): void {
75+
$connection = Mockery::mock(Connection::class);
76+
$queryBuilder = Mockery::mock(QueryBuilder::class);
77+
$result = Mockery::mock(Result::class);
78+
79+
$queryBuilder->shouldReceive('select')
80+
->with('[*]')
81+
->once()
82+
->andReturnSelf();
83+
84+
$queryBuilder->shouldReceive('from')
85+
->with('[test_table]')
86+
->once()
87+
->andReturnSelf();
88+
89+
$queryBuilder->shouldReceive('getQuerySql')
90+
->once()
91+
->andReturn('SELECT * FROM test_table');
92+
93+
$queryBuilder->shouldReceive('getQueryParameters')
94+
->once()
95+
->andReturn([]);
96+
97+
$connection->shouldReceive('createQueryBuilder')
98+
->once()
99+
->andReturn($queryBuilder);
100+
101+
$connection->shouldReceive('queryArgs')
102+
->with('SELECT * FROM test_table', [])
103+
->once()
104+
->andReturn($result);
105+
106+
$qo = new TestExecutableQueryObject($connection);
107+
$executeResult = $qo->execute();
108+
109+
Assert::type(Result::class, $executeResult);
110+
111+
Mockery::close();
112+
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Contributte\Nextras\Orm\QueryObject\Exception\InvalidObjectCreationException;
4+
use Contributte\Nextras\Orm\QueryObject\QueryObject;
5+
use Contributte\Nextras\Orm\QueryObject\QueryObjectContextAwareManager;
6+
use Nette\DI\Container;
7+
use Nextras\Dbal\Connection;
8+
use Nextras\Dbal\QueryBuilder\QueryBuilder;
9+
use Nextras\Dbal\Result\Result;
10+
use Tester\Assert;
11+
use Tests\Mocks\SimpleQueryObject;
12+
13+
require_once __DIR__ . '/../bootstrap.php';
14+
15+
// Test: QueryObjectContextAwareManager create returns QueryObject
16+
test(function (): void {
17+
$queryObject = new SimpleQueryObject();
18+
19+
$container = Mockery::mock(Container::class);
20+
$container->shouldReceive('getByType')
21+
->with(SimpleQueryObject::class)
22+
->once()
23+
->andReturn($queryObject);
24+
25+
$manager = new QueryObjectContextAwareManager($container);
26+
$result = $manager->create(SimpleQueryObject::class);
27+
28+
Assert::type(QueryObject::class, $result);
29+
Assert::same($queryObject, $result);
30+
31+
Mockery::close();
32+
});
33+
34+
// Test: QueryObjectContextAwareManager create throws InvalidObjectCreationException for non-QueryObject
35+
test(function (): void {
36+
$nonQueryObject = new stdClass();
37+
38+
$container = Mockery::mock(Container::class);
39+
$container->shouldReceive('getByType')
40+
->with(stdClass::class)
41+
->once()
42+
->andReturn($nonQueryObject);
43+
44+
$manager = new QueryObjectContextAwareManager($container);
45+
46+
Assert::exception(function () use ($manager): void {
47+
$manager->create(stdClass::class);
48+
}, InvalidObjectCreationException::class);
49+
50+
Mockery::close();
51+
});
52+
53+
// Test: QueryObjectContextAwareManager fetch returns Result
54+
test(function (): void {
55+
$connection = Mockery::mock(Connection::class);
56+
$queryBuilder = Mockery::mock(QueryBuilder::class);
57+
$result = Mockery::mock(Result::class);
58+
59+
$queryBuilder->shouldReceive('select')
60+
->with('[*]')
61+
->once()
62+
->andReturnSelf();
63+
64+
$queryBuilder->shouldReceive('from')
65+
->with('[foobar]')
66+
->once()
67+
->andReturnSelf();
68+
69+
$queryBuilder->shouldReceive('getQuerySql')
70+
->once()
71+
->andReturn('SELECT * FROM foobar');
72+
73+
$queryBuilder->shouldReceive('getQueryParameters')
74+
->once()
75+
->andReturn([]);
76+
77+
$connection->shouldReceive('createQueryBuilder')
78+
->once()
79+
->andReturn($queryBuilder);
80+
81+
$connection->shouldReceive('queryArgs')
82+
->with('SELECT * FROM foobar', [])
83+
->once()
84+
->andReturn($result);
85+
86+
$container = Mockery::mock(Container::class);
87+
$container->shouldReceive('getByType')
88+
->with(Connection::class)
89+
->once()
90+
->andReturn($connection);
91+
92+
$manager = new QueryObjectContextAwareManager($container);
93+
$queryObject = new SimpleQueryObject();
94+
$fetchResult = $manager->fetch($queryObject);
95+
96+
Assert::type(Result::class, $fetchResult);
97+
98+
Mockery::close();
99+
});

tests/Cases/Queryable.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Contributte\Nextras\Orm\QueryObject\Queryable;
4+
use Tester\Assert;
5+
6+
require_once __DIR__ . '/../bootstrap.php';
7+
8+
// Test: Queryable interface defines HYDRATION_RESULTSET constant
9+
test(function (): void {
10+
Assert::same(1, Queryable::HYDRATION_RESULTSET);
11+
});
12+
13+
// Test: Queryable interface defines HYDRATION_ENTITY constant
14+
test(function (): void {
15+
Assert::same(2, Queryable::HYDRATION_ENTITY);
16+
});

0 commit comments

Comments
 (0)