• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cecilapp / Cecil / 26945982891

04 Jun 2026 10:24AM UTC coverage: 82.017% (-0.07%) from 82.085%
26945982891

Pull #2400

github

web-flow
Merge a2466c63e into ffa4b35b8
Pull Request #2400: fix: log and skip unsupported/empty data files

2 of 7 new or added lines in 1 file covered. (28.57%)

3521 of 4293 relevant lines covered (82.02%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.72
/src/Step/Data/Load.php
1
<?php
2

3
/**
4
 * This file is part of Cecil.
5
 *
6
 * (c) Arnaud Ligny <arnaud@ligny.fr>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace Cecil\Step\Data;
15

16
use Cecil\Collection\Page\PrefixSuffix;
17
use Cecil\Step\AbstractStep;
18
use Cecil\Util;
19
use Symfony\Component\Finder\Finder;
20
use Symfony\Component\Serializer\Encoder\CsvEncoder;
21
use Symfony\Component\Serializer\Encoder\JsonEncoder;
22
use Symfony\Component\Serializer\Encoder\XmlEncoder;
23
use Symfony\Component\Serializer\Encoder\YamlEncoder;
24
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
25
use Symfony\Component\Serializer\Serializer;
26

27
/**
28
 * Load step class.
29
 *
30
 * This class is responsible for loading data files from a specified directory,
31
 * decoding their contents based on the file extension, and merging the data
32
 * into the builder's data collection. It supports various file formats such as
33
 * YAML, JSON, CSV, and XML. The loaded data is organized into a nested array
34
 * structure based on the file paths and language suffixes.
35
 */
36
class Load extends AbstractStep
37
{
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function getName(): string
42
    {
43
        return 'Loading data';
1✔
44
    }
45

46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function init(array $options): void
50
    {
51
        if (is_dir($this->config->getDataPath()) && $this->config->isEnabled('data.load')) {
1✔
52
            $this->canProcess = true;
1✔
53
        }
54
    }
55

56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function process(): void
60
    {
61
        $files = Finder::create()
1✔
62
            ->files()
1✔
63
            ->in($this->config->getDataPath())
1✔
64
            ->name('/\.(' . implode('|', (array) $this->config->get('data.ext')) . ')$/')
1✔
65
            ->sortByName(true);
1✔
66

67
        if ($this->config->hasTheme()) {
1✔
68
            $themes = $this->config->getTheme();
1✔
69
            foreach ($themes ?? [] as $theme) {
1✔
70
                if (Util\File::getFS()->exists($this->config->getThemeDirPath($theme, 'data'))) {
1✔
71
                    $files->in($this->config->getThemeDirPath($theme, 'data'));
×
72
                }
73
            }
74
        }
75

76
        $total = \count($files);
1✔
77

78
        if ($total < 1) {
1✔
79
            $message = 'No files';
×
80
            $this->builder->getLogger()->info($message);
×
81

82
            return;
×
83
        }
84

85
        $serializerYaml = new Serializer([new ObjectNormalizer()], [new YamlEncoder()]);
1✔
86
        $serializerJson = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
1✔
87
        $serializerCsv = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
1✔
88
        $serializerXml = new Serializer([new ObjectNormalizer()], [new XmlEncoder()]);
1✔
89
        $count = 0;
1✔
90

91
        /** @var \Symfony\Component\Finder\SplFileInfo $file */
92
        foreach ($files as $file) {
1✔
93
            $count++;
1✔
94
            set_error_handler(
1✔
95
                function ($severity, $message, $file, $line) {
1✔
96
                    throw new \ErrorException($message, 0, $severity, $file, $line, null);
×
97
                }
1✔
98
            );
1✔
99
            $data = $file->getContents();
1✔
100
            restore_error_handler();
1✔
101

102
            $dataAsArray = [];
1✔
103
            switch ($file->getExtension()) {
1✔
104
                case 'yml':
1✔
105
                case 'yaml':
1✔
106
                    $dataAsArray = $serializerYaml->decode($data, 'yaml');
1✔
107
                    break;
1✔
108
                case 'json':
1✔
109
                    $dataAsArray = $serializerJson->decode($data, 'json');
1✔
110
                    break;
1✔
111
                case 'csv':
1✔
112
                    $dataAsArray = $serializerCsv->decode($data, 'csv');
1✔
113
                    break;
1✔
114
                case 'xml':
1✔
115
                    $dataAsArray = $serializerXml->decode($data, 'xml');
1✔
116
                    break;
1✔
117
                default:
NEW
118
                    $message = \sprintf('File "%s" has an unsupported extension', $file->getRelativePathname());
×
NEW
119
                    $this->builder->getLogger()->warning($message, ['progress' => [$count, $total]]);
×
120
                    continue;
121
            }
122

123
            // If the file is empty or contains invalid data, skip it and log a warning
124
            if (!\is_array($dataAsArray)) {
1✔
NEW
125
                $message = \sprintf('File "%s" is empty or contains invalid data', $file->getRelativePathname());
×
NEW
126
                $this->builder->getLogger()->warning($message, ['progress' => [$count, $total]]);
×
127

NEW
128
                continue;
×
129
            }
130

131
            $lang = $this->config->getLanguageDefault();
1✔
132
            if (PrefixSuffix::hasSuffix($file->getFilenameWithoutExtension())) {
1✔
133
                $lang = PrefixSuffix::getSuffix($file->getFilenameWithoutExtension());
×
134
            }
135
            $array = [];
1✔
136
            $path = Util::joinFile((string) pathinfo($file->getRelativePathname(), \PATHINFO_DIRNAME), (string) pathinfo($file->getRelativePathname(), \PATHINFO_FILENAME));
1✔
137
            $path = trim($path, './');
1✔
138
            $localizedPath = Util::joinFile((string) $lang, PrefixSuffix::sub($path));
1✔
139
            $this->pathToArray($array, $localizedPath, $dataAsArray);
1✔
140

141
            $dataAsArray = array_merge_recursive(
1✔
142
                $this->builder->getData(),
1✔
143
                $array
1✔
144
            );
1✔
145
            $this->builder->setData($dataAsArray);
1✔
146

147
            $message = \sprintf('File "%s" loaded', $file->getRelativePathname());
1✔
148
            $this->builder->getLogger()->info($message, ['progress' => [$count, $total]]);
1✔
149
        }
150
    }
151

152
    /**
153
     * Puts a path/value couple into an array.
154
     *
155
     * @param array  $arr       Target array
156
     * @param string $path      Source path
157
     * @param array  $value     Source values
158
     * @param string $separator Path separator (ie: '/')
159
     */
160
    private function pathToArray(array &$arr, string $path, array $value, string $separator = DIRECTORY_SEPARATOR): void
161
    {
162
        $keys = explode($separator, $path);
1✔
163
        foreach ($keys as $key) {
1✔
164
            $arr = &$arr[$key];
1✔
165
        }
166
        $arr = $value;
1✔
167
    }
168
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc