GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

BaseGenerator   F
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 661
Duplicated Lines 4.54 %

Coupling/Cohesion

Components 4
Dependencies 3

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 74
lcom 4
cbo 3
dl 30
loc 661
rs 2.0903
c 1
b 1
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
B sandbox() 0 24 4
C normalizePath() 0 48 8
B __construct() 0 25 4
run() 0 1 ?
C createFile() 8 47 7
A createDirectory() 0 16 3
A copyFile() 0 13 2
A copyTemplate() 0 11 2
C injectIntoFile() 8 49 11
A generate() 0 17 3
A route() 0 15 2
A readme() 0 17 2
B render() 0 43 5
A setupThemer() 0 18 3
A loadUIKit() 0 13 2
A determineOutputPath() 0 11 2
B locateGenerator() 14 26 5
C stringify() 0 52 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like BaseGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseGenerator, and based on these observations, apply Extract Interface, too.

1
<?php namespace Myth\Forge;
2
/**
3
 * Sprint
4
 *
5
 * A set of power tools to enhance the CodeIgniter framework and provide consistent workflow.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package     Sprint
26
 * @author      Lonnie Ezell
27
 * @copyright   Copyright 2014-2015, New Myth Media, LLC (http://newmythmedia.com)
28
 * @license     http://opensource.org/licenses/MIT  (MIT)
29
 * @link        http://sprintphp.com
30
 * @since       Version 1.0
31
 */
32
33
use Myth\Controllers\CLIController;
34
use Myth\CLI;
35
36
/**
37
 * Class BaseGenerator
38
 * 
39
 * Builds on top of the features of the CLIController to provide
40
 * handy methods used for generating boilerplate code.
41
 *
42
 * @package Myth\Forge
43
 */
