@@ -33,37 +33,30 @@ public function __construct(
3333 *
3434 * @param resource $source
3535 *
36- * @return array<string,array<string,string|array<string>> >
36+ * @return Generator<int, ImportCountEvent|ImportObjectEvent >
3737 *
3838 * @throws \InvalidArgumentException
3939 */
40- public function import ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options ): array {
40+ public function import ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options ): Generator {
4141 if (!is_resource ($ source )) {
4242 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
4343 }
44- switch ($ options ->getFormat ()) {
45- case 'ical ' :
46- return $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importText (...));
47- break ;
48- case 'jcal ' :
49- return $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importJson (...));
50- break ;
51- case 'xcal ' :
52- return $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importXml (...));
53- break ;
54- default :
55- throw new InvalidArgumentException ('Invalid import format ' );
56- }
44+ return match ($ options ->getFormat ()) {
45+ 'ical ' => $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importText (...)),
46+ 'jcal ' => $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importJson (...)),
47+ 'xcal ' => $ this ->importProcess ($ source , $ calendar , $ options , $ this ->importXml (...)),
48+ default => throw new InvalidArgumentException ('Invalid import format ' ),
49+ };
5750 }
5851
5952 /**
6053 * Generates object stream from a text formatted source (ical)
6154 *
6255 * @param resource $source
6356 *
64- * @return Generator<\Sabre\VObject\Component\ VCalendar>
57+ * @return Generator<int|string, VCalendar|array{VEVENT: int, VTODO: int, VJOURNAL: int}, mixed, void >
6558 */
66- public function importText ($ source ): Generator {
59+ public function importText ($ source, ? CalendarImportOptions $ options = null ): Generator {
6760 if (!is_resource ($ source )) {
6861 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
6962 }
@@ -85,6 +78,14 @@ public function importText($source): Generator {
8578 $ vObject = Reader::read ($ sObjectPrefix . $ sObjectContents . $ sObjectSuffix );
8679 $ timezones [$ tid ] = clone $ vObject ->VTIMEZONE ;
8780 }
81+ // object counts before streaming if requested
82+ if ($ options ?->getCounts()) {
83+ yield 'counts ' => [
84+ 'VEVENT ' => count ($ structure ['VEVENT ' ]),
85+ 'VTODO ' => count ($ structure ['VTODO ' ]),
86+ 'VJOURNAL ' => count ($ structure ['VJOURNAL ' ]),
87+ ];
88+ }
8889 // calendar components
8990 // for each component type, construct a full calendar object with all components
9091 // that match the same UID and appropriate time zones that are used in the components
@@ -114,9 +115,9 @@ public function importText($source): Generator {
114115 *
115116 * @param resource $source
116117 *
117- * @return Generator<\Sabre\VObject\Component\ VCalendar>
118+ * @return Generator<int|string, VCalendar|array{VEVENT: int, VTODO: int, VJOURNAL: int}, mixed, void >
118119 */
119- public function importXml ($ source ): Generator {
120+ public function importXml ($ source, ? CalendarImportOptions $ options = null ): Generator {
120121 if (!is_resource ($ source )) {
121122 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
122123 }
@@ -132,6 +133,14 @@ public function importXml($source): Generator {
132133 $ vObject = Reader::readXml ($ sObjectPrefix . $ sObjectContents . $ sObjectSuffix );
133134 $ timezones [$ tid ] = clone $ vObject ->VTIMEZONE ;
134135 }
136+ // object counts before streaming if requested
137+ if ($ options ?->getCounts()) {
138+ yield 'counts ' => [
139+ 'VEVENT ' => count ($ structure ['VEVENT ' ]),
140+ 'VTODO ' => count ($ structure ['VTODO ' ]),
141+ 'VJOURNAL ' => count ($ structure ['VJOURNAL ' ]),
142+ ];
143+ }
135144 // calendar components
136145 // for each component type, construct a full calendar object with all components
137146 // that match the same UID and appropriate time zones that are used in the components
@@ -160,10 +169,11 @@ public function importXml($source): Generator {
160169 * Generates object stream from a json formatted source (jcal)
161170 *
162171 * @param resource $source
172+ * @param CalendarImportOptions|null $options
163173 *
164- * @return Generator<\Sabre\VObject\Component\ VCalendar>
174+ * @return Generator<int|string, VCalendar|array{VEVENT: int, VTODO: int, VJOURNAL: int}, mixed, void >
165175 */
166- public function importJson ($ source ): Generator {
176+ public function importJson ($ source, ? CalendarImportOptions $ options = null ): Generator {
167177 if (!is_resource ($ source )) {
168178 throw new InvalidArgumentException ('Invalid import source must be a file resource ' );
169179 }
@@ -178,7 +188,27 @@ public function importJson($source): Generator {
178188 }
179189 }
180190 // calendar components
181- foreach ($ importer ->getBaseComponents () as $ base ) {
191+ $ baseComponents = $ importer ->getBaseComponents ();
192+ // object counts before streaming if requested
193+ if ($ options ?->getCounts()) {
194+ /** @var array{VEVENT: int, VTODO: int, VJOURNAL: int} $counts */
195+ $ counts = ['VEVENT ' => 0 , 'VTODO ' => 0 , 'VJOURNAL ' => 0 ];
196+ foreach ($ baseComponents as $ component ) {
197+ switch ($ component ->name ) {
198+ case 'VEVENT ' :
199+ $ counts ['VEVENT ' ]++;
200+ break ;
201+ case 'VTODO ' :
202+ $ counts ['VTODO ' ]++;
203+ break ;
204+ case 'VJOURNAL ' :
205+ $ counts ['VJOURNAL ' ]++;
206+ break ;
207+ }
208+ }
209+ yield 'counts ' => $ counts ;
210+ }
211+ foreach ($ baseComponents as $ base ) {
182212 $ vObject = new VCalendar ;
183213 $ vObject ->VERSION = clone $ importer ->VERSION ;
184214 $ vObject ->PRODID = clone $ importer ->PRODID ;
@@ -225,22 +255,34 @@ private function findTimeZones(VCalendar $vObject): array {
225255 * @param CalendarImportOptions $options
226256 * @param callable $generator<CalendarImportOptions>: Generator<\Sabre\VObject\Component\VCalendar>
227257 *
228- * @return array<string,array<string,string|array<string>> >
258+ * @return Generator<int, ImportCountEvent|ImportObjectEvent >
229259 */
230- public function importProcess ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options , callable $ generator ): array {
260+ public function importProcess ($ source , CalendarImpl $ calendar , CalendarImportOptions $ options , callable $ generator ): Generator {
231261 $ calendarId = $ calendar ->getKey ();
232262 $ calendarUri = $ calendar ->getUri ();
233263 $ principalUri = $ calendar ->getPrincipalUri ();
234- $ outcome = [];
235- foreach ($ generator ($ source ) as $ vObject ) {
264+ foreach ($ generator ($ source , $ options ) as $ key => $ value ) {
265+ if ($ key === 'counts ' ) {
266+ yield new ImportCountEvent (
267+ vevent: $ value ['VEVENT ' ] ?? 0 ,
268+ vtodo: $ value ['VTODO ' ] ?? 0 ,
269+ vjournal: $ value ['VJOURNAL ' ] ?? 0 ,
270+ );
271+ continue ;
272+ }
273+ $ vObject = $ value ;
236274 $ components = $ vObject ->getBaseComponents ();
237275 // determine if the object has no base component types
238276 if (count ($ components ) === 0 ) {
239277 $ errorMessage = 'One or more objects discovered with no base component types ' ;
240278 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
241279 throw new InvalidArgumentException ('Error importing calendar data: ' . $ errorMessage );
242280 }
243- $ outcome ['nbct ' ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
281+ yield new ImportObjectEvent (
282+ disposition: ImportDisposition::Error,
283+ identifier: null ,
284+ errors: [$ errorMessage ]
285+ );
244286 continue ;
245287 }
246288 // determine if the object has more than one base component type
@@ -254,7 +296,11 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
254296 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
255297 throw new InvalidArgumentException ('Error importing calendar data: ' . $ errorMessage );
256298 }
257- $ outcome ['mbct ' ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
299+ yield new ImportObjectEvent (
300+ disposition: ImportDisposition::Error,
301+ identifier: null ,
302+ errors: [$ errorMessage ]
303+ );
258304 continue 2 ;
259305 }
260306 }
@@ -265,15 +311,23 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
265311 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
266312 throw new InvalidArgumentException ('Error importing calendar data: ' . $ errorMessage );
267313 }
268- $ outcome ['noid ' ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
314+ yield new ImportObjectEvent (
315+ disposition: ImportDisposition::Error,
316+ identifier: null ,
317+ errors: [$ errorMessage ]
318+ );
269319 continue ;
270320 }
271321 $ uid = $ components [0 ]->UID ->getValue ();
272322 // validate object
273323 if ($ options ->getValidate () !== $ options ::VALIDATE_NONE ) {
274324 $ issues = $ this ->componentValidate ($ vObject , true , 3 );
275325 if ($ options ->getValidate () === $ options ::VALIDATE_SKIP && $ issues !== []) {
276- $ outcome [$ uid ] = ['outcome ' => 'error ' , 'errors ' => $ issues ];
326+ yield new ImportObjectEvent (
327+ disposition: ImportDisposition::Error,
328+ identifier: $ uid ,
329+ errors: $ issues
330+ );
277331 continue ;
278332 } elseif ($ options ->getValidate () === $ options ::VALIDATE_FAIL && $ issues !== []) {
279333 throw new InvalidArgumentException ('Error importing calendar data: UID < ' . $ uid . '> - ' . $ issues [0 ]);
@@ -290,7 +344,10 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
290344 $ objectId ,
291345 $ objectData
292346 );
293- $ outcome [$ uid ] = ['outcome ' => 'created ' ];
347+ yield new ImportObjectEvent (
348+ disposition: ImportDisposition::Created,
349+ identifier: $ uid ,
350+ );
294351 } else {
295352 [$ cid , $ oid ] = explode ('/ ' , $ objectId );
296353 if ($ options ->getSupersede ()) {
@@ -299,21 +356,29 @@ public function importProcess($source, CalendarImpl $calendar, CalendarImportOpt
299356 $ oid ,
300357 $ objectData
301358 );
302- $ outcome [$ uid ] = ['outcome ' => 'updated ' ];
359+ yield new ImportObjectEvent (
360+ disposition: ImportDisposition::Updated,
361+ identifier: $ uid ,
362+ );
303363 } else {
304- $ outcome [$ uid ] = ['outcome ' => 'exists ' ];
364+ yield new ImportObjectEvent (
365+ disposition: ImportDisposition::Exists,
366+ identifier: $ uid ,
367+ );
305368 }
306369 }
307370 } catch (Exception $ e ) {
308371 $ errorMessage = $ e ->getMessage ();
309372 if ($ options ->getErrors () === $ options ::ERROR_FAIL ) {
310373 throw new Exception ('Error importing calendar data: UID < ' . $ uid . '> - ' . $ errorMessage , 0 , $ e );
311374 }
312- $ outcome [$ uid ] = ['outcome ' => 'error ' , 'errors ' => [$ errorMessage ]];
375+ yield new ImportObjectEvent (
376+ disposition: ImportDisposition::Error,
377+ identifier: $ uid ,
378+ errors: [$ errorMessage ]
379+ );
313380 }
314381 }
315-
316- return $ outcome ;
317382 }
318383
319384 /**
0 commit comments