Skip to content

Commit 1d8b181

Browse files
Copilotanderly
andauthored
Add comprehensive OData Batch operations support with examples and documentation (#186)
* Initial plan * Implement comprehensive OData Batch functionality with examples and documentation Co-authored-by: anderly <[email protected]> * Move guzzlehttp/guzzle back to require-dev section Co-authored-by: anderly <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: anderly <[email protected]>
1 parent 42d91f9 commit 1d8b181

File tree

6 files changed

+661
-0
lines changed

6 files changed

+661
-0
lines changed

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,91 @@ $activePeopleWithTrips = $client->from('People')
300300

301301
For comprehensive examples and advanced usage patterns, see [`examples/lambda_operators.php`](examples/lambda_operators.php).
302302

303+
### Batch Operations
304+
305+
The OData Client supports batch operations, which allow you to send multiple HTTP requests in a single batch request. This can significantly improve performance when you need to perform multiple operations.
306+
307+
#### Basic Batch Usage
308+
309+
```php
310+
<?php
311+
312+
use SaintSystems\OData\ODataClient;
313+
use SaintSystems\OData\GuzzleHttpProvider;
314+
315+
$httpProvider = new GuzzleHttpProvider();
316+
$client = new ODataClient('https://services.odata.org/V4/TripPinService', null, $httpProvider);
317+
318+
// Simple batch with multiple GET requests
319+
$response = $client->batch()
320+
->get('People', 'get-people')
321+
->get('Airlines', 'get-airlines')
322+
->get('Airports', 'get-airports')
323+
->execute();
324+
```
325+
326+
#### Changesets for Atomic Operations
327+
328+
Changesets ensure that all operations within the changeset are executed atomically (all succeed or all fail):
329+
330+
```php
331+
// Batch with changeset for atomic operations
332+
$response = $client->batch()
333+
->startChangeset()
334+
->post('People', [
335+
'FirstName' => 'John',
336+
'LastName' => 'Doe',
337+
'UserName' => 'johndoe',
338+
'Emails' => ['[email protected]']
339+
], 'create-person')
340+
->patch('People(\'russellwhyte\')', [
341+
'FirstName' => 'Jane',
342+
'LastName' => 'Smith'
343+
], 'update-person')
344+
->endChangeset()
345+
->execute();
346+
```
347+
348+
#### Mixed Batch Operations
349+
350+
You can combine individual requests and changesets in a single batch:
351+
352+
```php
353+
$response = $client->batch()
354+
// Individual queries (not in changeset)
355+
->get('People?$top=5', 'get-top-people')
356+
357+
// Atomic operations in changeset
358+
->startChangeset()
359+
->post('People', $newPersonData, 'create-person')
360+
->delete('People(\'obsolete-id\')', 'delete-person')
361+
->endChangeset()
362+
363+
// More individual queries
364+
->get('Airlines?$top=3', 'get-airlines')
365+
->execute();
366+
```
367+
368+
#### Available Batch Methods
369+
370+
- `get($uri, $id)` - Add a GET request to the batch
371+
- `post($uri, $data, $id)` - Add a POST request to the batch
372+
- `put($uri, $data, $id)` - Add a PUT request to the batch
373+
- `patch($uri, $data, $id)` - Add a PATCH request to the batch
374+
- `delete($uri, $id)` - Add a DELETE request to the batch
375+
- `startChangeset()` - Begin a new changeset for atomic operations
376+
- `endChangeset()` - End the current changeset
377+
- `execute()` - Execute the batch request
378+
379+
**Key Features:**
380+
- **Performance**: Reduces network overhead by combining multiple requests
381+
- **Atomic transactions**: Changesets ensure all-or-nothing execution
382+
- **Content-ID references**: Use request IDs to reference results between operations
383+
- **Error handling**: Individual operation errors are reported separately
384+
- **Fluent interface**: Chain operations for clean, readable code
385+
386+
For comprehensive examples, see [`examples/batch_operations.php`](examples/batch_operations.php).
387+
303388
## Develop
304389

305390
### Run Tests

examples/batch_operations.php

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
/**
4+
* OData Batch Operations Examples
5+
*
6+
* This example demonstrates how to use batch operations with the OData Client for PHP.
7+
* Batch operations allow you to send multiple requests in a single HTTP request,
8+
* which can significantly improve performance when making multiple OData operations.
9+
*/
10+
11+
require_once '../vendor/autoload.php';
12+
13+
use SaintSystems\OData\ODataClient;
14+
use SaintSystems\OData\GuzzleHttpProvider;
15+
16+
// Initialize the OData client
17+
$httpProvider = new GuzzleHttpProvider();
18+
$client = new ODataClient('https://services.odata.org/V4/TripPinService', null, $httpProvider);
19+
20+
echo "=== OData Batch Operations Examples ===\n\n";
21+
22+
// Example 1: Simple Batch with Multiple GET Requests
23+
echo "1. Simple Batch with Multiple GET Requests\n";
24+
echo "--------------------------------------------\n";
25+
26+
try {
27+
$response = $client->batch()
28+
->get('People', 'get-people')
29+
->get('Airlines', 'get-airlines')
30+
->get('Airports', 'get-airports')
31+
->execute();
32+
33+
echo "✓ Batch with multiple GET requests executed successfully\n";
34+
echo "Response contains multiple parts for different entity sets\n\n";
35+
} catch (Exception $e) {
36+
echo "✗ Error in simple batch: " . $e->getMessage() . "\n\n";
37+
}
38+
39+
// Example 2: Batch with Changeset (Atomic Operations)
40+
echo "2. Batch with Changeset for Atomic Operations\n";
41+
echo "----------------------------------------------\n";
42+
43+
try {
44+
$newPerson = [
45+
'FirstName' => 'John',
46+
'LastName' => 'Doe',
47+
'UserName' => 'johndoe' . time(),
48+
'Emails' => ['[email protected]']
49+
];
50+
51+
$updatedPerson = [
52+
'FirstName' => 'Jane',
53+
'LastName' => 'Smith'
54+
];
55+
56+
$response = $client->batch()
57+
->startChangeset()
58+
->post('People', $newPerson, 'create-person')
59+
->patch('People(\'russellwhyte\')', $updatedPerson, 'update-person')
60+
->endChangeset()
61+
->execute();
62+
63+
echo "✓ Batch with changeset executed successfully\n";
64+
echo "All operations in changeset will be committed atomically\n\n";
65+
} catch (Exception $e) {
66+
echo "✗ Error in changeset batch: " . $e->getMessage() . "\n\n";
67+
}
68+
69+
// Example 3: Mixed Batch - Queries and Changesets Combined
70+
echo "3. Mixed Batch - Queries and Changesets Combined\n";
71+
echo "------------------------------------------------\n";
72+
73+
try {
74+
$response = $client->batch()
75+
// First, get some data (not in changeset)
76+
->get('People?$top=5', 'get-top-people')
77+
78+
// Then perform atomic operations
79+
->startChangeset()
80+
->post('People', [
81+
'FirstName' => 'Alice',
82+
'LastName' => 'Johnson',
83+
'UserName' => 'alicejohnson' . time(),
84+
'Emails' => ['[email protected]']
85+
], 'create-alice')
86+
->post('People', [
87+
'FirstName' => 'Bob',
88+
'LastName' => 'Wilson',
89+
'UserName' => 'bobwilson' . time(),
90+
'Emails' => ['[email protected]']
91+
], 'create-bob')
92+
->endChangeset()
93+
94+
// More queries outside changeset
95+
->get('Airlines?$top=3', 'get-top-airlines')
96+
->execute();
97+
98+
echo "✓ Mixed batch with queries and changesets executed successfully\n";
99+
echo "Queries executed independently, changesets executed atomically\n\n";
100+
} catch (Exception $e) {
101+
echo "✗ Error in mixed batch: " . $e->getMessage() . "\n\n";
102+
}
103+
104+
// Example 4: Batch with Different HTTP Methods
105+
echo "4. Batch with Different HTTP Methods\n";
106+
echo "------------------------------------\n";
107+
108+
try {
109+
$response = $client->batch()
110+
->get('People?$filter=FirstName eq \'Russell\'', 'find-russell')
111+
->startChangeset()
112+
->put('People(\'russellwhyte\')', [
113+
'FirstName' => 'Russell',
114+
'LastName' => 'Whyte',
115+
'UserName' => 'russellwhyte',
116+
'Emails' => ['[email protected]'],
117+
'AddressInfo' => []
118+
], 'replace-russell')
119+
->delete('People(\'vincentcalabrese\')', 'delete-vincent')
120+
->endChangeset()
121+
->execute();
122+
123+
echo "✓ Batch with mixed HTTP methods executed successfully\n";
124+
echo "Demonstrated GET, PUT, and DELETE operations\n\n";
125+
} catch (Exception $e) {
126+
echo "✗ Error in mixed methods batch: " . $e->getMessage() . "\n\n";
127+
}
128+
129+
// Example 5: Error Handling in Batch Operations
130+
echo "5. Error Handling in Batch Operations\n";
131+
echo "--------------------------------------\n";
132+
133+
try {
134+
// This batch intentionally includes operations that might fail
135+
$response = $client->batch()
136+
->get('People', 'valid-request')
137+
->get('NonExistentEntitySet', 'invalid-request') // This will likely fail
138+
->startChangeset()
139+
->post('People', [
140+
'FirstName' => 'Test',
141+
'UserName' => 'test' . time()
142+
// Missing required fields - might cause validation errors
143+
], 'potentially-failing-create')
144+
->endChangeset()
145+
->execute();
146+
147+
echo "✓ Batch with potential errors handled gracefully\n";
148+
echo "Check response for individual operation status codes\n\n";
149+
} catch (Exception $e) {
150+
echo "✗ Expected error in batch with invalid operations: " . $e->getMessage() . "\n";
151+
echo "This demonstrates how batch errors are handled\n\n";
152+
}
153+
154+
// Example 6: Advanced Batch with Content-ID References
155+
echo "6. Advanced Batch with Content-ID References\n";
156+
echo "---------------------------------------------\n";
157+
158+
try {
159+
$response = $client->batch()
160+
->startChangeset()
161+
->post('People', [
162+
'FirstName' => 'Reference',
163+
'LastName' => 'Example',
164+
'UserName' => 'refexample' . time(),
165+
'Emails' => ['[email protected]']
166+
], 'new-person')
167+
// Note: In a real scenario, you might reference the created person
168+
// in subsequent operations using the Content-ID
169+
->endChangeset()
170+
->get('People?$orderby=FirstName desc&$top=1', 'get-latest-person')
171+
->execute();
172+
173+
echo "✓ Advanced batch with Content-ID references executed\n";
174+
echo "Content-IDs allow referencing results from one operation in another\n\n";
175+
} catch (Exception $e) {
176+
echo "✗ Error in advanced batch: " . $e->getMessage() . "\n\n";
177+
}
178+
179+
echo "=== Batch Operations Examples Complete ===\n\n";
180+
181+
echo "Key Concepts Demonstrated:\n";
182+
echo "- Simple batching of multiple GET requests\n";
183+
echo "- Changesets for atomic operations (all succeed or all fail)\n";
184+
echo "- Mixing queries and changesets in a single batch\n";
185+
echo "- Using different HTTP methods (GET, POST, PUT, PATCH, DELETE)\n";
186+
echo "- Error handling in batch operations\n";
187+
echo "- Content-ID references for operation dependencies\n\n";
188+
189+
echo "Benefits of Batch Operations:\n";
190+
echo "- Reduced network overhead (single HTTP request)\n";
191+
echo "- Atomic transactions for related operations\n";
192+
echo "- Better performance for multiple operations\n";
193+
echo "- Simplified error handling for grouped operations\n\n";
194+
195+
echo "Best Practices:\n";
196+
echo "- Use changesets for operations that should be atomic\n";
197+
echo "- Keep batch sizes reasonable (typically 10-100 operations)\n";
198+
echo "- Use Content-IDs for operations that depend on each other\n";
199+
echo "- Handle both individual operation errors and batch-level errors\n";

0 commit comments

Comments
 (0)