Skip to content

Commit 1e0af4b

Browse files
Added split feed support
1 parent 6b7ba1d commit 1e0af4b

File tree

16 files changed

+426
-89
lines changed

16 files changed

+426
-89
lines changed

src/Feeds/Feed.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ public function chunkSize(): int
4646
return 1000;
4747
}
4848

49+
public function perFile(): int
50+
{
51+
return 0;
52+
}
53+
54+
public function maxFiles(): int
55+
{
56+
return 0;
57+
}
58+
4959
public function header(): string
5060
{
5161
return match ($this->format()) {
@@ -87,10 +97,26 @@ public function filename(): string
8797
->toString();
8898
}
8999

90-
public function path(): string
100+
public function path(int|string $suffix = ''): string
91101
{
102+
if (empty($suffix)) {
103+
return $this->storage()->path(
104+
$this->filename()
105+
);
106+
}
107+
108+
$filename = $this->filename();
109+
110+
$directory = pathinfo($filename, PATHINFO_DIRNAME);
111+
$basename = pathinfo($filename, PATHINFO_FILENAME);
112+
$extension = pathinfo($filename, PATHINFO_EXTENSION);
113+
114+
if ($suffix) {
115+
$suffix = '-' . $suffix;
116+
}
117+
92118
return $this->storage()->path(
93-
$this->filename()
119+
"$directory/$basename$suffix.$extension"
94120
);
95121
}
96122

src/Services/ExportService.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DragonCode\LaravelFeed\Services;
6+
7+
use Closure;
8+
use DragonCode\LaravelFeed\Feeds\Feed;
9+
use Illuminate\Console\OutputStyle;
10+
use Illuminate\Database\Eloquent\Model;
11+
use Symfony\Component\Console\Helper\ProgressBar;
12+
13+
use function blank;
14+
use function implode;
15+
use function max;
16+
use function value;
17+
18+
class ExportService
19+
{
20+
protected int $chunk;
21+
22+
protected readonly int $perFile;
23+
24+
protected readonly int $maxFiles;
25+
26+
protected readonly int $modelCount;
27+
28+
protected int $total;
29+
30+
protected Closure $createFile;
31+
32+
protected Closure $closeFile;
33+
34+
protected Closure $item;
35+
36+
protected ?ProgressBar $progressBar;
37+
38+
protected $resource;
39+
40+
protected int $file = 1;
41+
42+
protected int $line = 0;
43+
44+
protected int $records = 0;
45+
46+
protected array $content = [];
47+
48+
public function __construct(
49+
protected Feed $feed,
50+
protected FilesystemService $filesystem,
51+
protected ?OutputStyle $output,
52+
) {
53+
$this->perFile = $this->perFile($this->feed);
54+
$this->maxFiles = $this->maxFiles($this->feed);
55+
$this->total = $this->total();
56+
57+
$this->progressBar = $this->createProgressBar(
58+
$this->total
59+
);
60+
}
61+
62+
public function chunk(int $chunk): static
63+
{
64+
$this->chunk = $chunk;
65+
66+
return $this;
67+
}
68+
69+
public function file(Closure $create, Closure $close): static
70+
{
71+
$this->createFile = $create;
72+
$this->closeFile = $close;
73+
74+
return $this;
75+
}
76+
77+
public function item(Closure $callback): static
78+
{
79+
$this->item = $callback;
80+
81+
return $this;
82+
}
83+
84+
public function export(): void
85+
{
86+
$this->feed->builder()
87+
->lazyById($this->chunk)
88+
->each(function (Model $model) {
89+
$this->line++;
90+
$this->records++;
91+
$this->total--;
92+
93+
$this->content[] = value($this->item, $model, $this->line);
94+
95+
$this->store();
96+
97+
if ($this->total <= 0) {
98+
return false;
99+
}
100+
101+
//if ($this->maxFiles && $this->file >= $this->maxFiles) {
102+
// return false;
103+
//}
104+
});
105+
106+
$this->store(true);
107+
108+
$this->progressBar?->finish();
109+
}
110+
111+
protected function store(bool $force = false): void
112+
{
113+
if ($force || $this->records >= $this->perFile || $this->line >= $this->chunk) {
114+
$this->line = 0;
115+
116+
$this->append();
117+
118+
$this->content = [];
119+
}
120+
121+
if ($force || $this->records >= $this->perFile) {
122+
$this->records = 0;
123+
124+
$this->releaseFile();
125+
}
126+
}
127+
128+
protected function getFile() // @pest-ignore-type
129+
{
130+
if (! empty($this->resource)) {
131+
return $this->resource;
132+
}
133+
134+
return $this->resource ??= value($this->createFile);
135+
}
136+
137+
protected function releaseFile(): void
138+
{
139+
if ($this->resource === null) {
140+
return;
141+
}
142+
143+
value($this->closeFile, $this->resource, $this->file);
144+
145+
$this->resource = null;
146+
147+
$this->file++;
148+
}
149+
150+
protected function append(): void
151+
{
152+
if (blank($this->content)) {
153+
return;
154+
}
155+
156+
$this->filesystem->append($this->getFile(), implode(PHP_EOL, $this->content), $this->feed->path());
157+
}
158+
159+
protected function perFile(Feed $feed): int
160+
{
161+
if ($count = max($feed->perFile(), 0)) {
162+
return $count;
163+
}
164+
165+
return $this->modelCount();
166+
}
167+
168+
protected function maxFiles(Feed $feed): int
169+
{
170+
return max($feed->maxFiles(), 0);
171+
}
172+
173+
protected function total(): int
174+
{
175+
if ($this->maxFiles <= 0) {
176+
return $this->modelCount();
177+
}
178+
179+
return $this->perFile * $this->maxFiles;
180+
}
181+
182+
protected function modelCount(): int
183+
{
184+
return $this->modelCount ??= $this->feed->builder()->count();
185+
}
186+
187+
protected function createProgressBar(int $total): ?ProgressBar
188+
{
189+
return $this->output?->createProgressBar($total);
190+
}
191+
}

src/Services/FilesystemService.php

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616

1717
use function dirname;
1818
use function fclose;
19-
use function fflush;
20-
use function flock;
2119
use function fopen;
22-
use function ftruncate;
2320
use function fwrite;
2421
use function is_resource;
2522
use function microtime;
@@ -47,8 +44,6 @@ public function createDraft(string $filename) // @pest-ignore-type
4744
// @codeCoverageIgnoreEnd
4845
}
4946

