Skip to content

Commit d38d064

Browse files
committed
Find object aggregate service, find, sort and limit service, and JSON serialize models in KeyCollections
1 parent f2c5037 commit d38d064

9 files changed

Lines changed: 296 additions & 17 deletions

File tree

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,40 @@ $jobs = $pace->job
170170
->find();
171171
```
172172

173+
### Limiting
174+
175+
Use the `offset()` and `limit()` methods to limit your results. The default offset is 0 if it is not specified.
176+
177+
```php
178+
$jobs = $pace->job
179+
->filter('adminStatus/@openJob', true)
180+
->sort('@dateSetup', true)
181+
->limit(50)
182+
->find();
183+
```
184+
185+
You can also use the `paginate()` method to set the offset and limit for a page.
186+
187+
```php
188+
$jobs = $pace->job
189+
->filter('adminStatus/@openJob', true)
190+
->sort('@dateSetup', true)
191+
->paginate(1, 50)
192+
->find();
193+
```
194+
195+
### Eager loading
196+
197+
The `load()` method preloads the models as part of the find request, using the find object aggregate service. It does not read the entire object; you must specify a list of fields in XPath. If the offset and limit are not specified, then 0 and 1,000 will be used by default.
198+
199+
```php
200+
$employees = $pace->model('Employee')->filter('@status', 'A')->load([
201+
'@firstName',
202+
'@lastName',
203+
'department' => 'department/@description',
204+
])->find();
205+
```
206+
173207
## Dates
174208

175209
Dates are automatically converted to and from [Carbon](http://carbon.nesbot.com/) instances. Check out the `Soap\DateTimeMapper` and `Soap\Factory` classes if you want to see how this happens.

src/Client.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pace;
44

5+
use BadMethodCallException;
56
use Closure;
67
use InvalidArgumentException;
78
use Pace\Contracts\Soap\Factory as SoapFactory;
@@ -131,15 +132,33 @@ public function deleteObject(string $object, mixed $key): void
131132
* @param string $object
132133
* @param string $filter
133134
* @param array|null $sort
135+
* @param int|null $offset
136+
* @param int|null $limit
137+
* @param array $fields
134138
* @return array
135139
*/
136-
public function findObjects(string $object, string $filter, ?array $sort = null): array
140+
public function findObjects(string $object, string $filter, ?array $sort = null, ?int $offset = null, ?int $limit = null, array $fields = []): array
137141
{
138-
if (is_null($sort)) {
139-
return $this->service('FindObjects')->find($object, $filter);
142+
if (!empty($fields)) {
143+
if (is_null($offset) || is_null($limit)) {
144+
throw new BadMethodCallException('Offset and limit are required when fields is not empty.');
145+
}
146+
147+
// I think this is a bug in the Pace SOAP API? This method always returns limit + 1.
148+
$limit -= 1;
149+
150+
return $this->service('FindObjects')->loadValueObjects($object, $filter, $sort, $offset, $limit, $fields);
151+
}
152+
153+
if (!is_null($limit)) {
154+
return $this->service('FindObjects')->findSortAndLimit($object, $filter, $sort, $offset, $limit);
155+
}
156+
157+
if (!is_null($sort)) {
158+
return $this->service('FindObjects')->findAndSort($object, $filter, $sort);
140159
}
141160

142-
return $this->service('FindObjects')->findAndSort($object, $filter, $sort);
161+
return $this->service('FindObjects')->find($object, $filter);
143162
}
144163

145164
/**

src/KeyCollection.php

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,40 @@ public function __construct(protected Model $model, protected array $keys)
2828
{
2929
}
3030

31+
/**
32+
* Create a new key collection instance from an array of value objects.
33+
*
34+
* @param Model $model
35+
* @param array $valueObjects
36+
* @return static
37+
*/
38+
public static function fromValueObjects(Model $model, array $valueObjects): static
39+
{
40+
$keys = array_map(fn(object $valueObject) => $valueObject->primaryKey, $valueObjects);
41+
$readModels = array_combine(
42+
$keys,
43+
array_map(function (object $valueObject) use ($model) {
44+
$fields = $valueObject->fields->ValueField;
45+
$fields = is_array($fields) ? $fields : [$fields];
46+
$attributes = array_combine(
47+
array_map(fn(object $field) => $field->name, $fields),
48+
array_map(fn(object $field) => $field->value, $fields)
49+
);
50+
$attributes['primaryKey'] = $valueObject->primaryKey;
51+
52+
$readModel = $model->newInstance($attributes);
53+
$readModel->exists = true;
54+
55+
return $readModel;
56+
}, $valueObjects)
57+
);
58+
59+
$collection = new static($model, $keys);
60+
$collection->readModels = $readModels;
61+
62+
return $collection;
63+
}
64+
3165
/**
3266
* Convert this instance to its string representation.
3367
*
@@ -146,7 +180,7 @@ public function isEmpty(): bool
146180
*/
147181
function jsonSerialize(): array
148182
{
149-
return $this->all();
183+
return array_map(fn($value) => $value instanceof JsonSerializable ? $value->jsonSerialize() : $value, $this->all());
150184
}
151185

152186
/**

src/Model.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,23 @@ public function duplicate(mixed $newKey = null): ?static
195195
*
196196
* @param string $filter
197197
* @param array|null $sort
198+
* @param int|null $offset
199+
* @param int|null $limit
200+
* @param array $fields
198201
* @return KeyCollection
199202
*/
200-
public function find(string $filter, ?array $sort = null): KeyCollection
203+
public function find(string $filter, ?array $sort = null, ?int $offset = null, ?int $limit = null, array $fields = []): KeyCollection
201204
{
202-
$keys = $this->client->findObjects($this->type, $filter, $sort);
205+
if (!empty($fields)) {
206+
if (is_null($offset)) {
207+
$offset = 0;
208+
}
209+
if (is_null($limit)) {
210+
$limit = 1000;
211+
}
212+
}
213+
214+
$keys = $this->client->findObjects($this->type, $filter, $sort, $offset, $limit, $fields);
203215

204216
return $this->newKeyCollection($keys);
205217
}
@@ -657,6 +669,13 @@ public function newBuilder(): Builder
657669
*/
658670
protected function newKeyCollection(array $keys): KeyCollection
659671
{
672+
foreach ($keys as $key) {
673+
if (is_object($key)) {
674+
return KeyCollection::fromValueObjects($this, $keys);
675+
}
676+
break;
677+
}
678+
660679
return new KeyCollection($this, $keys);
661680
}
662681

src/Services/FindObjects.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,58 @@ public function findAndSort(string $object, string $filter, array $sort): array
3838

3939
return isset($response->out->string) ? (array)$response->out->string : [];
4040
}
41+
42+
/**
43+
* Find, sort and limit objects.
44+
*
45+
* @param string $object
46+
* @param string $filter
47+
* @param array|null $sort
48+
* @param int $offset
49+
* @param int $limit
50+
* @return array
51+
*/
52+
public function findSortAndLimit(string $object, string $filter, ?array $sort, int $offset, int $limit): array
53+
{
54+
$request = ['in0' => $object, 'in1' => $filter, 'in2' => $sort, 'in3' => $offset, 'in4' => $limit];
55+
56+
$response = $this->soap->findSortAndLimit($request);
57+
58+
return isset($response->out->string) ? (array)$response->out->string : [];
59+
}
60+
61+
/**
62+
* Call the find object aggregate service.
63+
*
64+
* @param string $object
65+
* @param string $filter
66+
* @param array|null $sort
67+
* @param int $offset
68+
* @param int $limit
69+
* @param array $fields
70+
* @param mixed $primaryKey
71+
* @return array
72+
*/
73+
public function loadValueObjects(string $object, string $filter, ?array $sort, int $offset, int $limit, array $fields, mixed $primaryKey = null): array
74+
{
75+
$request = [
76+
'in0' => [
77+
'objectName' => $object,
78+
'xpathFilter' => $filter,
79+
'xpathSorts' => $sort,
80+
'offset' => $offset,
81+
'limit' => $limit,
82+
'fields' => $fields,
83+
'primaryKey' => $primaryKey,
84+
],
85+
];
86+
87+
$response = $this->soap->loadValueObjects($request);
88+
89+
if (is_array($response->out->valueObjects->ValueObject)) {
90+
return $response->out->valueObjects->ValueObject;
91+
}
92+
93+
return [$response->out->valueObjects->ValueObject];
94+
}
4195
}

src/XPath/Builder.php

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ class Builder
3939
*/
4040
protected array $sorts = [];
4141

42+
/**
43+
* The fields to load.
44+
*
45+
* @var array
46+
*/
47+
protected array $fields = [];
48+
49+
/**
50+
* The result offset.
51+
*
52+
* @var int
53+
*/
54+
protected int $offset = 0;
55+
56+
/**
57+
* The results limit.
58+
*
59+
* @var int|null
60+
*/
61+
protected ?int $limit = null;
62+
4263
/**
4364
* Create a new instance.
4465
*
@@ -108,7 +129,7 @@ public function filter(string|Closure $xpath, mixed $operator = null, mixed $val
108129
*/
109130
public function find(): KeyCollection
110131
{
111-
return $this->model->find($this->toXPath(), $this->toXPathSort());
132+
return $this->model->find($this->toXPath(), $this->toXPathSort(), $this->offset, $this->limit, $this->toFieldDescriptor());
112133
}
113134

114135
/**
@@ -158,6 +179,64 @@ public function get(): KeyCollection
158179
return $this->find();
159180
}
160181

182+
/**
183+
* Load the specified fields.
184+
*
185+
* @param array $fields
186+
* @return $this
187+
*/
188+
public function load(array $fields): static
189+
{
190+
foreach ($fields as $key => $xpath) {
191+
if (is_int($key)) {
192+
$key = ltrim($xpath, '@');
193+
}
194+
$this->fields[$key] = $xpath;
195+
}
196+
197+
return $this;
198+
}
199+
200+
/**
201+
* Set the result offset.
202+
*
203+
* @param int $offset
204+
* @return $this
205+
*/
206+
public function offset(int $offset): static
207+
{
208+
$this->offset = $offset;
209+
210+
return $this;
211+
}
212+
213+
/**
214+
* Set the results limit.
215+
*
216+
* @param int $limit
217+
* @return $this
218+
*/
219+
public function limit(int $limit): static
220+
{
221+
$this->limit = $limit;
222+
223+
return $this;
224+
}
225+
226+
/**
227+
* Paginate the results.
228+
*
229+
* @param int $page
230+
* @param int $perPage
231+
* @return $this
232+
*/
233+
public function paginate(int $page, int $perPage = 25): static
234+
{
235+
$offset = max($page - 1, 0) * $perPage;
236+
237+
return $this->offset($offset)->limit($perPage);
238+
}
239+
161240
/**
162241
* Add an "in" filter.
163242
*
@@ -292,6 +371,21 @@ public function toXPathSort(): ?array
292371
return count($this->sorts) ? ['XPathDataSort' => $this->sorts] : null;
293372
}
294373

374+
/**
375+
* Get the field descriptor array.
376+
*
377+
* @return array
378+
*/
379+
public function toFieldDescriptor(): array
380+
{
381+
return array_map(function (string $name, string $xpath) {
382+
return [
383+
'name' => $name,
384+
'xpath' => $xpath,
385+
];
386+
}, array_keys($this->fields), array_values($this->fields));
387+
}
388+
295389
/**
296390
* Compile a simple filter.
297391
*

tests/KeyCollectionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ public function testJsonSerializable()
9999
$model = Mockery::mock(Model::class);
100100
$collection = new KeyCollection($model, [5]);
101101
$model->shouldReceive('read')->with(5)->once()->andReturnSelf();
102+
$model->shouldReceive('jsonSerialize')->once()->andReturn([]);
102103
$array = $collection->jsonSerialize();
103104
$this->assertInstanceOf('JsonSerializable', $collection);
104105
$this->assertIsArray($array);
105-
$this->assertContainsOnlyInstancesOf('JsonSerializable', $array);
106106
}
107107

108108
public function testArrayAccess()

0 commit comments

Comments
 (0)