Skip to content

Commit d2eb55c

Browse files
committed
Remove POST and PUT methods to abstract classes
1 parent 12fca21 commit d2eb55c

File tree

4 files changed

+194
-44
lines changed

4 files changed

+194
-44
lines changed

DependencyInjection/Compiler/ResourcePass.php

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,40 @@ public function process(ContainerBuilder $container)
4242
continue;
4343
}
4444

45+
$isAbstract = $this->isAbstract($resourceDefinition->getArgument(0));
4546
if (!$resourceDefinition->hasMethodCall('initItemOperations')) {
46-
$resourceDefinition->addMethodCall('initItemOperations', [[
47-
$this->createOperation($container, $serviceId, 'GET', false),
48-
$this->createOperation($container, $serviceId, 'PUT', false),
49-
$this->createOperation($container, $serviceId, 'DELETE', false),
50-
]]);
47+
$methods = $isAbstract ? ['GET', 'DELETE'] : ['GET', 'PUT', 'DELETE'];
48+
$resourceDefinition->addMethodCall('initItemOperations', [$this->createOperations($container, $serviceId, $methods, false)]);
5149
}
5250

5351
if (!$resourceDefinition->hasMethodCall('initCollectionOperations')) {
54-
$resourceDefinition->addMethodCall('initCollectionOperations', [[
55-
$this->createOperation($container, $serviceId, 'GET', true),
56-
$this->createOperation($container, $serviceId, 'POST', true),
57-
]]);
52+
53+
$methods = $isAbstract ? ['GET'] : ['GET', 'POST'];
54+
$resourceDefinition->addMethodCall('initCollectionOperations', [$this->createOperations($container, $serviceId, $methods, true)]);
5855
}
5956
}
6057

6158
$resourceCollectionDefinition->addMethodCall('init', [$resourceReferences]);
6259
}
6360

