Skip to content

Commit 421eccb

Browse files
committed
Merge pull request #1 from sroze/non-writable-attributes
Guess non writable attributes
2 parents 1df9e2d + 0e0c52a commit 421eccb

File tree

5 files changed

+186
-39
lines changed

5 files changed

+186
-39
lines changed

Mapping/ClassMetadata.php

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -158,31 +158,27 @@ public function getAttributes(
158158
$reflClass = $this->getReflectionClass();
159159
// methods
160160
foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
161-
if (
162-
!$reflMethod->isConstructor() &&
163-
!$reflMethod->isDestructor() &&
164-
0 === $reflMethod->getNumberOfRequiredParameters()
165-
) {
166-
$methodName = $reflMethod->getName();
167-
168-
if (strpos($methodName, 'get') === 0 || strpos($methodName, 'has') === 0) {
169-
// getters and hassers
170-
$attributeName = lcfirst(substr($methodName, 3));
171-
} elseif (strpos($methodName, 'is') === 0) {
172-
// issers
173-
$attributeName = lcfirst(substr($methodName, 2));
174-
}
161+
if ($reflMethod->isConstructor() || $reflMethod->isDestructor()) {
162+
continue;
163+
}
175164

176-
if (isset($attributeName)) {
177-
$attributes[$attributeName] = [
178-
'readable' => true,
179-
'writable' => true,
180-
'description' => (new DocBlock($reflMethod))->getShortDescription(),
181-
];
165+
$methodName = $reflMethod->getName();
166+
if (null === ($attributeName = $this->guessAttributeName($methodName))) {
167+
continue;
168+
}
182169

183-
$this->populateAttribute($attributeName, $attributes[$attributeName], $validationGroups);
184-
unset($attributeName);
185-
}
170+
if (!isset($attributes[$attributeName])) {
171+
$attributes[$attributeName] = [
172+
'readable' => false,
173+
'writable' => false,
174+
'description' => (new DocBlock($reflMethod))->getShortDescription(),
175+
];
176+
}
177+
178+
if (0 === $reflMethod->getNumberOfRequiredParameters()) {
179+
$attributes[$attributeName]['readable'] = true;
180+
} else {
181+
$attributes[$attributeName]['writable'] = true;
186182
}
187183
}
188184

@@ -195,10 +191,13 @@ public function getAttributes(
195191
'writable' => true,
196192
'description' => (new DocBlock($reflProperty))->getShortDescription(),
197193
];
198-
199-
$this->populateAttribute($attributeName, $attributes[$attributeName], $validationGroups);
200194
}
201195
}
196+
197+
// populate attributes
198+
foreach ($attributes as $attributeName => $attribute) {
199+
$this->populateAttribute($attributeName, $attributes[$attributeName], $validationGroups);
200+
}
202201
}
203202

204203
return $this->attributes[$key] = $attributes;
@@ -226,6 +225,25 @@ public function getReflectionClass()
226225
return $this->reflClass;
227226
}
228227

228+
/**
229+
* Guess an attribute name by its method name.
230+
*
231+
* @param $methodName
232+
* @return null|string
233+
*/
234+
private function guessAttributeName($methodName)
235+
{
236+
if (strpos($methodName, 'get') === 0 || strpos($methodName, 'has') === 0 || strpos($methodName, 'set') === 0) {
237+
// getters, setters and hassers
238+
return lcfirst(substr($methodName, 3));
239+
} elseif (strpos($methodName, 'is') === 0) {
240+
// issers
241+
return lcfirst(substr($methodName, 2));
242+
}
243+
244+
return null;
245+
}
246+
229247
/**
230248
* Returns a DocBlock instance for this class.
231249
*

Tests/Behat/TestBundle/Entity/Dummy.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,24 @@ class Dummy
2828
* @ORM\Id
2929
* @ORM\GeneratedValue(strategy="AUTO")
3030
*/
31-
public $id;
31+
private $id;
3232

3333
/**
3434
* @ORM\Column
3535
* @Assert\NotBlank
3636
*/
3737
private $name;
3838

39+
/**
40+
* @ORM\Column(nullable=true)
41+
*/
42+
public $dummy;
43+
44+
public function getId()
45+
{
46+
return $this->id;
47+
}
48+
3949
public function setName($name)
4050
{
4151
$this->name = $name;

features/collection.feature

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ Feature: Collections support
2626
{
2727
"@id": "/dummies/1",
2828
"@type": "Dummy",
29-
"name": "Dummy #1"
29+
"name": "Dummy #1",
30+
"dummy": null
3031
},
3132
{
3233
"@id": "/dummies/2",
3334
"@type": "Dummy",
34-
"name": "Dummy #2"
35+
"name": "Dummy #2",
36+
"dummy": null
3537
},
3638
{
3739
"@id": "/dummies/3",
3840
"@type": "Dummy",
39-
"name": "Dummy #3"
41+
"name": "Dummy #3",
42+
"dummy": null
4043
}
4144
]
4245
}
@@ -66,17 +69,20 @@ Feature: Collections support
6669
{
6770
"@id": "/dummies/19",
6871
"@type": "Dummy",
69-
"name": "Dummy #19"
72+
"name": "Dummy #19",
73+
"dummy": null
7074
},
7175
{
7276
"@id": "/dummies/20",
7377
"@type": "Dummy",
74-
"name": "Dummy #20"
78+
"name": "Dummy #20",
79+
"dummy": null
7580
},
7681
{
7782
"@id": "/dummies/21",
7883
"@type": "Dummy",
79-
"name": "Dummy #21"
84+
"name": "Dummy #21",
85+
"dummy": null
8086
}
8187
]
8288
}
@@ -105,17 +111,20 @@ Feature: Collections support
105111
{
106112
"@id": "/dummies/28",
107113
"@type": "Dummy",
108-
"name": "Dummy #28"
114+
"name": "Dummy #28",
115+
"dummy": null
109116
},
110117
{
111118
"@id": "/dummies/29",
112119
"@type": "Dummy",
113-
"name": "Dummy #29"
120+
"name": "Dummy #29",
121+
"dummy": null
114122
},
115123
{
116124
"@id": "/dummies/30",
117125
"@type": "Dummy",
118-
"name": "Dummy #30"
126+
"name": "Dummy #30",
127+
"dummy": null
119128
}
120129
]
121130
}

