Passed
Push — master ( 0f6b23...8bef8c )
by Fabio
05:11
created

PradoCommandLineActiveRecordGen::performAction()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 7
eloc 11
c 2
b 2
f 0
nc 8
nop 1
dl 0
loc 15
rs 8.8333
1
<?php
2
3
/**
4
 * Prado command line developer tools.
5
 *
6
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
if (!isset($_SERVER['argv']) || php_sapi_name() !== 'cli') {
12
	die('Must be run from the command line');
13
}
14
15
// Locate composer's autoloader
16
if (file_exists($autoloader = realpath(__DIR__ . '/../vendor/autoload.php'))) {
17
	// if we are running inside a prado repo checkout, get out of bin/
18
	include($autoloader);
19
} elseif (file_exists($autoloader = realpath(__DIR__ . '/../../../autoload.php'))) {
20
	// if we are running from inside an application's vendor/ directory, get out of pradosoft/prado/bin/
21
	include($autoloader);
22
}
23
24
use Prado\TApplication;
25
use Prado\TShellApplication;
26
use Prado\Prado;
27
use Prado\Data\ActiveRecord\TActiveRecordConfig;
28
use Prado\Data\ActiveRecord\TActiveRecordManager;
29
30
//stub application class
31
class PradoShellApplication extends TShellApplication
32
{
33
}
34
35
restore_exception_handler();
36
37
//register action classes
38
PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLinePhpShell');
39
PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLineActiveRecordGen');
40
PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLineActiveRecordGenAll');
41
42
//run it;
43
PradoCommandLineInterpreter::getInstance()->run($_SERVER['argv']);
44
45
/**************** END CONFIGURATION **********************/
46
47
/**
48
 * PradoCommandLineInterpreter Class
49
 *
50
 * Command line interface, configures the action classes and dispatches the command actions.
51
 *
52
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
53
 * @since 3.0.5
54
 */
55
class PradoCommandLineInterpreter
56
{
57
	/**
58
	 * @var array command action classes
59
	 */
60
	protected $_actions = [];
61
62
	/**
63
	 * @param string $class action class name
64
	 */
65
	public function addActionClass($class)
66
	{
67
		$this->_actions[$class] = new $class;
68
	}
69
70
	/**
71
	 * @return PradoCommandLineInterpreter static instance
72
	 */
73
	public static function getInstance()
74
	{
75
		static $instance;
76
		if ($instance === null) {
77
			$instance = new self;
78
		}
79
		return $instance;
80
	}
81
82
	public static function printGreeting()
83
	{
84
		echo "Command line tools for Prado " . Prado::getVersion() . ".\n";
85
	}
86
87
	/**
88
	 * Dispatch the command line actions.
89
	 * @param array $args command line arguments
90
	 */
91
	public function run($args)
92
	{
93
		if (count($args) > 1) {
94
			array_shift($args);
95
		}
96
		$valid = false;
97
		foreach ($this->_actions as $class => $action) {
98
			if ($action->isValidAction($args)) {
99
				$valid |= $action->performAction($args);
100
				break;
101
			} else {
102
				$valid = false;
103
			}
104
		}
105
		if (!$valid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $valid of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
106
			$this->printHelp();
107
		}
108
	}
109
110
	/**
111
	 * Print command line help, default action.
112
	 */
113
	public function printHelp()
114
	{
115
		PradoCommandLineInterpreter::printGreeting();
116
117
		echo "usage: php prado-cli.php action <parameter> [optional]\n";
118
		echo "example: php prado-cli.php -c mysite\n\n";
119
		echo "actions:\n";
120
		foreach ($this->_actions as $action) {
121
			echo $action->renderHelp();
122
		}
123
	}
124
}
125
126
/**
127
 * Base class for command line actions.
128
 *
129
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
130
 * @since 3.0.5
131
 */
