|
2 | 2 |
|
3 | 3 | namespace INTERMediator\FileMakerServer\RESTAPI; |
4 | 4 |
|
| 5 | +use INTERMediator\FileMakerServer\RESTAPI\SessionCache\SessionCacheInterface; |
5 | 6 | use INTERMediator\FileMakerServer\RESTAPI\Supporting\FileMakerLayout; |
6 | 7 | use INTERMediator\FileMakerServer\RESTAPI\Supporting\FileMakerRelation; |
7 | 8 | use INTERMediator\FileMakerServer\RESTAPI\Supporting\CommunicationProvider; |
@@ -58,23 +59,33 @@ class FMDataAPI |
58 | 59 | * Ex. [{"database"=>"<databaseName>", "username"=>"<username>", "password"=>"<password>"}]. |
59 | 60 | * If you use OAuth, "oAuthRequestId" and "oAuthIdentifier" keys have to be specified. |
60 | 61 | * @param boolean $isUnitTest If it's set to true, the communication provider just works locally. |
61 | | - */ |
62 | | - public function __construct(string $solution, |
63 | | - string $user, |
64 | | - string|null $password, |
65 | | - string|null $host = null, |
66 | | - int|null $port = null, |
67 | | - string|null $protocol = null, |
68 | | - array|null $fmDataSource = null, |
69 | | - bool $isUnitTest = false) |
| 62 | + * @param SessionCacheInterface|null $sessionCache Cache backend for persistent sessions. |
| 63 | + * If omitted, the library logs in and out on every database operation, or once |
| 64 | + * per communication scope when using startCommunication() / endCommunication(). |
| 65 | + * If specified, session tokens are persisted and reused across requests via |
| 66 | + * startCommunication() / endCommunication(), avoiding redundant logins against the FileMaker Server. |
| 67 | + * When a session cache is specified, {@see self::setRetryOnAccessTokenInvalidation()} is |
| 68 | + * automatically set to true, ensuring the library re-authenticates and retries the request if |
| 69 | + * the cached token has expired on the FileMaker Server. |
| 70 | + */ |
| 71 | + public function __construct(string $solution, |
| 72 | + string $user, |
| 73 | + string|null $password, |
| 74 | + string|null $host = null, |
| 75 | + int|null $port = null, |
| 76 | + string|null $protocol = null, |
| 77 | + array|null $fmDataSource = null, |
| 78 | + bool $isUnitTest = false, |
| 79 | + SessionCacheInterface|null $sessionCache = null) |
70 | 80 | { |
71 | 81 | if (is_null($password)) { |
72 | 82 | $password = "password"; // For testing purpose. |
73 | 83 | } |
| 84 | + |
74 | 85 | if (!$isUnitTest) { |
75 | | - $this->provider = new Supporting\CommunicationProvider($solution, $user, $password, $host, $port, $protocol, $fmDataSource); |
| 86 | + $this->provider = new Supporting\CommunicationProvider($solution, $user, $password, $host, $port, $protocol, $fmDataSource, $sessionCache); |
76 | 87 | } else { |
77 | | - $this->provider = new Supporting\TestProvider($solution, $user, $password, $host, $port, $protocol, $fmDataSource); |
| 88 | + $this->provider = new Supporting\TestProvider($solution, $user, $password, $host, $port, $protocol, $fmDataSource, $sessionCache); |
78 | 89 | } |
79 | 90 | } |
80 | 91 |
|
@@ -264,33 +275,42 @@ public function setThrowException(bool $value): void |
264 | 275 | } |
265 | 276 |
|
266 | 277 | /** |
267 | | - * Start a transaction which is a serial calling of multiple database operations before the single authentication. |
268 | | - * Usually most methods login and logout before/after the database operation, and so a little bit of time is going to |
269 | | - * take. |
270 | | - * The startCommunication() login and endCommunication() logout, and methods between them don't log in/out, and |
271 | | - * it can expect faster operations. |
| 278 | + * Start a communication scope with a shared authenticated session. |
| 279 | + * |
| 280 | + * Usually most methods login and logout before and after each database operation. |
| 281 | + * By calling startCommunication() and endCommunication(), methods between them don't |
| 282 | + * log in and out every time, and it can expect faster operations. |
| 283 | + * |
| 284 | + * Without a session cache, one authenticated session is kept for the duration of |
| 285 | + * the current communication scope and discarded when endCommunication() is called. |
| 286 | + * |
| 287 | + * With a session cache, the session token is persisted beyond the current communication |
| 288 | + * scope and reused across requests. If no cached token is available, a new session is |
| 289 | + * created and stored for future reuse. |
| 290 | + * |
272 | 291 | * @throws Exception |
273 | 292 | */ |
274 | 293 | public function startCommunication(): void |
275 | 294 | { |
276 | | - try { |
277 | | - if ($this->provider->login()) { |
278 | | - $this->provider->keepAuth = true; |
279 | | - } |
280 | | - } catch (Exception $e) { |
281 | | - $this->provider->keepAuth = false; |
282 | | - throw $e; |
283 | | - } |
| 295 | + $this->provider->startCommunication(); |
284 | 296 | } |
285 | 297 |
|
286 | 298 | /** |
287 | | - * Finish a transaction which is a serial calling of any database operations, and logout. |
| 299 | + * Finish a communication scope. |
| 300 | + * |
| 301 | + * Without a session cache, the authenticated session for the current communication |
| 302 | + * scope is ended and the server session is logged out. |
| 303 | + * |
| 304 | + * With a session cache, the cached token's TTL is renewed if it still matches the |
| 305 | + * token held by this instance. If another process has replaced the cached token in |
| 306 | + * the meantime, only this instance's now-stale token is logged out, leaving the |
| 307 | + * newer cached token intact. |
| 308 | + * |
288 | 309 | * @throws Exception |
289 | 310 | */ |
290 | 311 | public function endCommunication(): void |
291 | 312 | { |
292 | | - $this->provider->keepAuth = false; |
293 | | - $this->provider->logout(); |
| 313 | + $this->provider->endCommunication(); |
294 | 314 | } |
295 | 315 |
|
296 | 316 | /** |
@@ -425,4 +445,44 @@ public function setExcludeTimeStampInException(bool $value = true): void |
425 | 445 | { |
426 | 446 | $this->provider->excludeTimeStampInException = $value; |
427 | 447 | } |
| 448 | + |
| 449 | + /** |
| 450 | + * Controls whether failed Data API calls are automatically retried after session invalidation. |
| 451 | + * |
| 452 | + * When enabled and a call fails with error 952 (invalid token) or 112 (window missing), the |
| 453 | + * current session is discarded, a new session is established, and the call is retried once. |
| 454 | + * |
| 455 | + * When a session cache is provided to the constructor, retry on token invalidation is always |
| 456 | + * active regardless of this setting. This flag only has an effect when no session cache is |
| 457 | + * configured. |
| 458 | + * |
| 459 | + * Warning: The retry runs in a fresh session. Any session-scoped state from the original session |
| 460 | + * is lost — for example, global fields set before the retry will not carry over. |
| 461 | + * @param bool $value |
| 462 | + */ |
| 463 | + public function setRetryOnAccessTokenInvalidation(bool $value = true): void |
| 464 | + { |
| 465 | + $this->provider->retryOnAccessTokenInvalidation = $value; |
| 466 | + } |
| 467 | + |
| 468 | + /** |
| 469 | + * Overrides the time-to-live (TTL) of the cached FileMaker Data API session token. |
| 470 | + * |
| 471 | + * WARNING: Setting a TTL that exceeds the FileMaker Data API session timeout (15 minutes) |
| 472 | + * will cause the library to use expired tokens, resulting in authentication failures. |
| 473 | + * Do not use this method unless you fully understand the implications. |
| 474 | + * |
| 475 | + * The default TTL is 840 seconds (14 minutes), intentionally set one minute below the |
| 476 | + * FileMaker Data API session timeout of 15 minutes to ensure the cached token is |
| 477 | + * invalidated before it expires on the FileMaker Server. |
| 478 | + * @param int $ttl Time-to-live in seconds. Defaults to 840 seconds (14 minutes). |
| 479 | + * @throws Exception If a session cache is not set, an exception is thrown. |
| 480 | + */ |
| 481 | + public function setSessionCacheTtl(int $ttl = 840): void |
| 482 | + { |
| 483 | + if ($this->provider->sessionCache === null) { |
| 484 | + throw new Exception("setSessionCacheTtl() requires a session cache to be configured via the constructor."); |
| 485 | + } |
| 486 | + $this->provider->sessionCache->setTtl($ttl); |
| 487 | + } |
428 | 488 | } |
0 commit comments