Skip to content

Commit a68069d

Browse files
Copilotanderly
andauthored
Add nested property access support for OData entities (#187)
* Initial plan * Implement nested property access for OData entities - Add support for object-style nested property access (entity->Info->IsAHomeFolder) - Add dot notation property access via getProperty('Info.IsAHomeFolder') - Convert nested associative arrays to Entity objects automatically - Add hasProperty() method for checking property existence with dot notation - Maintain full backward compatibility with existing flat property access - Preserve array access functionality for both nested and flat properties - Support deep nesting (unlimited levels) - Handle both complex objects and arrays appropriately Addresses the ShareFile OData scenario where users need to access nested properties like Info/IsAHomeFolder and work with collection filtering. Co-authored-by: anderly <[email protected]> * Add comprehensive integration tests for nested properties with TripPinService People endpoint Co-authored-by: anderly <[email protected]> * Fix static property typo and method name conflict in Entity class - Fix typo: $snakePropreties -> $snakeProperties - Add missing static property declaration for $snakeProperties with default value false - Rename getNestedProperty() to getNestedPropertyByPath() to avoid conflict with automatic mutator detection for 'nested' property - Resolves 18 test failures caused by undeclared static property error Co-authored-by: anderly <[email protected]> * Add comprehensive nested property access documentation and examples Co-authored-by: anderly <[email protected]> * Move Guzzle back to require-dev dependencies Co-authored-by: anderly <[email protected]> * Restore PHPUnit version specification to ^9.5 || ^10.0 || ^11.0 || ^12.0 Co-authored-by: anderly <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: anderly <[email protected]>
1 parent 1d8b181 commit a68069d

File tree

5 files changed

+943
-1
lines changed

5 files changed

+943
-1
lines changed

README.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,183 @@ This approach allows you to customize request creation without having to overrid
231231

232232
For a complete working example, see [`examples/custom_headers_example.php`](examples/custom_headers_example.php).
233233

234+
### Nested Property Access
235+
236+
The OData Client provides powerful support for accessing nested properties in OData entities, making it easy to work with complex data structures returned by modern OData services.
237+
238+
#### Object-Style Access
239+
240+
Access nested properties naturally using object notation:
241+
242+
```php
243+
<?php
244+
245+
use SaintSystems\OData\ODataClient;
246+
use SaintSystems\OData\GuzzleHttpProvider;
247+
248+
$httpProvider = new GuzzleHttpProvider();
249+
$client = new ODataClient('https://services.odata.org/V4/TripPinService', null, $httpProvider);
250+
251+
// Get a person with address information
252+
$person = $client->from('People')->find('russellwhyte');
253+
254+
// Access nested properties directly
255+
$city = $person->AddressInfo[0]->City; // Object-style access
256+
$country = $person->AddressInfo[0]->CountryRegion; // Deep nesting supported
257+
258+
// Complex nested structures work naturally
259+
if ($person->Settings && $person->Settings->Preferences) {
260+
$theme = $person->Settings->Preferences->Theme;
261+
}
262+
```
263+
264+
#### Dot Notation Access
265+
266+
Use dot notation for safe navigation through nested properties:
267+
268+
```php
269+
// Safe access with dot notation - returns null if any part doesn't exist
270+
$city = $person->getProperty('AddressInfo.0.City');
271+
$country = $person->getProperty('AddressInfo.0.CountryRegion');
272+
$theme = $person->getProperty('Settings.Preferences.Theme');
273+
274+
// Works with array indices and object properties
275+
$firstFriendName = $person->getProperty('Friends.0.FirstName');
276+
$homeAddress = $person->getProperty('AddressInfo.0.Address');
277+
```
278+
279+
#### Property Existence Checking
280+
281+
Check if nested properties exist before accessing them:
282+
283+
```php
284+
// Check existence using hasProperty()
285+
if ($person->hasProperty('AddressInfo.0.City')) {
286+
$city = $person->getProperty('AddressInfo.0.City');
287+
}
288+
289+
// Also works with isset() for object-style access
290+
if (isset($person->AddressInfo[0]->City)) {
291+
$city = $person->AddressInfo[0]->City;
292+
}
293+
294+
// Check for deeply nested paths
295+
if ($person->hasProperty('Settings.Preferences.AutoSave')) {
296+
$autoSave = $person->getProperty('Settings.Preferences.AutoSave');
297+
}
298+
```
299+
300+
#### Working with Collections
301+
302+
Handle arrays and collections within nested structures:
303+
304+
```php
305+
// Get people with address information
306+
$people = $client->select('UserName,FirstName,LastName,AddressInfo')
307+
->from('People')
308+
->get();
309+
310+
foreach ($people as $person) {
311+
echo "Person: " . $person->FirstName . " " . $person->LastName . "\n";
312+
313+
// Access nested address info - remains as array for easy filtering
314+
$addresses = $person->AddressInfo;
315+
316+
// Filter addresses by type
317+
$homeAddresses = array_filter($addresses, function($address) {
318+
return isset($address['Type']) && $address['Type'] === 'Home';
319+
});
320+
321+
// Access properties within filtered results
322+
foreach ($homeAddresses as $address) {
323+
// Convert to Entity for object-style access
324+
$addrEntity = new \SaintSystems\OData\Entity($address);
325+
echo " Home Address: " . $addrEntity->Address . ", " . $addrEntity->City . "\n";
326+
}
327+
}
328+
```
329+
330+
#### Real-World ShareFile OData Example
331+
332+
Working with ShareFile-style OData responses with Info objects and Children collections:
333+
334+
```php
335+
// Query for folders with nested Info and Children data
336+
$folders = $client->select('Id,Name,CreatorNameShort,Info,Info/IsAHomeFolder,Children/Id,Children/Name')
337+
->from('Items')
338+
->where('HasChildren', true)
339+
->get();
340+
341+
foreach ($folders as $folder) {
342+
echo "Folder: " . $folder->Name . "\n";
343+
echo "Creator: " . $folder->CreatorNameShort . "\n";
344+
345+
// Access nested Info properties
346+
if ($folder->Info) {
347+
echo "Is Home Folder: " . ($folder->Info->IsAHomeFolder ? 'Yes' : 'No') . "\n";
348+
349+
// Safe navigation for optional nested properties
350+
if ($folder->hasProperty('Info.Settings.Theme')) {
351+
echo "Theme: " . $folder->getProperty('Info.Settings.Theme') . "\n";
352+
}
353+
}
354+
355+
// Work with Children collection
356+
if ($folder->Children) {
357+
echo "Children:\n";
358+
359+
// Filter children by type
360+
$subfolders = array_filter($folder->Children, function($child) {
361+
return $child['FileSizeBytes'] == 0; // Folders have 0 file size
362+
});
363+
364+
foreach ($subfolders as $subfolder) {
365+
echo " - " . $subfolder['Name'] . " (ID: " . $subfolder['Id'] . ")\n";
366+
}
367+
}
368+
echo "\n";
369+
}
370+
```
371+
372+
#### Integration with Query Building
373+
374+
Nested property access works seamlessly with OData query operations:
375+
376+
```php
377+
// Select specific nested properties
378+
$result = $client->select('Id,Name,Info/IsAHomeFolder,Children/Name,AddressInfo/City')
379+
->from('Items')
380+
->get();
381+
382+
// Use in where clauses (if supported by the OData service)
383+
$homeItems = $client->from('Items')
384+
->where('Info/IsAHomeFolder', true)
385+
->get();
386+
387+
// Expand related data and access nested properties
388+
$peopleWithTrips = $client->from('People')
389+
->expand('Trips')
390+
->get();
391+
392+
foreach ($peopleWithTrips as $person) {
393+
foreach ($person->Trips as $trip) {
394+
// Access nested trip properties
395+
$tripEntity = new \SaintSystems\OData\Entity($trip);
396+
echo $person->FirstName . " has trip: " . $tripEntity->Name . "\n";
397+
}
398+
}
399+
```
400+
401+
**Key Features:**
402+
- **Multiple Access Patterns**: Object notation, dot notation, and array access all supported
403+
- **Automatic Type Conversion**: Nested associative arrays become Entity objects for object-style access
404+
- **Safe Navigation**: Non-existent properties return `null` instead of throwing errors
405+
- **Performance Optimized**: Entity objects created lazily only when accessed
406+
- **Backward Compatible**: All existing code continues to work unchanged
407+
- **Collection Friendly**: Arrays remain as arrays for easy filtering and manipulation
408+
409+
For comprehensive examples and advanced usage patterns, see [`examples/nested_properties_example.php`](examples/nested_properties_example.php).
410+
234411
### Lambda Operators (any/all)
235412

236413
The OData Client supports lambda operators `any` and `all` for filtering collections within entities. These operators allow you to filter based on conditions within related navigation properties.

0 commit comments

Comments
 (0)