132
abstract class PradoCommandLineAction
133
{
134
	/**
135
	 * Execute the action.
136
	 * @param array $args command line parameters
137
	 * @return bool true if action was handled
138
	 */
139
	abstract public function performAction($args);
140
141
	/**
142
	 * Creates a directory and sets its mode
143
	 * @param string $dir directory name
144
	 * @param int $mask directory mode mask suitable for chmod()
145
	 */
146
	protected function createDirectory($dir, $mask)
147
	{
148
		if (!is_dir($dir)) {
149
			mkdir($dir);
150
			echo "creating $dir\n";
151
		}
152
		if (is_dir($dir)) {
153
			chmod($dir, $mask);
154
		}
155
	}
156
157
	/**
158
	 * Creates a file and fills it with content
159
	 * @param string $filename file name
160
	 * @param int $content file contents
161
	 */
162
	protected function createFile($filename, $content)
163
	{
164
		if (!is_file($filename)) {
165
			file_put_contents($filename, $content);
166
			echo "creating $filename\n";
167
		}
168
	}
169
170
	/**
171
	 * Checks if specified parameters are suitable for the specified action
172
	 * @param array $args parameters
173
	 * @return bool
174
	 */
175
	public function isValidAction($args)
176
	{
177
		return 0 == strcasecmp($args[0], $this->action) &&
178
			count($args) - 1 >= count($this->parameters);
179
	}
180
181
	/**
182
	 * @return string
183
	 */
184
	public function renderHelp()
185
	{
186
		$params = [];
187
		foreach ($this->parameters as $v) {
188
			$params[] = '<' . $v . '>';
189
		}
190
		$parameters = implode(' ', $params);
191
		$options = [];
192
		foreach ($this->optional as $v) {
193
			$options[] = '[' . $v . ']';
194
		}
195
		$optional = (strlen($parameters) ? ' ' : '') . implode(' ', $options);
196
		$description = '';
197
		foreach (explode("\n", wordwrap($this->description, 65)) as $line) {
198
			$description .= '    ' . $line . "\n";
199
		}
200
		return <<<EOD
201
  {$this->action} {$parameters}{$optional}
202
{$description}
203
204
EOD;
205
	}
206
207
	/**
208
	 * Initalize a Prado application inside the specified directory
209
	 * @param string $directory directory name
210
	 * @return false|TApplication
211
	 */
212
	protected function initializePradoApplication($directory)
213
	{
214
		$_SERVER['SCRIPT_FILENAME'] = $directory . '/index.php';
215
		$app_dir = realpath($directory . '/protected/');
216
		if ($app_dir !== false && is_dir($app_dir)) {
217
			if (Prado::getApplication() === null) {
218
				$app = new PradoShellApplication($app_dir);
219
				$app->run();
220
				$dir = substr(str_replace(realpath('./'), '', $app_dir), 1);
221
				PradoCommandLineInterpreter::printGreeting();
222
				echo '** Loaded PRADO appplication in directory "' . $dir . "\".\n";
223
			}
224
225
			return Prado::getApplication();
226
		} else {
227
			PradoCommandLineInterpreter::printGreeting();
228
			echo '+' . str_repeat('-', 77) . "+\n";
229
			echo '** Unable to load PRADO application in directory "' . $directory . "\".\n";
230
			echo '+' . str_repeat('-', 77) . "+\n";
231
		}
232
		return false;
233
	}
234
}
235
236
/**
237
 * Creates and run a Prado application in a PHP Shell.
238
 *
239
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
240
 * @since 3.0.5
241
 */
242
class PradoCommandLinePhpShell extends PradoCommandLineAction
243
{
244
	protected $action = 'shell';
245
	protected $parameters = [];
246
	protected $optional = ['directory'];
247
	protected $description = 'Runs a PHP interactive interpreter. Initializes the Prado application in the given [directory].';
248
249
	/**
250
	 * @param array $args parameters
251
	 * @return bool
252
	 */
253
	public function performAction($args)
254
	{
255
		if (count($args) > 1) {
256
			$this->initializePradoApplication($args[1]);
257
		}
258
259
		\Psy\debug([], Prado::getApplication());
260
		return true;
261
	}
262
}
263
264
/**
265
 * Create active record skeleton
266
 *
267
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
268
 * @since 3.1
269
 */
270
class PradoCommandLineActiveRecordGen extends PradoCommandLineAction
271
{
272
	protected $action = 'generate';
273
	protected $parameters = ['table', 'output'];
274
	protected $optional = ['directory', 'soap'];
275
	protected $description = 'Generate Active Record skeleton for <table> to <output> file using application.xml in [directory]. May also generate [soap] properties.';
276
	private $_soap = false;
277
278
	/**
279
	 * @param array $args parameters
280
	 * @return bool
281
	 */
282
	public function performAction($args)
283
	{
284
		$app_dir = count($args) > 3 ? $this->getAppDir($args[3]) : $this->getAppDir();
285
		$this->_soap = count($args) > 4;
286
		$this->_overwrite = count($args) > 5;
0 ignored issues
show
Bug Best Practice introduced by
The property _overwrite does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
287
		if ($app_dir !== false) {
288
			$config = $this->getActiveRecordConfig($app_dir);
289
			$output = $this->getOutputFile($app_dir, $args[2]);
290
			if (is_file($output) && !$this->_overwrite) {
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type false; however, parameter $filename of is_file() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

290
			if (is_file(/** @scrutinizer ignore-type */ $output) && !$this->_overwrite) {
Loading history...
291
				echo "** File $output already exists, skipping. \n";
292
			} elseif ($config !== false && $output !== false) {
0 ignored issues
show
introduced by
The condition $config !== false is always false.
Loading history...
293
				$this->generateActiveRecord($config, $args[1], $output);
294
			}
295
		}
296
		return true;
297
	}
298
299
	/**
300
	 * @param string $dir application directory
301
	 * @return false|string
302
	 */
303
	protected function getAppDir($dir = ".")
304
	{
305
		if (is_dir($dir)) {
306
			return realpath($dir);
307
		}
308
		if (false !== ($app_dir = realpath($dir . '/protected/')) && is_dir($app_dir)) {
309
			return $app_dir;
310
		}
311
		echo '** Unable to find directory "' . $dir . "\".\n";
312
		return false;
313
	}
314
315
	/**
316
	 * @param string $app_dir application directory
317
	 * @return false|string
318
	 */
319
	protected function getXmlFile($app_dir)
320
	{
321
		if (false !== ($xml = realpath($app_dir . '/application.xml')) && is_file($xml)) {
322
			return $xml;
323
		}
324
		if (false !== ($xml = realpath($app_dir . '/protected/application.xml')) && is_file($xml)) {
325
			return $xml;
326
		}
327
		echo '** Unable to find application.xml in ' . $app_dir . "\n";
328
		return false;
329
	}
330
331
	/**
332
	 * @param string $app_dir application directory
333
	 * @return false|string
334
	 */
335
	protected function getActiveRecordConfig($app_dir)
336
	{
337
		if (false === ($xml = $this->getXmlFile($app_dir))) {
338
			return false;
339
		}
340
		if (false !== ($app = $this->initializePradoApplication($app_dir))) {
341
			foreach ($app->getModules() as $module) {
342
				if ($module instanceof TActiveRecordConfig) {
343
					return $module;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $module returns the type Prado\Data\ActiveRecord\TActiveRecordConfig which is incompatible with the documented return type false|string.
Loading history...
344
				}
345
			}
346
			echo '** Unable to find TActiveRecordConfig module in ' . $xml . "\n";
347
		}
348
		return false;
349
	}
350
351
	/**
352
	 * @param string $app_dir application directory
353
	 * @param string $namespace output file in namespace format
354
	 * @return false|string
355
	 */
356
	protected function getOutputFile($app_dir, $namespace)
357
	{
358
		if (is_file($namespace) && strpos($namespace, $app_dir) === 0) {
359
			return $namespace;
360
		}
361
		$file = Prado::getPathOfNamespace($namespace, ".php");
362
		if ($file !== null && false !== ($path = realpath(dirname($file))) && is_dir($path)) {
363
			if (strpos($path, $app_dir) === 0) {
364
				return $file;
365
			}
366
		}
367
		echo '** Output file ' . $file . ' must be within directory ' . $app_dir . "\n";
368
		return false;
369
	}
370
371
	/**
372
	 * @param string $config database configuration
373
	 * @param string $tablename table name
374
	 * @param string $output output file name
375
	 * @return bool
376
	 */
377
	protected function generateActiveRecord($config, $tablename, $output)
378
	{
379
		$manager = TActiveRecordManager::getInstance();
380
		if ($manager->getDbConnection()) {
381
			$gateway = $manager->getRecordGateway();
382
			$tableInfo = $gateway->getTableInfo($manager->getDbConnection(), $tablename);
383
			if (count($tableInfo->getColumns()) === 0) {
384
				echo '** Unable to find table or view "' . $tablename . '" in "' . $manager->getDbConnection()->getConnectionString() . "\".\n";
385
				return false;
386
			} else {
387
				$properties = [];
388
				foreach ($tableInfo->getColumns() as $field => $column) {
389
					$properties[] = $this->generateProperty($field, $column);
390
				}
391
			}
392
393
			$classname = basename($output, '.php');
394
			$class = $this->generateClass($properties, $tablename, $classname);
395
			echo "  Writing class $classname to file $output\n";
396
			file_put_contents($output, $class);
397
		} else {
398
			echo '** Unable to connect to database with ConnectionID=\'' . $config->getConnectionID() . "'. Please check your settings in application.xml and ensure your database connection is set up first.\n";
399
		}
400
	}
401
402
	/**
403
	 * @param string $field php variable name
404
	 * @param string $column database column name
405
	 * @return string
406
	 */
407
	protected function generateProperty($field, $column)
408
	{
409
		$prop = '';
410
		$name = '$' . $field;
411
		$type = $column->getPHPType();
412
		if ($this->_soap) {
413
			$prop .= <<<EOD
414
415
	/**
416
	 * @var $type $name
417
	 * @soapproperty
418
	 */
419
420
EOD;
421
		}
422
		$prop .= "\tpublic $name;";
423
		return $prop;
424
	}
425
426
	/**
427
	 * @param array $properties class varibles
428
	 * @param string $tablename database table name
429
	 * @param string $class php class name
430
	 * @return string
431
	 */
432
	protected function generateClass($properties, $tablename, $class)
433
	{
434
		$props = implode("\n", $properties);
435
		$date = date('Y-m-d h:i:s');
436
		return <<<EOD
437
<?php
438
/**
439
 * Auto generated by prado-cli.php on $date.
440
 */
441
class $class extends TActiveRecord
442
{
443
	const TABLE='$tablename';
444
445
$props
446
447
	public static function finder(\$className=__CLASS__)
448
	{
449
		return parent::finder(\$className);
450
	}
451
}
452
453
EOD;
454
	}
455
}
456
457
/**
458
 * Create active record skeleton for all tables in DB and its relations
459
 *
460
 * @author Matthias Endres <me[at]me23[dot]de>
461
 * @author Daniel Sampedro Bello <darthdaniel85[at]gmail[dot]com>
462
 * @since 3.2
463
 */
464
class PradoCommandLineActiveRecordGenAll extends PradoCommandLineAction
465
{
466
	protected $action = 'generateAll';
467
	protected $parameters = ['output'];
468
	protected $optional = ['directory', 'soap', 'overwrite', 'prefix', 'postfix'];
469
	protected $description = "Generate Active Record skeleton for all Tables to <output> file using application.xml in [directory]. May also generate [soap] properties.\nGenerated Classes are named like the Table with optional [Prefix] and/or [Postfix]. [Overwrite] is used to overwrite existing Files.";
470
	private $_soap = false;
471
	private $_prefix = '';
472
	private $_postfix = '';
473
	private $_overwrite = false;
474
475
	/**
476
	 * @param array $args parameters
477
	 * @return bool
478
	 */
479
	public function performAction($args)
480
	{
481
		$app_dir = count($args) > 2 ? $this->getAppDir($args[2]) : $this->getAppDir();
482
		$this->_soap = count($args) > 3 ? ($args[3] == "soap" || $args[3] == "true" ? true : false) : false;
483
		$this->_overwrite = count($args) > 4 ? ($args[4] == "overwrite" || $args[4] == "true" ? true : false) : false;
484
		$this->_prefix = count($args) > 5 ? $args[5] : '';
485
		$this->_postfix = count($args) > 6 ? $args[6] : '';
486
487
		if ($app_dir !== false) {
488
			$config = $this->getActiveRecordConfig($app_dir);
489
490
			$manager = TActiveRecordManager::getInstance();
491
			$con = $manager->getDbConnection();
492
			$con->Active = true;
493
494
			switch ($con->getDriverName()) {
495
				case 'mysqli':
496
				case 'mysql':
497
					$command = $con->createCommand("SHOW TABLES");
498
					break;
499
				case 'sqlite': //sqlite 3
500
				case 'sqlite2': //sqlite 2
501
					$command = $con->createCommand("SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'");
502
					break;
503
				case 'pgsql':
504
				case 'mssql': // Mssql driver on windows hosts
505
				case 'sqlsrv': // sqlsrv driver on windows hosts
506
				case 'dblib': // dblib drivers on linux (and maybe others os) hosts
507
				case 'oci':
508
//				case 'ibm':
509
				default:
510
					echo "\n    Sorry, generateAll is not implemented for " . $con->getDriverName() . "\n";
511
512
			   }
513
514
			$dataReader = $command->query();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $command does not seem to be defined for all execution paths leading up to this point.
Loading history...
515
			$dataReader->bindColumn(1, $table);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $table does not exist. Did you maybe mean $tables?
Loading history...
516
			$tables = [];
517
			while ($dataReader->read() !== false) {
518
				$tables[] = $table;
519
			}
520
			$con->Active = false;
521
			foreach ($tables as $key => $table) {
522
				$output = $args[1] . "." . $this->_prefix . ucfirst($table) . $this->_postfix;
523
				if ($config !== false && $output !== false) {
524
					$this->generate("generate " . $table . " " . $output . " " . $this->_soap . " " . $this->_overwrite);
525
				}
526
			}
527
		}
528
		return true;
529
	}
530
531
	/**
532
	 * @param string $l commandline
533
	 */
534
	public function generate($l)
535
	{
536
		$input = explode(" ", trim($l));
537
		if (count($input) > 2) {
538
			$app_dir = '.';
539
			if (Prado::getApplication() !== null) {
540
				$app_dir = dirname(Prado::getApplication()->getBasePath());
541
			}
542
			$args = [$input[0], $input[1], $input[2], $app_dir];
543
			if (count($input) > 3) {
544
				$args[] = 'soap';
545
			}
546
			if (count($input) > 4) {
547
				$args[] = 'overwrite';
548
			}
549
			$cmd = new PradoCommandLineActiveRecordGen;
550
			$cmd->performAction($args);
551
		} else {
552
			echo "\n    Usage: generate table_name Application.pages.RecordClassName\n";
553
		}
554
	}
555
556
	/**
557
	 * @param string $dir application directory
558
	 * @return false|string
559
	 */
560
	protected function getAppDir($dir = ".")
561
	{
562
		if (is_dir($dir)) {
563
			return realpath($dir);
564
		}
565
		if (false !== ($app_dir = realpath($dir . '/protected/')) && is_dir($app_dir)) {
566
			return $app_dir;
567
		}
568
		echo '** Unable to find directory "' . $dir . "\".\n";
569
		return false;
570
	}
571
572
	/**
573
	 * @param string $app_dir application directory
574
	 * @return false|string
575
	 */
576
	protected function getXmlFile($app_dir)
577
	{
578
		if (false !== ($xml = realpath($app_dir . '/application.xml')) && is_file($xml)) {
579
			return $xml;
580
		}
581
		if (false !== ($xml = realpath($app_dir . '/protected/application.xml')) && is_file($xml)) {
582
			return $xml;
583
		}
584
		echo '** Unable to find application.xml in ' . $app_dir . "\n";
585
		return false;
586
	}
587
588
	/**
589
	 * @param string $app_dir application directory
590
	 * @return false|string
591
	 */
592
	protected function getActiveRecordConfig($app_dir)
593
	{
594
		if (false === ($xml = $this->getXmlFile($app_dir))) {
595
			return false;
596
		}
597
		if (false !== ($app = $this->initializePradoApplication($app_dir))) {
598
			foreach ($app->getModules() as $module) {
599
				if ($module instanceof TActiveRecordConfig) {
600
					return $module;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $module returns the type Prado\Data\ActiveRecord\TActiveRecordConfig which is incompatible with the documented return type false|string.
Loading history...
601
				}
602
			}
603
			echo '** Unable to find TActiveRecordConfig module in ' . $xml . "\n";
604
		}
605
		return false;
606
	}
607
608
	/**
609
	 * @param string $app_dir application directory
610
	 * @param string $namespace output file in namespace format
611
	 * @return false|string
612
	 */
613
	protected function getOutputFile($app_dir, $namespace)
614
	{
615
		if (is_file($namespace) && strpos($namespace, $app_dir) === 0) {
616
			return $namespace;
617
		}
618
		$file = Prado::getPathOfNamespace($namespace, "");
619
		if ($file !== null && false !== ($path = realpath(dirname($file))) && is_dir($path)) {
620
			if (strpos($path, $app_dir) === 0) {
621
				return $file;
622
			}
623
		}
624
		echo '** Output file ' . $file . ' must be within directory ' . $app_dir . "\n";
625
		return false;
626
	}
627
}
628