50-
$this->lock($resource);
51-
5247
return $resource;
5348
// @codeCoverageIgnoreStart
5449
} catch (Throwable $e) {
@@ -77,7 +72,6 @@ public function release($resource, string $path): void // @pest-ignore-type
7772
try {
7873
$temp = $this->getMetaPath($resource);
7974

80-
$this->unlock($resource);
8175
$this->close($resource);
8276

8377
if ($this->file->exists($path)) {
@@ -93,7 +87,7 @@ public function release($resource, string $path): void // @pest-ignore-type
9387
$this->cleanTemporaryDirectory($temp);
9488
// @codeCoverageIgnoreStart
9589
} catch (Throwable $e) {
96-
throw new CloseFeedException($temp, $e);
90+
throw new CloseFeedException($path, $e);
9791
}
9892
// @codeCoverageIgnoreEnd
9993
}
@@ -145,27 +139,4 @@ protected function getMetaPath($file): string // @pest-ignore-type
145139

146140
return $meta['uri'] ?? throw new ResourceMetaException;
147141
}
148-
149-
/**
150-
* @param resource $resource
151-
*/
152-
protected function lock($resource): void // @pest-ignore-type
153-
{
154-
if (! flock($resource, LOCK_EX)) {
155-
// @codeCoverageIgnoreStart
156-
throw new RuntimeException('Resource lock error. The resource may be in use by another process.');
157-
// @codeCoverageIgnoreEnd
158-
}
159-
160-
ftruncate($resource, 0);
161-
}
162-
163-
/**
164-
* @param resource $resource
165-
*/
166-
protected function unlock($resource): void // @pest-ignore-type
167-
{
168-
fflush($resource);
169-
flock($resource, LOCK_UN);
170-
}
171142
}

0 commit comments

Comments
 (0)