1final class ImportVacancies
2{
3 public function run(array $items): void
4 {
5 foreach ($items as $item) {
6 $item['title'] = trim(preg_replace('/\s+/', ' ', $item['title']));
7 $item['city'] = $this->cities->normalize($item['city'] ?? 'Віддалено');
8
9 if ($this->vacancies->existsByUrl($item['url'])) {
10 continue;
11 }
12
13 $item['tags'] = [];
14 if (str_contains(strtolower($item['title']), 'laravel')) {
15 $item['tags'][] = 'laravel';
16 }
17
18 $this->vacancies->create($item);
19 }
20 }
21}
1<?php
2
31final class Normalize
4{
5 public function __invoke(array $items): array
6 {
7 return array_map(fn (array $item): array => [
8 ...$item,
9 'title' => trim(preg_replace('/\s+/', ' ', $item['title'])),
10 'city' => in_array(mb_strtolower($item['city']), ['kyiv', 'киев', 'київ'], true) ? 'Київ' : $item['city'],
11 ], $items);
12 }
13}
14
152final class Dedupe
16{
17 /** @param list<string> $known */
18 public function __construct(private array $known) {}
19
20 public function __invoke(array $items): array
21 {
22 $fresh = array_filter($items, fn (array $item): bool => ! in_array($item['url'], $this->known, true));
23
24 return array_values(array_column($fresh, null, 'url'));
25 }
26}
27
28final class Classify
29{
30 public function __invoke(array $items): array
31 {
32 return array_map(fn (array $item): array => [
33 ...$item,
34 'tags' => array_values(array_filter(
35 ['laravel', 'symfony'],
36 fn (string $tag): bool => str_contains(mb_strtolower($item['title']), $tag),
37 )),
38 ], $items);
39 }
40}
41
423function pipeline(array $data, callable ...$stages): array
43{
44 return array_reduce($stages, fn (array $carry, callable $stage): array => $stage($carry), $data);
45}
46
47$feed = [
48 ['title' => ' Senior Laravel Developer ', 'city' => 'Kyiv', 'url' => 'https://example.com/1'],
49 ['title' => 'Symfony Developer', 'city' => 'Львів', 'url' => 'https://example.com/2'],
50 ['title' => 'Symfony Developer', 'city' => 'Львів', 'url' => 'https://example.com/2'],
51 ['title' => 'PHP Team Lead', 'city' => 'Київ', 'url' => 'https://example.com/3'],
52];
53
544$vacancies = pipeline($feed, new Normalize(), new Dedupe(known: ['https://example.com/3']), new Classify());
55
56foreach ($vacancies as $vacancy) {
57 echo mb_str_pad($vacancy['title'], 26), mb_str_pad($vacancy['city'], 7), '[', implode(', ', $vacancy['tags']), "]\n";
58}
59
60echo 'У фіді ', count($feed), ', нових ', count($vacancies), "\n";