61+
62+
/**
63+
* Adds a list of operations.
64+
*
65+
* @param ContainerBuilder $container
66+
* @param string $serviceId
67+
* @param array $methods
68+
* @param bool $collection
69+
*
70+
* @return Reference[]
71+
*/
72+
private function createOperations(ContainerBuilder $container, $serviceId, $methods, $collection)
73+
{
74+
return array_map(function ($method) use ($container, $serviceId, $collection) {
75+
return $this->createOperation($container, $serviceId, $method, $collection);
76+
}, $methods);
77+
}
78+
6479
/**
6580
* Adds an operation.
6681
*
@@ -94,6 +109,19 @@ private function createOperation(ContainerBuilder $container, $serviceId, $metho
94109
return new Reference($operationId);
95110
}
96111

112+
/**
113+
* Returns if the given class is abstract.
114+
*
115+
* @param string $instanceClass
116+
*
117+
* @return boolean
118+
*/
119+
private function isAbstract($instanceClass)
120+
{
121+
$reflectionClass = new \ReflectionClass($instanceClass);
122+
return $reflectionClass->isAbstract();
123+
}
124+
97125
/**
98126
* Gets class of the given definition.
99127
*

Tests/DependencyInjection/Compiler/ResourcePassTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function testProcess()
3737

3838
$builtinResourceDefinitionProphecy = $this->prophesize('Symfony\Component\DependencyInjection\Definition');
3939
$builtinResourceDefinitionProphecy->getClass()->willReturn('Dunglas\ApiBundle\Api\Resource')->shouldBeCalled();
40+
$builtinResourceDefinitionProphecy->getArgument(0)->willReturn('stdClass')->shouldBeCalled();
4041
$builtinResourceDefinitionProphecy->hasMethodCall('initItemOperations')->willReturn(true)->shouldBeCalled();
4142
$builtinResourceDefinitionProphecy->addMethodCall('initItemOperations')->shouldNotBeCalled();
4243
$builtinResourceDefinitionProphecy->hasMethodCall('initCollectionOperations', Argument::any())->willReturn(true)->shouldBeCalled();
@@ -50,6 +51,7 @@ public function testProcess()
5051
$decoratedResourceDefinitionProphecy = $this->prophesize('Symfony\Component\DependencyInjection\DefinitionDecorator');
5152
$decoratedResourceDefinitionProphecy->getClass()->willReturn(false)->shouldBeCalled();
5253
$decoratedResourceDefinitionProphecy->getParent()->willReturn('inner_resource')->shouldBeCalled();
54+
$decoratedResourceDefinitionProphecy->getArgument(0)->willReturn('stdClass')->shouldBeCalled();
5355
$decoratedResourceDefinitionProphecy->hasMethodCall('initItemOperations')->willReturn(false)->shouldBeCalled();
5456
$decoratedResourceDefinitionProphecy->addMethodCall('initItemOperations', Argument::type('array'))->shouldBeCalled();
5557
$decoratedResourceDefinitionProphecy->hasMethodCall('initCollectionOperations')->willReturn(false)->shouldBeCalled();

features/crud_abstract.feature

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,6 @@ Feature: Create-Retrieve-Update-Delete on abstract resource
44
I need to be able to retrieve, create, update and delete JSON-LD encoded resources even if they are abstract.
55

66
@createSchema
7-
Scenario: Create an abstract resource
8-
When I send a "POST" request to "/abstract_dummies" with body:
9-
"""
10-
{
11-
"instance": "Concrete",
12-
"name": "My Dummy"
13-
}
14-
"""
15-
Then the response status code should be 400
16-
And the response should be in JSON
17-
And the header "Content-Type" should be equal to "application/ld+json"
18-
And the JSON node "@context" should be equal to "/contexts/Error"
19-
And the JSON node "@type" should be equal to "Error"
20-
And the JSON node "hydra:title" should be equal to "An error occurred"
21-
And the JSON node "hydra:description" should be equal to "Cannot create an instance of Dunglas\ApiBundle\Tests\Behat\TestBundle\Entity\AbstractDummy from serialized data because it is an abstract resource"
22-
And the JSON node "trace" should exist
23-
247
Scenario: Create a concrete resource
258
When I send a "POST" request to "/concrete_dummies" with body:
269
"""
@@ -116,24 +99,6 @@ Feature: Create-Retrieve-Update-Delete on abstract resource
11699
}
117100
"""
118101

119-
Scenario: Update an abstract resource
120-
When I send a "PUT" request to "/abstract_dummies/1" with body:
121-
"""
122-
{
123-
"@id": "/concrete_dummies/1",
124-
"instance": "Become real",
125-
"name": "A nice dummy"
126-
}
127-
"""
128-
Then the response status code should be 400
129-
And the response should be in JSON
130-
And the header "Content-Type" should be equal to "application/ld+json"
131-
And the JSON node "@context" should be equal to "/contexts/Error"
132-
And the JSON node "@type" should be equal to "Error"
133-
And the JSON node "hydra:title" should be equal to "An error occurred"
134-
And the JSON node "hydra:description" should be equal to "Cannot create an instance of Dunglas\ApiBundle\Tests\Behat\TestBundle\Entity\AbstractDummy from serialized data because it is an abstract resource"
135-
And the JSON node "trace" should exist
136-
137102
Scenario: Update a concrete resource
138103
When I send a "PUT" request to "/concrete_dummies/1" with body:
139104
"""

features/hydra/doc.feature

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,109 @@ Feature: Documentation support
420420
}
421421
]
422422
},
423+
{
424+
"@id": "#AbstractDummy",
425+
"@type": "hydra:Class",
426+
"rdfs:label": "AbstractDummy",
427+
"hydra:title": "AbstractDummy",
428+
"hydra:description": "AbstractDummy.",
429+
"hydra:supportedProperty": [
430+
{
431+
"@type": "hydra:SupportedProperty",
432+
"hydra:property": {
433+
"@id": "http://schema.org/name",
434+
"@type": "rdf:Property",
435+
"rdfs:label": "name",
436+
"domain": "#AbstractDummy",
437+
"range": "xmls:string"
438+
},
439+
"hydra:title": "name",
440+
"hydra:required": true,
441+
"hydra:readable": true,
442+
"hydra:writable": true,
443+
"hydra:description": "The dummy name."
444+
}
445+
],
446+
"hydra:supportedOperation": [
447+
{
448+
"@type": "hydra:Operation",
449+
"hydra:method": "GET",
450+
"hydra:title": "Retrieves AbstractDummy resource.",
451+
"rdfs:label": "Retrieves AbstractDummy resource.",
452+
"returns": "#AbstractDummy"
453+
},
454+
{
455+
"@type": "hydra:Operation",
456+
"hydra:method": "DELETE",
457+
"hydra:title": "Deletes the AbstractDummy resource.",
458+
"rdfs:label": "Deletes the AbstractDummy resource.",
459+
"returns": "owl:Nothing"
460+
}
461+
]
462+
},
463+
{
464+
"@id": "#ConcreteDummy",
465+
"@type": "hydra:Class",
466+
"rdfs:label": "ConcreteDummy",
467+
"hydra:title": "ConcreteDummy",
468+
"hydra:description": "ConcreteDummy.",
469+
"hydra:supportedProperty": [
470+
{
471+
"@type": "hydra:SupportedProperty",
472+
"hydra:property": {
473+
"@id": "#ConcreteDummy/instance",
474+
"@type": "rdf:Property",
475+
"rdfs:label": "instance",
476+
"domain": "#ConcreteDummy",
477+
"range": "xmls:string"
478+
},
479+
"hydra:title": "instance",
480+
"hydra:required": true,
481+
"hydra:readable": true,
482+
"hydra:writable": true,
483+
"hydra:description": "a concrete thing"
484+
},
485+
{
486+
"@type": "hydra:SupportedProperty",
487+
"hydra:property": {
488+
"@id": "http://schema.org/name",
489+
"@type": "rdf:Property",
490+
"rdfs:label": "name",
491+
"domain": "#ConcreteDummy",
492+
"range": "xmls:string"
493+
},
494+
"hydra:title": "name",
495+
"hydra:required": false,
496+
"hydra:readable": true,
497+
"hydra:writable": true,
498+
"hydra:description": "The dummy name."
499+
}
500+
],
501+
"hydra:supportedOperation": [
502+
{
503+
"@type": "hydra:Operation",
504+
"hydra:method": "GET",
505+
"hydra:title": "Retrieves ConcreteDummy resource.",
506+
"rdfs:label": "Retrieves ConcreteDummy resource.",
507+
"returns": "#ConcreteDummy"
508+
},
509+
{
510+
"@type": "hydra:ReplaceResourceOperation",
511+
"expects": "#ConcreteDummy",
512+
"hydra:method": "PUT",
513+
"hydra:title": "Replaces the ConcreteDummy resource.",
514+
"rdfs:label": "Replaces the ConcreteDummy resource.",
515+
"returns": "#ConcreteDummy"
516+
},
517+
{
518+
"@type": "hydra:Operation",
519+
"hydra:method": "DELETE",
520+
"hydra:title": "Deletes the ConcreteDummy resource.",
521+
"rdfs:label": "Deletes the ConcreteDummy resource.",
522+
"returns": "owl:Nothing"
523+
}
524+
]
525+
},
423526
{
424527
"@id": "#Custom",
425528
"@type": "hydra:Class",
@@ -858,6 +961,58 @@ Feature: Documentation support
858961
"hydra:readable": true,
859962
"hydra:writable": false
860963
},
964+
{
965+
"@type": "hydra:SupportedProperty",
966+
"hydra:property": {
967+
"@id": "#Entrypoint/abstractDummy",
968+
"@type": "hydra:Link",
969+
"domain": "#Entrypoint",
970+
"rdfs:label": "The collection of AbstractDummy resources",
971+
"range": "hydra:PagedCollection",
972+
"hydra:supportedOperation": [
973+
{
974+
"@type": "hydra:Operation",
975+
"hydra:method": "GET",
976+
"hydra:title": "Retrieves the collection of AbstractDummy resources.",
977+
"rdfs:label": "Retrieves the collection of AbstractDummy resources.",
978+
"returns": "hydra:PagedCollection"
979+
}
980+
]
981+
},
982+
"hydra:title": "The collection of AbstractDummy resources",
983+
"hydra:readable": true,
984+
"hydra:writable": false
985+
},
986+
{
987+
"@type": "hydra:SupportedProperty",
988+
"hydra:property": {
989+
"@id": "#Entrypoint/concreteDummy",
990+
"@type": "hydra:Link",
991+
"domain": "#Entrypoint",
992+
"rdfs:label": "The collection of ConcreteDummy resources",
993+
"range": "hydra:PagedCollection",
994+
"hydra:supportedOperation": [
995+
{
996+
"@type": "hydra:Operation",
997+
"hydra:method": "GET",
998+
"hydra:title": "Retrieves the collection of ConcreteDummy resources.",
999+
"rdfs:label": "Retrieves the collection of ConcreteDummy resources.",
1000+
"returns": "hydra:PagedCollection"
1001+
},
1002+
{
1003+
"@type": "hydra:CreateResourceOperation",
1004+
"expects": "#ConcreteDummy",
1005+
"hydra:method": "POST",
1006+
"hydra:title": "Creates a ConcreteDummy resource.",
1007+
"rdfs:label": "Creates a ConcreteDummy resource.",
1008+
"returns": "#ConcreteDummy"
1009+
}
1010+
]
1011+
},
1012+
"hydra:title": "The collection of ConcreteDummy resources",
1013+
"hydra:readable": true,
1014+
"hydra:writable": false
1015+
},
8611016
{
8621017
"@type": "hydra:SupportedProperty",
8631018
"hydra:property": {

0 commit comments

Comments
 (0)