Passed
Push — master ( 8b9eca...d54ee5 )
by Fran
21:46
created

GeneratorService::createModuleModels()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 3
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
1
<?php
2
namespace PSFS\Services;
3
4
use PSFS\base\Cache;
5
use PSFS\base\exception\ConfigException;
6
use PSFS\base\Service;
7
use PSFS\base\types\helpers\GeneratorHelper;
8
9
class GeneratorService extends Service
10
{
11
    /**
12
     * @Inyectable
13
     * @var \PSFS\base\config\Config Servicio de configuración
14
     */
15
    protected $config;
16
    /**
17
     * @Inyectable
18
     * @var \PSFS\base\Security Servicio de autenticación
19
     */
20
    protected $security;
21
    /**
22
     * @Inyectable
23
     * @var \PSFS\base\Template Servicio de gestión de plantillas
24
     */
25
    protected $tpl;
26
27
    /**
28
     * Método que revisa las traducciones directorio a directorio
29
     * @param $path
30
     * @param $locale
31
     * @return array
32
     */
33
    public static function findTranslations($path, $locale)
34
    {
35
        $locale_path = realpath(BASE_DIR . DIRECTORY_SEPARATOR . 'locale');
36
        $locale_path .= DIRECTORY_SEPARATOR . $locale . DIRECTORY_SEPARATOR . 'LC_MESSAGES' . DIRECTORY_SEPARATOR;
37
38
        $translations = array();
39
        if (file_exists($path)) {
40
            $d = dir($path);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $d. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
41
            while (false !== ($dir = $d->read())) {
42
                GeneratorHelper::createDir($locale_path);
43
                if (!file_exists($locale_path . 'translations.po')) {
44
                    file_put_contents($locale_path . 'translations.po', '');
45
                }
46
                $inspect_path = realpath($path . DIRECTORY_SEPARATOR . $dir);
47
                $cmd_php = "export PATH=\$PATH:/opt/local/bin; xgettext " .
48
                    $inspect_path . DIRECTORY_SEPARATOR .
49
                    "*.php --from-code=UTF-8 -j -L PHP --debug --force-po -o {$locale_path}translations.po";
50
                if (is_dir($path . DIRECTORY_SEPARATOR . $dir) && preg_match('/^\./', $dir) == 0) {
51
                    $res = _('Revisando directorio: ') . $inspect_path;
52
                    $res .= _('Comando ejecutado: ') . $cmd_php;
53
                    $res .= shell_exec($cmd_php);
54
                    usleep(10);
55
                    $translations[] = $res;
56
                    $translations = array_merge($translations, self::findTranslations($inspect_path, $locale));
57
                }
58
            }
59
        }
60
        return $translations;
61
    }
62
63
    /**
64
     * Servicio que genera la estructura de un módulo o lo actualiza en caso de ser necesario
65
     * @param string $module
66
     * @param boolean $force
67
     * @param string $type
68
     * @param boolean $isModule
69
     * @return mixed
70
     */
71
    public function createStructureModule($module, $force = false, $type = "", $isModule = false)
72
    {
73
        $mod_path = CORE_DIR . DIRECTORY_SEPARATOR;
74
        $module = ucfirst($module);
75
        $this->createModulePath($module, $mod_path, $isModule);
76
        $this->createModulePathTree($module, $mod_path, $isModule);
0 ignored issues
show
Documentation introduced by
$mod_path is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
77
        $this->createModuleBaseFiles($module, $mod_path, $force, $type, $isModule);
78
        $this->createModuleModels($module, $mod_path, $isModule);
79
        $this->generateBaseApiTemplate($module, $mod_path, $force, $isModule);
80
        //Redireccionamos al home definido
81
        $this->log->infoLog("Módulo generado correctamente");
82
    }
83
84
    /**
85
     * Service that creates the root paths for the modules
86
     * @param string $module
87
     * @param string $mod_path
88
     * @param boolean $isModule
89
     */
90
    private function createModulePath($module, $mod_path, $isModule = false)
91
    {
92
        // Creates the src folder
93
        GeneratorHelper::createDir($mod_path);
94
        // Create module path
95
        if (false === $isModule) {
96
            GeneratorHelper::createDir($mod_path . $module);
97
        }
98
    }
99
100
    /**
101
     * Servicio que genera la estructura base
102
     * @param string $module
103
     * @param boolean $mod_path
104
     * @param boolean $isModule
105
     * @return boolean
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
106
     */
107
    private function createModulePathTree($module, $mod_path, $isModule = false)
108
    {
109
        //Creamos las carpetas CORE del módulo
110
        $this->log->infoLog("Generamos la estructura");
111
        $paths = [
112
            "Api", "Api/base", "Config", "Controller", "Form", "Models", "Public", "Templates", "Services", "Test"
113
        ];
114
        $module_path = $isModule ? $mod_path : $mod_path . $module;
115
        foreach ($paths as $path) {
116
            GeneratorHelper::createDir($module_path . DIRECTORY_SEPARATOR . $path);
117
        }
118
        //Creamos las carpetas de los assets
119
        $htmlPaths = array("css", "js", "img", "media", "font");
120
        foreach ($htmlPaths as $path) {
121
            GeneratorHelper::createDir($module_path . DIRECTORY_SEPARATOR . "Public" . DIRECTORY_SEPARATOR . $path);
122
        }
123
124
        if ($isModule) {
125
            return $this->writeTemplateToFile(json_encode([
126
                "module" => "\\" . preg_replace('/(\\\|\/)/', '\\\\', $module),
127
            ], JSON_PRETTY_PRINT), $mod_path . DIRECTORY_SEPARATOR . "module.json", true);
128
        }
129
    }
130
131
    /**
132
     * Servicio que genera las plantillas básicas de ficheros del módulo
133
     * @param string $module
134
     * @param string $mod_path
135
     * @param boolean $force
136
     * @param string $controllerType
137
     * @param boolean $isModule
138
     */
139
    private function createModuleBaseFiles($module, $mod_path, $force = false, $controllerType = '', $isModule = false)
140
    {
141
        $module_path = $isModule ? $mod_path : $mod_path . $module;
142
        $this->generateControllerTemplate($module, $module_path, $force, $controllerType);
143
        $this->generateServiceTemplate($module, $module_path, $force);
144
        $this->genereateAutoloaderTemplate($module, $module_path, $force, $isModule);
145
        $this->generateSchemaTemplate($module, $module_path, $force);
146
        $this->generatePropertiesTemplate($module, $module_path, $force);
147
        $this->generateConfigTemplate($module_path, $force);
148
        $this->generateIndexTemplate($module, $module_path, $force);
149
        $this->generatePublicTemplates($module_path, $force);
150
    }
151
152
    /**
153
     * Servicio que ejecuta Propel y genera el modelo de datos
154
     * @param string $module
155
     * @param string $path
156
     * @param boolean $isModule
157
     */
158
    private function createModuleModels($module, $path, $isModule = false)
159
    {
160
        $module_path = $isModule ? $path : $path . $module;
161
        $module_path = str_replace(CORE_DIR . DIRECTORY_SEPARATOR, '', $module_path);
162
        //Generamos las clases de propel y la configuración
163
        $exec = "export PATH=\$PATH:/opt/local/bin; " . BASE_DIR . DIRECTORY_SEPARATOR .
164
            "vendor" . DIRECTORY_SEPARATOR . "bin" . DIRECTORY_SEPARATOR . "propel ";
165
        $schemaOpt = " --schema-dir=" . CORE_DIR . DIRECTORY_SEPARATOR . $module_path .
166
            DIRECTORY_SEPARATOR . "Config";
167
        $opt = " --config-dir=" . CORE_DIR . DIRECTORY_SEPARATOR . $module_path . DIRECTORY_SEPARATOR .
168
            "Config --output-dir=" . CORE_DIR . " --verbose";
169
        $this->log->infoLog("[GENERATOR] Ejecutamos propel:\n" . $exec . "build" . $opt . $schemaOpt);
170
        $ret = shell_exec($exec . "build" . $opt . $schemaOpt);
171
172
        $this->log->infoLog("[GENERATOR] Generamos clases invocando a propel:\n $ret");
173
        $ret = shell_exec($exec . "sql:build" . $opt . " --output-dir=" . CORE_DIR . DIRECTORY_SEPARATOR .
174
            $module_path . DIRECTORY_SEPARATOR . "Config" . $schemaOpt);
175
        $this->log->infoLog("[GENERATOR] Generamos sql invocando a propel:\n $ret");
176
177
        $configTemplate = $this->tpl->dump("generator/config.propel.template.twig", array(
178
            "module" => $module,
179
        ));
180
        $this->writeTemplateToFile($configTemplate, CORE_DIR . DIRECTORY_SEPARATOR . $module_path . DIRECTORY_SEPARATOR . "Config" .
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
181
            DIRECTORY_SEPARATOR . "config.php", true);
182
        $this->log->infoLog("Generado config genérico para propel:\n $ret");
183
    }
184
185
    /**
186
     * @param string $module
187
     * @param string $mod_path
188
     * @param boolean $force
189
     * @param string $controllerType
190
     * @return boolean
191
     */
192
    private function generateControllerTemplate($module, $mod_path, $force = false, $controllerType = "")
193
    {
194
        //Generamos el controlador base
195
        $this->log->infoLog("Generamos el controlador BASE");
196
        $class = preg_replace('/(\\\|\/)/', '', $module);
197
        $controllerBody = $this->tpl->dump("generator/controller.template.twig", array(
198
            "module" => $module,
199
            "namespace" => preg_replace('/(\\\|\/)/', '\\', $module),
200
            "url" => preg_replace('/(\\\|\/)/', '/', $module),
201
            "class" => $class,
202
            "controllerType" => $class . "Base",
203
            "is_base" => false
204
        ));
205
        $controller = $this->writeTemplateToFile($controllerBody, $mod_path . DIRECTORY_SEPARATOR . "Controller" .
206
            DIRECTORY_SEPARATOR . "{$class}Controller.php", $force);
207
208
        $controllerBody = $this->tpl->dump("generator/controller.template.twig", array(
209
            "module" => $module,
210
            "namespace" => preg_replace('/(\\\|\/)/', '\\', $module),
211
            "url" => preg_replace('/(\\\|\/)/', '/', $module),
212
            "class" => $class . "Base",
213
            "service" => $class,
214
            "controllerType" => $controllerType,
215
            "is_base" => true,
216
            "domain" => $class,
217
        ));
218
        $controllerBase = $this->writeTemplateToFile($controllerBody, $mod_path . DIRECTORY_SEPARATOR . "Controller" .
219
            DIRECTORY_SEPARATOR . "base" . DIRECTORY_SEPARATOR . "{$class}BaseController.php", true);
220
221
        $testTemplate = $this->tpl->dump("generator/testCase.template.twig", array(
222
            "module" => $module,
223
            "namespace" => preg_replace('/(\\\|\/)/', '\\', $module),
224
            "class" => $class,
225
        ));
226
        $test = $this->writeTemplateToFile($testTemplate, $mod_path . DIRECTORY_SEPARATOR . "Test" .
227
            DIRECTORY_SEPARATOR . "{$class}Test.php", true);
228
        return ($controller && $controllerBase && $test);
229
    }
230
231
    /**
232
     * @param string $module
233
     * @param string $mod_path
234
     * @param boolean $force
235
     * @param boolean $isModule
236
     * @return boolean
237
     */
238
    private function generateBaseApiTemplate($module, $mod_path, $force = false, $isModule = false)
239
    {
240
        $created = true;
241
        $modelPath = $isModule ?
242
            $mod_path . DIRECTORY_SEPARATOR . 'Models' :
243
            $mod_path . $module . DIRECTORY_SEPARATOR . 'Models';
244
        $api_path = $isModule ?
245
            $mod_path . DIRECTORY_SEPARATOR . 'Api' :
246
            $mod_path . $module . DIRECTORY_SEPARATOR . 'Api';
247
        if (file_exists($modelPath)) {
248
            $dir = dir($modelPath);
249
            while ($file = $dir->read()) {
250
                if (!in_array($file, array('.', '..'))
251
                    && !preg_match('/Query\.php$/i', $file)
252
                    && preg_match('/\.php$/i', $file)
253
                ) {
254
                    $filename = str_replace(".php", "", $file);
255
                    $this->log->infoLog("Generamos Api BASES para {$filename}");
256
                    $this->createApiBaseFile($module, $api_path, $filename);
257
                    $this->createApi($module, $api_path, $force, $filename);
258
                }
259
            }
260
        }
261
        return $created;
262
    }
263
264
    /**
265
     * @param string $mod_path
266
     * @param boolean $force
267
     * @return boolean
268
     */
269
    private function generateConfigTemplate($mod_path, $force = false)
270
    {
271
        //Generamos el fichero de configuración
272
        $this->log->infoLog("Generamos fichero vacío de configuración");
273
        return $this->writeTemplateToFile("<?php\n\t",
274
            $mod_path . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . "config.php",
275
            $force);
276
    }
277
278
    /**
279
     * @param string $mod_path
280
     * @param string $mod_path
281
     * @param boolean $force
282
     * @return boolean
283
     */
284
    private function generatePublicTemplates($mod_path, $force = false)
285
    {
286
        //Generamos el fichero de configuración
287
        $this->log->infoLog("Generamos ficheros para assets base");
288
        $css = $this->writeTemplateToFile("/* CSS3 STYLES */\n\n",
289
            $mod_path . DIRECTORY_SEPARATOR . "Public" . DIRECTORY_SEPARATOR . "css" . DIRECTORY_SEPARATOR . "styles.css",
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
290
            $force);
291
        $js = $this->writeTemplateToFile("/* APP MODULE JS */\n\n(function() {\n\t'use strict';\n})();",
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $js. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
292
            $mod_path . DIRECTORY_SEPARATOR . "Public" . DIRECTORY_SEPARATOR . "js" . DIRECTORY_SEPARATOR . "app.js",
293
            $force);
294
        return ($css && $js);
295
    }
296
297
    /**
298
     * @param string $module
299
     * @param string $mod_path
300
     * @param boolean $force
301
     * @return boolean
302
     */
303
    private function generateServiceTemplate($module, $mod_path, $force = false)
304
    {
305
        //Generamos el controlador base
306
        $this->log->infoLog("Generamos el servicio BASE");
307
        $class = preg_replace('/(\\\|\/)/', '', $module);
308
        $controller = $this->tpl->dump("generator/service.template.twig", array(
309
            "module" => $module,
310
            "namespace" => preg_replace('/(\\\|\/)/', '\\', $module),
311
            "class" => $class,
312
        ));
313
        return $this->writeTemplateToFile($controller,
314
            $mod_path . DIRECTORY_SEPARATOR . "Services" . DIRECTORY_SEPARATOR . "{$class}Service.php",
315
            $force);
316
    }
317
318
    /**
319
     * @param string $module
320
     * @param string $mod_path
321
     * @param boolean $force
322
     * @param boolean $isModule
323
     * @return boolean
324
     */
325
    private function genereateAutoloaderTemplate($module, $mod_path, $force = false, $isModule = false)
326
    {
327
        //Generamos el autoloader del módulo
328
        $this->log->infoLog("Generamos el autoloader");
329
        $autoloader = $this->tpl->dump("generator/autoloader.template.twig", array(
330
            "module" => $module,
331
            "autoloader" => preg_replace('/(\\\|\/)/', '_', $module),
332
            "regex" => preg_replace('/(\\\|\/)/m', '\\\\\\\\\\\\', $module),
333
            "is_module" => $isModule,
334
        ));
335
        $autoload = $this->writeTemplateToFile($autoloader, $mod_path . DIRECTORY_SEPARATOR . "autoload.php", $force);
336
337
        $this->log->infoLog("Generamos el phpunit");
338
        $phpUnitTemplate = $this->tpl->dump("generator/phpunit.template.twig", array(
339
            "module" => $module,
340
            "is_module" => $isModule,
341
        ));
342
        $phpunit = $this->writeTemplateToFile($phpUnitTemplate, $mod_path . DIRECTORY_SEPARATOR . "phpunit.xml.dist", $force);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
343
        return $autoload && $phpunit;
344
    }
345
346
    /**
347
     * @param string $module
348
     * @param string $mod_path
349
     * @param boolean $force
350
     * @return boolean
351
     */
352
    private function generateSchemaTemplate($module, $mod_path, $force = false)
353
    {
354
        //Generamos el autoloader del módulo
355
        $this->log->infoLog("Generamos el schema");
356
        $schema = $this->tpl->dump("generator/schema.propel.twig", array(
357
            "module" => $module,
358
            "namespace" => preg_replace('/(\\\|\/)/', '', $module),
359
            "prefix" => preg_replace('/(\\\|\/)/', '', $module),
360
            "db" => $this->config->get("db_name"),
361
        ));
362
        return $this->writeTemplateToFile($schema,
363
            $mod_path . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . "schema.xml",
364
            $force);
365
    }
366
367
    /**
368
     * @param string $module
369
     * @param string $mod_path
370
     * @param boolean $force
371
     * @return boolean
372
     */
373
    private function generatePropertiesTemplate($module, $mod_path, $force = false)
374
    {
375
        $this->log->infoLog("Generamos la configuración de Propel");
376
        $build_properties = $this->tpl->dump("generator/build.properties.twig", array(
377
            "module" => $module,
378
            "host" => $this->config->get("db_host"),
379
            "port" => $this->config->get("db_port"),
380
            "user" => $this->config->get("db_user"),
381
            "pass" => $this->config->get("db_password"),
382
            "db" => $this->config->get("db_name"),
383
            "namespace" => preg_replace('/(\\\|\/)/', '', $module),
384
        ));
385
        return $this->writeTemplateToFile($build_properties,
386
            $mod_path . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . "propel.yml",
387
            $force);
388
    }
389
390
    /**
391
     * @param string $module
392
     * @param string $mod_path
393
     * @param boolean $force
394
     * @return boolean
395
     */
396
    private function generateIndexTemplate($module, $mod_path, $force = false)
397
    {
398
        //Generamos la plantilla de index
399
        $this->log->infoLog("Generamos una plantilla base por defecto");
400
        $index = $this->tpl->dump("generator/index.template.twig", array(
401
            "module" => $module,
402
        ));
403
        return $this->writeTemplateToFile($index,
404
            $mod_path . DIRECTORY_SEPARATOR . "Templates" . DIRECTORY_SEPARATOR . "index.html.twig",
405
            $force);
406
    }
407
408
    /**
409
     * Método que graba el contenido de una plantilla en un fichero
410
     * @param string $fileContent
411
     * @param string $filename
412
     * @param boolean $force
413
     * @return boolean
414
     */
415
    private function writeTemplateToFile($fileContent, $filename, $force = false)
416
    {
417
        $created = false;
418
        if ($force || !file_exists($filename)) {
419
            try {
420
                $this->cache->storeData($filename, $fileContent, Cache::TEXT, true);
421
                $created = true;
422
            } catch (\Exception $e) {
423
                $this->log->errorLog($e->getMessage());
424
            }
425
        } else {
426
            $this->log->errorLog($filename . _(' not exists or cant write'));
427
        }
428
        return $created;
429
    }
430
431
    /**
432
     * Create ApiBase
433
     * @param string $module
434
     * @param string $mod_path
435
     * @param string $api
436
     *
437
     * @return bool
438
     */
439
    private function createApiBaseFile($module, $mod_path, $api)
440
    {
441
        $class = preg_replace('/(\\\|\/)/', '', $module);
442
        $controller = $this->tpl->dump("generator/api.base.template.twig", array(
443
            "module" => $module,
444
            "api" => $api,
445
            "namespace" => preg_replace('/(\\\|\/)/', '\\', $module),
446
            "url" => preg_replace('/(\\\|\/)/', '/', $module),
447
            "class" => $class,
448
        ));
449
450
        return $this->writeTemplateToFile($controller,
451
            $mod_path . DIRECTORY_SEPARATOR . 'base' . DIRECTORY_SEPARATOR . "{$api}BaseApi.php", true);
452
    }
453
454
    /**
455
     * Create Api
456
     * @param string $module
457
     * @param string $mod_path
458
     * @param bool $force
459
     * @param string $api
460
     *
461
     * @return bool
462
     */
463
    private function createApi($module, $mod_path, $force, $api)
464
    {
465
        $class = preg_replace('/(\\\|\/)/', '', $module);
466
        $controller = $this->tpl->dump("generator/api.template.twig", array(
467
            "module" => $module,
468
            "api" => $api,
469
            "namespace" => preg_replace('/(\\\|\/)/', '\\', $module),
470
            "url" => preg_replace('/(\\\|\/)/', '/', $module),
471
            "class" => $class,
472
        ));
473
474
        return $this->writeTemplateToFile($controller, $mod_path . DIRECTORY_SEPARATOR . "{$api}.php", $force);
475
    }
476
477
    /**
478
     * Method that copy resources recursively
479
     * @param string $dest
480
     * @param boolean $force
481
     * @param $filename_path
482
     * @param boolean $debug
483
     */
484
    public static function copyResources($dest, $force, $filename_path, $debug)
485
    {
486
        if (file_exists($filename_path)) {
487
            $destfolder = basename($filename_path);
488
            if (!file_exists(WEB_DIR . $dest . DIRECTORY_SEPARATOR . $destfolder) || $debug || $force) {
489
                if (is_dir($filename_path)) {
490
                    self::copyr($filename_path, WEB_DIR . $dest . DIRECTORY_SEPARATOR . $destfolder);
491
                } else {
492
                    if (@copy($filename_path, WEB_DIR . $dest . DIRECTORY_SEPARATOR . $destfolder) === FALSE) {
493
                        throw new ConfigException("Can't copy " . $filename_path . " to " . WEB_DIR . $dest . DIRECTORY_SEPARATOR . $destfolder);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 145 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
494
                    }
495
                }
496
            }
497
        }
498
    }
499
500
    /**
501
     * Method that copy a resource
502
     * @param string $src
503
     * @param string $dst
504
     * @throws ConfigException
505
     */
506
    public static function copyr($src, $dst)
507
    {
508
        $dir = opendir($src);
509
        GeneratorHelper::createDir($dst);
510
        while (false !== ($file = readdir($dir))) {
511
            if (($file != '.') && ($file != '..')) {
512
                if (is_dir($src . '/' . $file)) {
513
                    self::copyr($src . '/' . $file, $dst . '/' . $file);
514
                } elseif (@copy($src . '/' . $file, $dst . '/' . $file) === false) {
515
                    throw new ConfigException("Can't copy " . $src . " to " . $dst);
516
                }
517
            }
518
        }
519
        closedir($dir);
520
    }
521
}
522