44
abstract class BaseGenerator extends CLIController {
45
46
    /**
47
     * Instance of the active themer.
48
     * @var null
49
     */
50
    protected $themer = null;
51
52
    protected $gen_path = null;
53
54
	/**
55
	 * The name of the module being used, if any.
56
	 * @var null
57
	 */
58
	protected $module = null;
59
	protected $module_path = null;
60
61
	protected $quiet = false;
62
63
	protected $overwrite = false;
64
65
    //--------------------------------------------------------------------
66
67
    public function __construct()
68
    {
69
        parent::__construct();
70
71
        $this->load->config('forge');
72
73
	    // Detect if we're genning in a module
74
	    if ($module = CLI::option('module'))
75
	    {
76
		    $this->module = $module;
77
78
		    $folders = config_item('modules_locations');
79
80
		    if (is_array($folders))
81
		    {
82
			    $this->module_path = $folders[0] . strtolower($module) .'/';
83
		    }
84
		}
85
86
	    // Should we overwrite files?
87
	    if (CLI::option('overwrite'))
88
	    {
89
		    $this->overwrite = true;
90
	    }
91
    }
92
93
    //--------------------------------------------------------------------
94
95
96
    /**
97
     * The method called by the main generator script. This must be
98
     * overridden by child classes to implement the actual logic used.
99
     *
100
     * @param array $segments
101
     * @param bool  $quiet      If true, models should accept default values.
102
     * @return mixed
103
     */
104
    abstract function run($segments=[], $quiet=false);
105
106
    //--------------------------------------------------------------------
107
108
109
    /**
110
     * Creates a file at the specified path with the given contents.
111
     *
112
     * @param $path
113
     * @param null $contents
114
     *
115
     * @return bool
116
     */
117
    public function createFile($path, $contents=null, $overwrite=false, $perms=0644)
118
    {
119
	    $path = $this->sandbox($path);
120
121
	    $file_exists = is_file($path);
122
123
        // Does file already exist?
124
        if ($file_exists)
125
        {
126
	        if (! $overwrite) {
127
		        CLI::write( CLI::color("\t". strtolower(lang('exists')) .": ", 'blue') . str_replace(APPPATH, '', $path ) );
128
		        return true;
129
	        }
130
131
	        unlink($path);
132
        }
133
134
	    // Do we need to create the directory?
135
	    $segments = explode('/', $path);
136
		array_pop($segments);
137
		$folder = implode('/', $segments);
138
139
	    if (! is_dir($folder))
140
	    {
141
		    $this->createDirectory($folder);
142
	    }
143
144
        get_instance()->load->helper('file');
145
146
        if (! write_file($path, $contents))
147
        {
148
            throw new \RuntimeException( sprintf( lang('errors.writing_file'),  $path) );
149
        }
150
151
        chmod($path, $perms);
152
153 View Code Duplication
	    if ($overwrite && $file_exists)
154
	    {
155
		    CLI::write( CLI::color("\t". strtolower( lang('overwrote') ) ." ", 'light_red') . str_replace(APPPATH, '', $path ) );
156
	    }
157
	    else
158
	    {
159
		    CLI::write( CLI::color("\t". strtolower( lang('created') ) ." ", 'yellow') . str_replace(APPPATH, '', $path ) );
160
	    }
161
162
        return $this;
163
    }
164
    
165
    //--------------------------------------------------------------------
166
167
    /**
168
     * Creates a new directory at the specified path.
169
     *
170
     * @param $path
171
     * @param int|string $perms
172
     *
173
     * @return bool
174
     */
175
    public function createDirectory($path, $perms=0755)
176
    {
177
	    $path = $this->sandbox($path);
178
179
        if (is_dir($path))
180
        {
181
            return $this;
182
        }
183
184
        if (! mkdir($path, $perms, true) )
185
        {
186
            throw new \RuntimeException( sprintf( lang('errors.creating_dir'), $path) );
187
        }
188
189
        return $this;
190
    }
191
192
    //--------------------------------------------------------------------
193
194
    /**
195
     * Copies a file from the current template group to the destination.
196
     *
197
     * @param $source
198
     * @param $destination
199
     * @param bool $overwrite
200
     *
201
     * @return bool
202
     */
203
    public function copyFile($source, $destination, $overwrite=false)
204
    {
205
	    $source = $this->sandbox($source);
206
207
	    if (! file_exists($source))
208
	    {
209
		    return null;
210
	    }
211
212
	    $content = file_get_contents($source);
213
214
	    return $this->createFile($destination, $content, $overwrite);
215
    }
216
217
    //--------------------------------------------------------------------
218
219
    /**
220
     * Attempts to locate a template within the current template group,
221
     * parses it with the passed in data, and writes to the new location.
222
     *
223
     * @param $template
224
     * @param $destination
225
     * @param array $data
226
     * @param bool $overwrite
227
     *
228
     * @return $this
229
     */
230
    public function copyTemplate($template, $destination, $data=[], $overwrite=false)
231
    {
232
	    if (! is_array($data))
233
	    {
234
		    $data = array($data);
235
	    }
236
237
        $content = $this->render($template, $data);
238
239
        return $this->createFile($destination, $content, $overwrite);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->createFile($desti... $content, $overwrite); of type boolean|Myth\Forge\BaseGenerator adds the type boolean to the return on line 239 which is incompatible with the return type documented by Myth\Forge\BaseGenerator::copyTemplate of type Myth\Forge\BaseGenerator.
Loading history...
240
    }
241
242
    //--------------------------------------------------------------------
243
244
245
    /**
246
     * Injects a block of code into an existing file. Using options
247
     * you can specify where the code should be inserted. Available options
248
     * are:
249
     *      prepend         - Place at beginning of file
250
     *      append          - Place at end of file
251
     *      before  => ''   - Insert just prior to this line of text (don't forget the line ending "\n")
252
     *      after   => ''   - Insert just after this line of text (don't forget the line ending "\n")
253
     *      replace => ''   - a simple string to be replaced. All locations will be replaced.
254
     *      regex   => ''   - a pregex pattern to use to replace all locations.
255
     *
256
     * @param $path
257
     * @param $content
258
     * @param array $options
259
     *
260
     * @return $this
261
     */
262
    public function injectIntoFile($path, $content, $options='append')
263
    {
264
        $kit = new FileKit();
265
266
        if (is_string($options))
267
        {
268
            $action = $options;
269
        }
270
        else if (is_array($options) && count($options))
271
        {
272
            $keys = array_keys($options);
273
            $action = array_shift( $keys );
274
            $param = $options[$action];
275
        }
276
277
        switch ( strtolower($action) )
278
        {
279
            case 'prepend':
280
                $success = $kit->prepend($path, $content);
281
                break;
282
            case 'before':
283
                $success = $kit->before($path, $param, $content);
284
                break;
285
            case 'after':
286
                $success = $kit->after($path, $param, $content);
287
                break;
288
            case 'replace':
289
                $success = $kit->replaceIn($path, $param, $content);
290
                break;
291
            case 'regex':
292
                $success = $kit->replaceWithRegex($path, $param, $content);
293
                break;
294
            case 'append':
295
            default:
296
                $success = $kit->append($path, $content);
297
                break;
298
        }
299
300 View Code Duplication
        if ($success)
301
        {
302
            CLI::write( CLI::color("\t". strtolower( lang('modified') ) ." ", 'cyan') . str_replace(APPPATH, '', $path ) );
303
        }
304
        else
305
        {
306
            CLI::write( CLI::color("\t". strtolower( lang('error') ) ." ", 'light_red') . str_replace(APPPATH, '', $path ) );
307
        }
308
309
        return $this;
310
    }
311
    
312
    //--------------------------------------------------------------------
313
314
	/**
315
	 * Runs another generator. The first parameter is the name of the
316
	 * generator to run. The $options parameter is added to the existing
317
	 * options passed to this command, and then they are passed along to
318
	 * the next command.
319
	 *
320
	 * @param $command
321
	 * @param null $options
322
	 */
323
    public function generate($command, $options = '', $quiet=false)
324
    {
325
		$orig_options = CLI::optionString();
326
	    $options = $orig_options .' '. $options;
327
328
	    if ($quiet === true)
329
	    {
330
		    $options .= ' -quiet';
331
	    }
332
333
	    if ($this->overwrite === true)
334
	    {
335
		    $options .= ' -overwrite';
336
	    }
337
338
	    passthru( "php sprint forge {$command} {$options}" );
339
    }
340
341
    //--------------------------------------------------------------------
342
343
    /**
344
     * Adds a new route to the application's route file.
345
     *
346
     * @param $left
347
     * @param $right
348
     *
349
     * @return \Myth\Forge\BaseGenerator
350
     */
351
    public function route($left, $right, $options=[], $method='any')
352
    {
353
        $option_str = '[';
354
355
        foreach ($options as $key => $value)
356
        {
357
            $option_str .= "";
358
        }
359
360
        $option_str .= ']';
361
362
        $content = "\$routes->{$method}('{$left}', '{$right}', {$option_str});\n";
363
364
        return $this->injectIntoFile(APPPATH .'config/routes.php', $content, ['after' => "// Auto-generated routes go here\n"]);
365
    }
366
    
367
    //--------------------------------------------------------------------
368
369
    /**
370
     * Outputs the contents of the file in the template's source path.
371
     */
372
    public function readme($file='readme.txt')
373
    {
374
	    $name = str_replace('Generator', '', get_class($this));
375
376
	    $path = $this->locateGenerator($name);
377
378
        if (! file_exists($path . $file))
379
        {
380
            CLI::error(sprintf( lang('forge.cant_find_readme'), $file) );
381
        }
382
383
	    $contents = file_get_contents($path . $file);
384
385
        CLI::new_line(2);
386
        CLI::write( CLI::wrap($contents), 'green' );
387
        CLI::new_line();
388
    }
389
390
    //--------------------------------------------------------------------
391
392
    /**
393
     * Renders a single generator template. The file must be in a folder
394
     * under the template group named the same as $this->generator_name.
395
     * The file must have a '.tpl.php' file extension.
396
     *
397
     * @param $template_name
398
     * @param array $data
399
     *
400
     * @return string The rendered template
401
     */
402
    public function render($template_name, $data=[], $folder=null)
403
    {
404
        if (empty($this->themer))
405
        {
406
            $this->setupThemer();
407
        }
408
409
	    $data['uikit'] = $this->loadUIKit();
410
411
        $output = null;
412
413
	    $view = $template_name .'.tpl';
414
415
        $groups = config_item('forge.collections');
416
417
	    $name = str_replace('Generator', '', get_class($this) );
418
419
        foreach ($groups as $group => $path)
420
        {
421
	        $path = rtrim($path, '/ ') .'/';
422
	        $folders = scandir($path);
423
424
	        if (! $i = array_search(ucfirst($name), $folders))
425
	        {
426
		        continue;
427
	        }
428
429
	        $view = $folders[$i] . '/'. $view;
430
431
            if (realpath($path . $view .'.php'))
432
            {
433
                $output = $this->themer->display($group .':'. $view, $data);
434
                break;
435
            }
436
        }
437
438
	    // To allow for including any PHP code in the templates,
439
	    // replace any '@php' and '@=' tags with their correct PHP syntax.
440
	    $output = str_replace('@php', '<?php', $output);
441
	    $output = str_replace('@=', '<?=', $output);
442
443
        return $output;
444
    }
445
446
    //--------------------------------------------------------------------
447
448
	/**
449
	 * Forces a path to exist within the current application's folder.
450
	 * This means it must be in APPPATH,  or FCPATH. If it's not
451
	 * the path will be forced within the APPPATH, possibly creating a
452
	 * ugly set of folders, but keeping the user from accidentally running
453
	 * an evil generator that might have done bad things to their system.
454
	 *
455
	 * @param $path
456
	 *
457
	 * @return string
458
	 */
459
	public function sandbox($path)
460
	{
461
        $path = $this->normalizePath($path);
462
463
		// If it's writing to BASEPATH - FIX IT
464
		if (strpos($path, $this->normalizePath(BASEPATH) ) === 0)
465
		{
466
			return APPPATH . $path;
467
		}
468
469
		// Exact match for FCPATH?
470
		if (strpos($path, $this->normalizePath(FCPATH) ) === 0)
471
		{
472
			return $path;
473
		}
474
475
		// Exact match for APPPATH?
476
		if (strpos($path, $this->normalizePath(APPPATH) ) === 0)
477
		{
478
			return $path;
479
		}
480
481
	    return APPPATH . $path;
482
	}
483
484
	//--------------------------------------------------------------------
485
486
487
488
    //--------------------------------------------------------------------
489
    // Private Methods
490
    //--------------------------------------------------------------------
491
492
    protected function setupThemer()
493
    {
494
        $themer_name = config_item('forge.themer');
495
496
        if (! $themer_name)
497
        {
498
            throw new \RuntimeException( lang('forge.no_themer') );
499
        }
500
501
        $this->themer = new $themer_name( get_instance() );
502
503
        // Register our paths with the themer
504
        $paths = config_item('forge.collections');
505
506
        foreach ($paths as $key => $path) {
507
            $this->themer->addThemePath($key, $path);
508
        }
509
    }
510
511
    //--------------------------------------------------------------------
512
513
	public function loadUIKit()
514
	{
515
		$kit_name = config_item('theme.uikit');
516
517
		if (! $kit_name)
518
		{
519
			throw new \RuntimeException( lang('forge.no_uikit') );
520
		}
521
522
		$uikit = new $kit_name();
523
524
	    return $uikit;
525
	}
526
527
	//--------------------------------------------------------------------
528
529
530
	protected function determineOutputPath($folder='')
531
	{
532
		$path = APPPATH . $folder;
533
534
		if (! empty($this->module_path))
535
		{
536
			$path = $this->module_path . $folder;
537
		}
538
539
		return rtrim($path, '/ ') .'/';
540
	}
541
542
	//--------------------------------------------------------------------
543
544
	/**
545
	 * Scans through the collections for the folder for this generator.
546
	 *
547
	 * @param $name
548
	 *
549
	 * @return null|string
550
	 */
551
	protected function locateGenerator($name)
552
	{
553
		$collections = config_item('forge.collections');
554
555
		if (! is_array($collections) || ! count($collections) )
556
		{
557
			return CLI::error( lang('forge.no_collections') );
558
		}
559
560 View Code Duplication
		foreach ($collections as $alias => $path)
561
		{
562
			$path = rtrim($path, '/ ') .'/';
563
			$folders = scandir($path);
564
565
			if (! $i = array_search(ucfirst($name), $folders))
566
			{
567
				continue;
568
			}
569
570
			$this->gen_path = $path . $folders[$i] .'/';
571
572
			return $this->gen_path;
573
		}
574
575
		return null;
576
	}
577
578
	//--------------------------------------------------------------------
579
580
	/**
581
	 * Converts an array to a string representation.
582
	 *
583
	 * @param $array
584
	 *
585
	 * @return string
586
	 */
587
	protected function stringify($array, $depth=0)
588
	{
589
		if (! is_array($array))
590
		{
591
			return '';
592
		}
593
594
595
596
		$str = '';
597
598
		if ($depth > 1)
599
		{
600
			$str .= str_repeat("\t", $depth);
601
		}
602
603
		$depth++;
604
605
		$str .= "[\n";
606
607
		foreach ($array as $key => $value)
608
		{
609
			$str .= str_repeat("\t", $depth +1);
610
611
			if (! is_numeric($key))
612
			{
613
				$str .= "'{$key}' => ";
614
			}
615
616
			if (is_array($value))
617
			{
618
				$str .= $this->stringify($value, $depth);
619
			}
620
			else if (is_bool($value))
621
			{
622
				$b = $value === true ? 'true' : 'false';
623
				$str .= "{$b},\n";
624
			}
625
			else if (is_numeric($value))
626
			{
627
				$str .= "{$value},\n";
628
			}
629
			else
630
			{
631
				$str .= "'{$value}',\n";
632
			}
633
		}
634
635
		$str .= str_repeat("\t", $depth) ."],";
636
637
		return $str;
638
	}
639
640
	//--------------------------------------------------------------------
641
642
    /**
643
     * Normalizes a path and cleans it up for healthy use within
644
     * realpath() and helps to mitigate changes between Windows and *nix
645
     * operating systems.
646
     *
647
     * Found at http://php.net/manual/en/function.realpath.php#112367
648
     *
649
     * @param $path
650
     *
651
     * @return string
652
     */
653
    protected function normalizePath($path)
654
    {
655
        // Array to build a new path from the good parts
656
        $parts = array();
657
658
        // Replace backslashes with forward slashes
659
        $path = str_replace('\\', '/', $path);
660
661
        // Combine multiple slashes into a single slash
662
        $path = preg_replace('/\/+/', '/', $path);
663
664
        // Collect path segments
665
        $segments = explode('/', $path);
666
667
        // Initialize testing variable
668
        $test = '';
669
670
        foreach($segments as $segment)
671
        {
672
            if($segment != '.')
673
            {
674
                $test = array_pop($parts);
675
676
                if(is_null($test))
677
                {
678
                    $parts[] = $segment;
679
                }
680
                else if ($segment == '..')
681
                {
682
                    if ($test == '..')
683
                    {
684
                        $parts[] = $test;
685
                    }
686
687
                    if ($test == '..' || $test == '')
688
                    {
689
                        $parts[] = $segment;
690
                    }
691
                }
692
                else
693
                {
694
                    $parts[] = $test;
695
                    $parts[] = $segment;
696
                }
697
            }
698
        }
699
        return implode('/', $parts);
700
    }
701
702
    //--------------------------------------------------------------------
703
704
}
705