1717use ApiPlatform \Metadata \ApiResource ;
1818use ApiPlatform \Metadata \CollectionOperationInterface ;
1919use ApiPlatform \Metadata \HttpOperation ;
20+ use ApiPlatform \Metadata \HydraOperation ;
21+ use ApiPlatform \Metadata \Resource \Factory \ResourceMetadataCollectionFactoryInterface ;
22+ use ApiPlatform \Metadata \ResourceAccessCheckerInterface ;
2023
2124/**
2225 * Generates Hydra operations for JSON-LD responses.
2326 *
2427 * @author Kévin Dunglas <dunglas@gmail.com>
28+ *
29+ * @property ResourceMetadataCollectionFactoryInterface|null $resourceMetadataCollectionFactory
30+ * @property ResourceAccessCheckerInterface|null $resourceAccessChecker
2531 */
2632trait HydraOperationsTrait
2733{
2834 /**
29- * Gets Hydra operations from all resource metadata .
35+ * Gets Hydra operations from all HydraOperation attributes .
3036 */
31- private function getHydraOperationsFromResourceMetadatas (string $ resourceClass , bool $ collection , string $ hydraPrefix = ContextBuilder::HYDRA_PREFIX ): array
37+ private function getHydraOperationsFromAttributes (string $ resourceClass , bool $ collection, ? object $ object , array $ context , string $ hydraPrefix = ContextBuilder::HYDRA_PREFIX ): array
3238 {
3339 $ allHydraOperations = [];
3440 $ operationNames = [];
3541
3642 foreach ($ this ->resourceMetadataCollectionFactory ->create ($ resourceClass ) as $ resourceMetadata ) {
37- $ hydraOperations = $ this ->getHydraOperationsFromResourceMetadata (
43+ $ hydraOperations = $ this ->getHydraOperationsFromAttributesForResource (
3844 $ collection ,
3945 $ resourceMetadata ,
4046 $ hydraPrefix ,
47+ $ resourceClass ,
48+ $ object ,
49+ $ context ,
4150 $ operationNames
4251 );
4352
@@ -50,35 +59,117 @@ private function getHydraOperationsFromResourceMetadatas(string $resourceClass,
5059 /**
5160 * Gets Hydra operations from a single resource metadata.
5261 */
53- private function getHydraOperationsFromResourceMetadata (bool $ collection , ApiResource $ resourceMetadata , string $ hydraPrefix , array &$ operationNames ): array
62+ private function getHydraOperationsFromAttributesForResource (bool $ collection , ApiResource $ resourceMetadata , string $ hydraPrefix, string $ resourceClass , ? object $ object , array $ context , array &$ operationNames ): array
5463 {
5564 $ operations = [];
56- $ hydraOperations = $ this -> getHydraOperations (
57- $ collection ,
58- $ resourceMetadata ,
59- $ hydraPrefix
60- );
61-
62- if (! empty ( $ hydraOperations )) {
63- foreach ( $ hydraOperations as $ operation ) {
64- $ operationName = $ operation [ $ hydraPrefix . ' method ' ] ;
65- if (! \in_array ( $ operationName , $ operationNames , true )) {
66- $ operationNames [] = $ operationName ;
67- $ operations [] = $ operation ;
68- }
65+
66+ foreach ( $ resourceMetadata -> getHydraOperations () ?? [] as $ hydraOperation ) {
67+ if ( $ hydraOperation -> getCollection () !== $ collection ) {
68+ continue ;
69+ }
70+
71+ $ method = $ hydraOperation -> getMethod ();
72+ if ( \in_array ( $ method , $ operationNames , true ) ) {
73+ continue ;
74+ }
75+
76+ if (! $ this -> isHydraOperationGranted ( $ hydraOperation , $ resourceClass , $ object , $ context )) {
77+ continue ;
6978 }
79+
80+ $ operationNames [] = $ method ;
81+ $ operations [] = $ this ->normalizeHydraOperationAttribute ($ hydraOperation , $ resourceMetadata ->getShortName (), $ hydraPrefix );
7082 }
7183
7284 return $ operations ;
7385 }
7486
87+ private function isHydraOperationGranted (HydraOperation $ hydraOperation , string $ resourceClass , ?object $ object , array $ context ): bool
88+ {
89+ if (null === $ expression = $ hydraOperation ->getSecurity ()) {
90+ return true ;
91+ }
92+
93+ if (null === $ this ->resourceAccessChecker ) {
94+ return false ;
95+ }
96+
97+ $ extraVariables = ['object ' => $ object ];
98+ if (isset ($ context ['request ' ])) {
99+ $ extraVariables ['request ' ] = $ context ['request ' ];
100+ }
101+
102+ return $ this ->resourceAccessChecker ->isGranted ($ resourceClass , $ expression , $ extraVariables );
103+ }
104+
105+ /**
106+ * Normalizes a HydraOperation attribute into a JSON-LD array.
107+ */
108+ private function normalizeHydraOperationAttribute (HydraOperation $ hydraOperation , ?string $ shortName , string $ hydraPrefix = ContextBuilder::HYDRA_PREFIX ): array
109+ {
110+ $ method = $ hydraOperation ->getMethod ();
111+ $ output = $ hydraOperation ->getExtraProperties ();
112+
113+ $ output ['@type ' ] = $ hydraOperation ->getTypes () ?? $ this ->defaultHydraOperationTypes ($ method , $ hydraPrefix );
114+
115+ if (null !== ($ description = $ hydraOperation ->getDescription ())) {
116+ $ output [$ hydraPrefix .'description ' ] = $ description ;
117+ }
118+
119+ if (null !== ($ expects = $ hydraOperation ->getExpects ())) {
120+ $ output ['expects ' ] = $ expects ;
121+ } elseif (\in_array ($ method , ['POST ' , 'PUT ' , 'PATCH ' ], true ) && null !== $ shortName ) {
122+ $ output ['expects ' ] = $ shortName ;
123+ }
124+
125+ if (null !== ($ returns = $ hydraOperation ->getReturns ())) {
126+ $ output ['returns ' ] = $ returns ;
127+ } elseif ('DELETE ' === $ method ) {
128+ $ output ['returns ' ] = 'owl:Nothing ' ;
129+ } elseif (null !== $ shortName ) {
130+ $ output ['returns ' ] = $ shortName ;
131+ }
132+
133+ $ output [$ hydraPrefix .'method ' ] = $ method ;
134+ $ output [$ hydraPrefix .'title ' ] = $ hydraOperation ->getTitle ()
135+ ?? $ this ->defaultHydraOperationTitle ($ method , $ shortName , $ hydraOperation ->getCollection () && 'GET ' === $ method );
136+
137+ if (null === $ output [$ hydraPrefix .'title ' ]) {
138+ unset($ output [$ hydraPrefix .'title ' ]);
139+ }
140+
141+ ksort ($ output );
142+
143+ return $ output ;
144+ }
145+
146+ private function defaultHydraOperationTypes (string $ method , string $ hydraPrefix ): array |string
147+ {
148+ return match ($ method ) {
149+ 'GET ' => [$ hydraPrefix .'Operation ' , 'schema:FindAction ' ],
150+ 'POST ' => [$ hydraPrefix .'Operation ' , 'schema:CreateAction ' ],
151+ 'PUT ' => [$ hydraPrefix .'Operation ' , 'schema:ReplaceAction ' ],
152+ 'DELETE ' => [$ hydraPrefix .'Operation ' , 'schema:DeleteAction ' ],
153+ default => $ hydraPrefix .'Operation ' ,
154+ };
155+ }
156+
157+ private function defaultHydraOperationTitle (string $ method , ?string $ shortName , bool $ isCollection ): ?string
158+ {
159+ if (null === $ shortName ) {
160+ return null ;
161+ }
162+
163+ return strtolower ($ method ).$ shortName .($ isCollection ? 'Collection ' : '' );
164+ }
165+
75166 /**
76167 * Gets Hydra operations.
77168 */
78169 private function getHydraOperations (bool $ collection , ApiResource $ resourceMetadata , string $ hydraPrefix = ContextBuilder::HYDRA_PREFIX ): array
79170 {
80171 $ hydraOperations = [];
81- foreach ($ resourceMetadata ->getOperations () as $ operation ) {
172+ foreach ($ resourceMetadata ->getOperations () ?? [] as $ operation ) {
82173 if (true === $ operation ->getHideHydraOperation ()) {
83174 continue ;
84175 }
@@ -112,21 +203,22 @@ private function getHydraOperation(HttpOperation $operation, string $prefixedSho
112203 $ inputClass = \array_key_exists ('class ' , $ inputMetadata ) ? $ inputMetadata ['class ' ] : false ;
113204 $ outputClass = \array_key_exists ('class ' , $ outputMetadata ) ? $ outputMetadata ['class ' ] : false ;
114205
115- if ('GET ' === $ method && $ operation instanceof CollectionOperationInterface) {
206+ $ isCollection = $ operation instanceof CollectionOperationInterface;
207+
208+ $ hydraOperation += ['@type ' => 'PATCH ' === $ method ? $ hydraPrefix .'Operation ' : $ this ->defaultHydraOperationTypes ($ method , $ hydraPrefix )];
209+
210+ if ('GET ' === $ method && $ isCollection ) {
116211 $ hydraOperation += [
117- '@type ' => [$ hydraPrefix .'Operation ' , 'schema:FindAction ' ],
118212 $ hydraPrefix .'description ' => "Retrieves the collection of $ shortName resources. " ,
119213 'returns ' => null === $ outputClass ? 'owl:Nothing ' : $ hydraPrefix .'Collection ' ,
120214 ];
121215 } elseif ('GET ' === $ method ) {
122216 $ hydraOperation += [
123- '@type ' => [$ hydraPrefix .'Operation ' , 'schema:FindAction ' ],
124217 $ hydraPrefix .'description ' => "Retrieves a $ shortName resource. " ,
125218 'returns ' => null === $ outputClass ? 'owl:Nothing ' : $ prefixedShortName ,
126219 ];
127220 } elseif ('PATCH ' === $ method ) {
128221 $ hydraOperation += [
129- '@type ' => $ hydraPrefix .'Operation ' ,
130222 $ hydraPrefix .'description ' => "Updates the $ shortName resource. " ,
131223 'returns ' => null === $ outputClass ? 'owl:Nothing ' : $ prefixedShortName ,
132224 'expects ' => null === $ inputClass ? 'owl:Nothing ' : $ prefixedShortName ,
@@ -144,28 +236,25 @@ private function getHydraOperation(HttpOperation $operation, string $prefixedSho
144236 }
145237 } elseif ('POST ' === $ method ) {
146238 $ hydraOperation += [
147- '@type ' => [$ hydraPrefix .'Operation ' , 'schema:CreateAction ' ],
148239 $ hydraPrefix .'description ' => "Creates a $ shortName resource. " ,
149240 'returns ' => null === $ outputClass ? 'owl:Nothing ' : $ prefixedShortName ,
150241 'expects ' => null === $ inputClass ? 'owl:Nothing ' : $ prefixedShortName ,
151242 ];
152243 } elseif ('PUT ' === $ method ) {
153244 $ hydraOperation += [
154- '@type ' => [$ hydraPrefix .'Operation ' , 'schema:ReplaceAction ' ],
155245 $ hydraPrefix .'description ' => "Replaces the $ shortName resource. " ,
156246 'returns ' => null === $ outputClass ? 'owl:Nothing ' : $ prefixedShortName ,
157247 'expects ' => null === $ inputClass ? 'owl:Nothing ' : $ prefixedShortName ,
158248 ];
159249 } elseif ('DELETE ' === $ method ) {
160250 $ hydraOperation += [
161- '@type ' => [$ hydraPrefix .'Operation ' , 'schema:DeleteAction ' ],
162251 $ hydraPrefix .'description ' => "Deletes the $ shortName resource. " ,
163252 'returns ' => 'owl:Nothing ' ,
164253 ];
165254 }
166255
167256 $ hydraOperation [$ hydraPrefix .'method ' ] ??= $ method ;
168- $ hydraOperation [$ hydraPrefix .'title ' ] ??= strtolower ($ method). $ shortName.( $ operation instanceof CollectionOperationInterface ? ' Collection ' : '' );
257+ $ hydraOperation [$ hydraPrefix .'title ' ] ??= $ this -> defaultHydraOperationTitle ($ method, $ shortName, $ isCollection );
169258
170259 ksort ($ hydraOperation );
171260
0 commit comments