features/crud.feature

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ Feature: Create-Retrieve-Update-Delete
2020
"@context": "/contexts/Dummy",
2121
"@id": "/dummies/1",
2222
"@type": "Dummy",
23-
"name": "My Dummy"
23+
"name": "My Dummy",
24+
"dummy": null
2425
}
2526
"""
2627

@@ -35,7 +36,8 @@ Feature: Create-Retrieve-Update-Delete
3536
"@context": "/contexts/Dummy",
3637
"@id": "/dummies/1",
3738
"@type": "Dummy",
38-
"name": "My Dummy"
39+
"name": "My Dummy",
40+
"dummy": null
3941
}
4042
"""
4143

@@ -58,7 +60,8 @@ Feature: Create-Retrieve-Update-Delete
5860
{
5961
"@id":"/dummies/1",
6062
"@type":"Dummy",
61-
"name":"My Dummy"
63+
"name":"My Dummy",
64+
"dummy": null
6265
}
6366
]
6467
}
@@ -81,7 +84,8 @@ Feature: Create-Retrieve-Update-Delete
8184
"@context": "/contexts/Dummy",
8285
"@id": "/dummies/1",
8386
"@type": "Dummy",
84-
"name": "A nice dummy"
87+
"name": "A nice dummy",
88+
"dummy": null
8589
}
8690
"""
8791

features/vocab.feature

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
Feature: Documentation support
2+
In order to build an auto-discoverable API
3+
As a client software developer
4+
I need to know the specifications of objects I send and receive
5+
6+
@createSchema
7+
@dropSchema
8+
Scenario: Retrieve the first page of a collection
9+
And I send a "GET" request to "/vocab"
10+
Then the response status code should be 200
11+
And the response should be in JSON
12+
And the header "Content-Type" should be equal to "application/ld+json"
13+
And the JSON should be equal to:
14+
"""
15+
{
16+
"@context": "\/contexts\/ApiDocumentation",
17+
"@id": "\/vocab",
18+
"hydra:title": "My Dummy API",
19+
"hydra:description": "This is a test API.",
20+
"hydra:entrypoint": "\/",
21+
"hydra:supportedClass": [
22+
{
23+
"@id": "Entrypoint",
24+
"@type": "hydra:class",
25+
"hydra:title": "The API entrypoint",
26+
"hydra:supportedProperty": [
27+
{
28+
"@type": "hydra:SupportedProperty",
29+
"hydra:property": "dummies",
30+
"hydra:title": "The collection of Dummy resources",
31+
"hydra:readable": true,
32+
"hydra:writable": false,
33+
"hydra:supportedOperation": [
34+
{
35+
"@type": "hydra:Operation",
36+
"hydra:title": "Retrieves the collection of Dummy resources.",
37+
"hydra:returns": "hydra:PagedCollection",
38+
"hydra:method": "GET"
39+
},
40+
{
41+
"@type": "hydra:CreateResourceOperation",
42+
"hydra:title": "Creates a Dummy resource.",
43+
"hydra:expects": "Dummy",
44+
"hydra:returns": "Dummy",
45+
"hydra:method": "POST"
46+
}
47+
]
48+
}
49+
]
50+
},
51+
{
52+
"@id": "Dummy",
53+
"@type": "hydra:Class",
54+
"hydra:title": "Dummy",
55+
"hydra:description": "Dummy.",
56+
"hydra:supportedProperty": [
57+
{
58+
"@type": "hydra:SupportedProperty",
59+
"hydra:property": "Dummy\/id",
60+
"hydra:title": "id",
61+
"hydra:required": false,
62+
"hydra:readable": true,
63+
"hydra:writable": false
64+
},
65+
{
66+
"@type": "hydra:SupportedProperty",
67+
"hydra:property": "Dummy\/name",
68+
"hydra:title": "name",
69+
"hydra:required": true,
70+
"hydra:readable": true,
71+
"hydra:writable": true
72+
},
73+
{
74+
"@type": "hydra:SupportedProperty",
75+
"hydra:property": "Dummy\/dummy",
76+
"hydra:title": "dummy",
77+
"hydra:required": false,
78+
"hydra:readable": true,
79+
"hydra:writable": true
80+
}
81+
],
82+
"hydra:supportedOperation": [
83+
{
84+
"@type": "hydra:Operation",
85+
"hydra:title": "Retrieves Dummy resource.",
86+
"hydra:returns": "Dummy",
87+
"hydra:method": "GET"
88+
},
89+
{
90+
"@type": "hydra:ReplaceResourceOperation",
91+
"hydra:title": "Replaces the Dummy resource.",
92+
"hydra:expects": "Dummy",
93+
"hydra:returns": "Dummy",
94+
"hydra:method": "PUT"
95+
},
96+
{
97+
"@type": "hydra:Operation",
98+
"hydra:title": "Deletes the Dummy resource.",
99+
"hydra:expects": "Dummy",
100+
"hydra:method": "DELETE"
101+
}
102+
]
103+
}
104+
]
105+
}
106+
"""

0 commit comments

Comments
 (0)