Skip to content

Commit b63cac1

Browse files
committed
WIP
1 parent 733068e commit b63cac1

40 files changed

+801
-135
lines changed

doc/making-requests/querying-data.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,33 @@ GET http://localhost:8000/odata/People?$filter=FirstName eq 'Scott'
4343
</code-block>
4444
</code-group>
4545

46+
`$filter` can also be used as a path segment, and used multiple times to sequentially add filter parameters.
47+
48+
<code-group>
49+
<code-block title="Request">
50+
```uri
51+
GET http://localhost:8000/odata/People/$filter(@a)/$filter(@b)?@a=DOB gt 2000-01-01&@b=endswith(FirstName, 'tt')
52+
```
53+
</code-block>
54+
55+
<code-block title="Response">
56+
```json
57+
{
58+
"@context": "http://localhost:8000/odata/$metadata#People",
59+
"value": [
60+
{
61+
"id": 1,
62+
"FirstName": "Scott",
63+
"LastName": "Bumble",
64+
"Email": "scott.bumble@gmail.com",
65+
"DOB": "2001-01-01"
66+
}
67+
]
68+
}
69+
```
70+
</code-block>
71+
</code-group>
72+
4673
## $orderby
4774

4875
The `$orderby` system query option allows clients to request resources in either ascending order using asc or descending
@@ -457,3 +484,61 @@ GET http://localhost:8000/odata/People?$filter=pets/any(s:endswith(s/Name, 'The
457484
```
458485
</code-block>
459486
</code-group>
487+
488+
## $each
489+
490+
The `$each` path segment enables actions and odata operations such as deletes and updates to be run on sequences of
491+
entities server-side. To filter the sequence the `$filter` path segment must be used.
492+
493+
Members of a collection can be updated by submitting a PATCH request to the URL constructed by appending `/$each` to the
494+
resource path of the collection. The additional path segment expresses that the request body describes an update to
495+
each member of the collection, not an update to the collection itself.
496+
497+
<code-group>
498+
<code-block title="Request">
499+
```uri
500+
PATCH http://localhost/odata/People/$filter(@bar)/$each?@bar=Color eq 'beige-brown'
501+
{
502+
"Color": "taupe"
503+
}
504+
```
505+
</code-block>
506+
507+
<code-block title="Response">
508+
```json
509+
{
510+
"@context": "http://localhost:8000/odata/$metadata#People",
511+
"value": [
512+
{
513+
"id": 1,
514+
"FirstName": "Scott",
515+
"LastName": "Bumble",
516+
"Color": "taupe"
517+
},
518+
{
519+
"id": 3,
520+
"FirstName": "Michael",
521+
"LastName": "Scott",
522+
"Color": "taupe"
523+
}
524+
]
525+
}
526+
```
527+
</code-block>
528+
</code-group>
529+
530+
Members of a collection can be deleted by submitting a DELETE request to the URL constructed by appending `/$each`
531+
to the resource path of the collection. The additional path segment expresses that the collection itself is not
532+
deleted.
533+
534+
```uri
535+
DELETE http://localhost/odata/People/$filter(@bar)/$each?@bar=Color eq 'beige-brown'
536+
```
537+
538+
A bound operation with a single-valued binding parameter can be applied to each member of a collection by appending
539+
the path segment `$each` to the resource path of the collection, followed by a forward slash and the namespace- or
540+
alias-qualified name of the bound operation.
541+
542+
```uri
543+
GET http://localhost/odata/People/$each/SampleModel.MostRecentOrder()
544+
```

src/Controller/Transaction.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Flat3\Lodata\Controller;
66

77
use Exception;
8-
use Flat3\Lodata\Drivers\StaticEntitySet;
98
use Flat3\Lodata\Entity;
109
use Flat3\Lodata\EntitySet;
1110
use Flat3\Lodata\EntityType;
@@ -59,6 +58,7 @@
5958
use Flat3\Lodata\Transaction\Parameter;
6059
use Flat3\Lodata\Transaction\ParameterList;
6160
use Flat3\Lodata\Transaction\Version;
61+
use Flat3\Lodata\Type\Collection;
6262
use Illuminate\Support\Arr;
6363
use Illuminate\Support\Facades\App;
6464
use Illuminate\Support\Str;
@@ -227,6 +227,7 @@ class Transaction
227227
PathSegment\Filter::class,
228228
PathSegment\Query::class,
229229
PathSegment\Reference::class,
230+
PathSegment\Each::class,
230231
Operation::class,
231232
Singleton::class,
232233
PropertyValue::class,
@@ -1433,7 +1434,8 @@ public function processDeltaPayloads(Entity $parentEntity): void
14331434
/** @var NavigationProperty $navigationProperty */
14341435
foreach ($navigationProperties as $navigationProperty) {
14351436
$deltaPayloads = $body[$navigationProperty->getName()] ?? [];
1436-
$deltaResponseSet = new StaticEntitySet($navigationProperty->getEntityType());
1437+
$deltaResponseSet = new Collection();
1438+
$deltaResponseSet->setUnderlyingType($navigationProperty->getEntityType());
14371439

14381440
foreach ($deltaPayloads as $deltaPayload) {
14391441
$entity = null;

src/Drivers/EnumerableEntitySet.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Flat3\Lodata\Entity;
88
use Flat3\Lodata\EntitySet;
9+
use Flat3\Lodata\EntityType;
910
use Flat3\Lodata\Exception\Protocol\NotFoundException;
1011
use Flat3\Lodata\Expression\Parser\Common;
1112
use Flat3\Lodata\Expression\Parser\Search;
@@ -29,6 +30,13 @@ abstract class EnumerableEntitySet extends EntitySet implements ReadInterface, Q
2930
/** @var Enumerable|Collection|LazyCollection $enumerable */
3031
protected $enumerable;
3132

33+
public function __construct(string $identifier, ?EntityType $entityType = null)
34+
{
35+
parent::__construct($identifier, $entityType);
36+
37+
$this->enumerable = new Collection();
38+
}
39+
3240
/**
3341
* Query this entity set
3442
* @return Generator

src/Drivers/SQL/SQLSchema.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ function () {
6868
$key = $this->columnToDeclaredProperty($column);
6969

7070
if (null === $key) {
71-
throw new ConfigurationException('missing_key', sprintf('The table %s had no resolvable key', $this->getTable()));
71+
throw new ConfigurationException(
72+
'missing_key',
73+
sprintf('The table %s had no resolvable key', $this->getTable())
74+
);
7275
}
7376

7477
if ($column->getAutoincrement()) {

src/Drivers/StaticEntitySet.php

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

src/Entity.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,8 @@ public function getResourceUrl(Transaction $transaction): string
241241
* Delete this entity
242242
* @param Transaction $transaction Related transaction
243243
* @param ContextInterface|null $context Current context
244-
* @return Response Client response
245244
*/
246-
public function delete(Transaction $transaction, ?ContextInterface $context = null): Response
245+
public function delete(Transaction $transaction, ?ContextInterface $context = null): void
247246
{
248247
$entitySet = $this->entitySet;
249248

@@ -255,8 +254,6 @@ public function delete(Transaction $transaction, ?ContextInterface $context = nu
255254
$transaction->assertIfMatchHeader($this->getETag());
256255

257256
$entitySet->delete($this->getEntityId());
258-
259-
throw new NoContentException('deleted', 'Content was deleted');
260257
}
261258

262259
/**
@@ -331,7 +328,8 @@ public function response(Transaction $transaction, ?ContextInterface $context =
331328
return $this->patch($transaction, $context);
332329

333330
case Request::METHOD_DELETE:
334-
return $this->delete($transaction, $context);
331+
$this->delete($transaction, $context);
332+
throw new NoContentException('deleted', 'Content was deleted');
335333

336334
case Request::METHOD_GET:
337335
return $this->get($transaction, $context);

src/Operation.php

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Flat3\Lodata\Exception\Protocol\ConfigurationException;
1414
use Flat3\Lodata\Exception\Protocol\InternalServerErrorException;
1515
use Flat3\Lodata\Exception\Protocol\NoContentException;
16+
use Flat3\Lodata\Exception\Protocol\NotImplementedException;
1617
use Flat3\Lodata\Expression\Lexer;
1718
use Flat3\Lodata\Facades\Lodata;
1819
use Flat3\Lodata\Helper\Arguments;
@@ -22,6 +23,7 @@
2223
use Flat3\Lodata\Helper\JSON;
2324
use Flat3\Lodata\Helper\PropertyValue;
2425
use Flat3\Lodata\Interfaces\AnnotationInterface;
26+
use Flat3\Lodata\Interfaces\EntitySet\QueryInterface;
2527
use Flat3\Lodata\Interfaces\IdentifierInterface;
2628
use Flat3\Lodata\Interfaces\PipeInterface;
2729
use Flat3\Lodata\Interfaces\RepositoryInterface;
@@ -36,6 +38,7 @@
3638
use Flat3\Lodata\Operation\Repository;
3739
use Flat3\Lodata\Operation\TransactionArgument;
3840
use Flat3\Lodata\Operation\ValueArgument;
41+
use Flat3\Lodata\PathSegment\Each;
3942
use Flat3\Lodata\Traits\HasAnnotations;
4043
use Flat3\Lodata\Traits\HasIdentifier;
4144
use Flat3\Lodata\Traits\HasTitle;
@@ -637,6 +640,41 @@ public static function pipe(
637640
);
638641
}
639642

643+
if ($argument instanceof Each) {
644+
$entitySet = $argument->getArgument();
645+
646+
if (!$entitySet instanceof QueryInterface) {
647+
throw new NotImplementedException(
648+
'entityset_cannot_query',
649+
'This entity set cannot be queried',
650+
);
651+
}
652+
653+
$result = new Collection();
654+
$result->setUnderlyingType($operation->getReturnType());
655+
656+
foreach ($entitySet->query() as $entity) {
657+
$operationInstance = clone $operation;
658+
$operationTransaction = new Transaction();
659+
$request = Request::create(
660+
'',
661+
Request::METHOD_POST,
662+
[],
663+
[],
664+
[],
665+
[],
666+
$transaction->getRequest()->getContent()
667+
);
668+
$request->headers->replace($transaction->getRequestHeaders());
669+
$operationTransaction->initialize(new Controller\Request($request));
670+
$operationInstance->setTransaction($operationTransaction);
671+
$operationInstance->setBoundParameter($entity);
672+
$result[] = $operationInstance->executeAction();
673+
}
674+
675+
return $result;
676+
}
677+
640678
$operation = clone $operation;
641679
$operation->setTransaction($transaction);
642680
$operation->setBoundParameter($argument);
@@ -734,11 +772,16 @@ public function ensureResult($result): ?PipeInterface
734772
);
735773
}
736774

737-
if ($returnType instanceof EntityType && !$result->getType() instanceof $returnType) {
738-
throw new InternalServerErrorException(
739-
'invalid_entity_type_returned',
740-
'The operation returned an entity type that did not match its defined type',
741-
);
775+
if ($returnType instanceof EntityType) {
776+
if (
777+
($result instanceof Collection && !$result->getUnderlyingType() instanceof $returnType) ||
778+
($result instanceof ComplexValue && !$result->getType() instanceof $returnType)
779+
) {
780+
throw new InternalServerErrorException(
781+
'invalid_entity_type_returned',
782+
'The operation returned an entity type that did not match its defined type',
783+
);
784+
}
742785
}
743786

744787
if ($returnType instanceof PrimitiveType && !$result instanceof Primitive) {

0 commit comments

Comments
 (0)