99use Lkrms \Support \Catalog \ArrayKeyConformity ;
1010use Lkrms \Support \Catalog \ArrayMapperFlag ;
1111use Lkrms \Support \Iterator \Contract \FluentIteratorInterface ;
12+ use Lkrms \Support \Iterator \IterableIterator ;
1213use Lkrms \Support \Pipeline ;
1314use Lkrms \Sync \Catalog \SyncEntitySource ;
1415use Lkrms \Sync \Catalog \SyncFilterPolicy ;
2021use Lkrms \Sync \Contract \ISyncProvider ;
2122use Lkrms \Sync \Exception \SyncEntityNotFoundException ;
2223use Lkrms \Sync \Exception \SyncFilterPolicyViolationException ;
23- use Lkrms \Sync \Support \SyncIntrospectionClass ;
2424use Lkrms \Sync \Support \SyncIntrospector ;
2525use Closure ;
2626use LogicException ;
@@ -58,16 +58,17 @@ abstract class SyncDefinition extends FluentInterface implements ISyncDefinition
5858 * {@see SyncDefinition::$Operations}.
5959 *
6060 * @param OP::* $operation
61+ * @return (Closure(ISyncContext, mixed...): (iterable<TEntity>|TEntity))|null
6162 * @phpstan-return (
6263 * $operation is OP::READ
6364 * ? (Closure(ISyncContext, int|string|null, mixed...): TEntity)
6465 * : (
6566 * $operation is OP::READ_LIST
66- * ? (Closure(ISyncContext, mixed...): FluentIteratorInterface<array-key, TEntity>)
67+ * ? (Closure(ISyncContext, mixed...): iterable< TEntity>)
6768 * : (
6869 * $operation is OP::CREATE|OP::UPDATE|OP::DELETE
6970 * ? (Closure(ISyncContext, TEntity, mixed...): TEntity)
70- * : (Closure(ISyncContext, FluentIteratorInterface<array-key, TEntity>, mixed...): FluentIteratorInterface<array-key, TEntity>)
71+ * : (Closure(ISyncContext, iterable< TEntity>, mixed...): iterable< TEntity>)
7172 * )
7273 * )
7374 * )|null
@@ -317,7 +318,10 @@ final public function getSyncOperationClosure($operation): ?Closure
317318 ($ closure = $ this ->getSyncOperationClosure (OP ::READ_LIST ))) {
318319 return $ this ->Closures [$ operation ] =
319320 function (ISyncContext $ ctx , $ id , ...$ args ) use ($ closure ) {
320- $ entity = $ closure ($ ctx , ...$ args )->nextWithValue ('Id ' , $ id );
321+ $ entity =
322+ $ this
323+ ->getFluentIterator ($ closure ($ ctx , ...$ args ))
324+ ->nextWithValue ('Id ' , $ id );
321325 if ($ entity === false ) {
322326 throw new SyncEntityNotFoundException ($ this ->Provider , $ this ->Entity , $ id );
323327 }
@@ -341,11 +345,27 @@ function (ISyncContext $ctx, $id, ...$args) use ($closure) {
341345 * Useful within overrides when a fallback implementation is required.
342346 *
343347 * @param OP::* $operation
348+ * @return (Closure(ISyncContext, mixed...): (iterable<TEntity>|TEntity))|null
349+ * @phpstan-return (
350+ * $operation is OP::READ
351+ * ? (Closure(ISyncContext, int|string|null, mixed...): TEntity)
352+ * : (
353+ * $operation is OP::READ_LIST
354+ * ? (Closure(ISyncContext, mixed...): iterable<TEntity>)
355+ * : (
356+ * $operation is OP::CREATE|OP::UPDATE|OP::DELETE
357+ * ? (Closure(ISyncContext, TEntity, mixed...): TEntity)
358+ * : (Closure(ISyncContext, iterable<TEntity>, mixed...): iterable<TEntity>)
359+ * )
360+ * )
361+ * )|null
362+ *
344363 * @see SyncDefinition::$Overrides
345364 */
346- final public function getFallbackSyncOperationClosure ($ operation ): ?Closure
365+ final public function getFallbackClosure ($ operation ): ?Closure
347366 {
348- if (!($ clone = $ this ->WithoutOverrides )) {
367+ $ clone = $ this ->WithoutOverrides ;
368+ if (!$ clone ) {
349369 $ clone = clone $ this ;
350370 $ clone ->Overrides = [];
351371 $ this ->WithoutOverrides = $ clone ;
@@ -354,6 +374,44 @@ final public function getFallbackSyncOperationClosure($operation): ?Closure
354374 return $ clone ->getSyncOperationClosure ($ operation );
355375 }
356376
377+ /**
378+ * @deprecated Use {@see SyncDefinition::getFallbackClosure()} instead
379+ *
380+ * @param OP::* $operation
381+ * @return (Closure(ISyncContext, mixed...): (iterable<TEntity>|TEntity))|null
382+ * @phpstan-return (
383+ * $operation is OP::READ
384+ * ? (Closure(ISyncContext, int|string|null, mixed...): TEntity)
385+ * : (
386+ * $operation is OP::READ_LIST
387+ * ? (Closure(ISyncContext, mixed...): iterable<TEntity>)
388+ * : (
389+ * $operation is OP::CREATE|OP::UPDATE|OP::DELETE
390+ * ? (Closure(ISyncContext, TEntity, mixed...): TEntity)
391+ * : (Closure(ISyncContext, iterable<TEntity>, mixed...): iterable<TEntity>)
392+ * )
393+ * )
394+ * )|null
395+ */
396+ final public function getFallbackSyncOperationClosure ($ operation ): ?Closure
397+ {
398+ return $ this ->getFallbackClosure ($ operation );
399+ }
400+
401+ /**
402+ * Specify whether to perform READ operations by iterating over entities
403+ * returned by READ_LIST
404+ *
405+ * @return $this
406+ */
407+ final public function withReadFromReadList (bool $ readFromReadList = true )
408+ {
409+ $ clone = clone $ this ;
410+ $ clone ->ReadFromReadList = $ readFromReadList ;
411+
412+ return $ clone ;
413+ }
414+
357415 /**
358416 * Get an entity-to-data pipeline for the entity
359417 *
@@ -476,6 +534,19 @@ function (ISyncContext $ctx, ?bool &$returnEmpty, &$empty) use ($operation): voi
476534 );
477535 }
478536
537+ /**
538+ * @param iterable<TEntity> $result
539+ * @return FluentIteratorInterface<array-key,TEntity>
540+ */
541+ private function getFluentIterator (iterable $ result ): FluentIteratorInterface
542+ {
543+ if (!($ result instanceof FluentIteratorInterface)) {
544+ return new IterableIterator ($ result );
545+ }
546+
547+ return $ result ;
548+ }
549+
479550 public static function getReadable (): array
480551 {
481552 return [
0 commit comments