PageRenderTime 61ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/myth/_generators/Api/ApiGenerator.php

https://gitlab.com/digitalpoetry/exceptionally-timed
PHP | 519 lines | 292 code | 87 blank | 140 comment | 34 complexity | dbcc7bd6949d0e07ad3c5a5c2e8d6d82 MD5 | raw file
  1. <?php
  2. /**
  3. * Code All The Things!
  4. *
  5. * A project kickstarter based on the Sprint & CodeIgniter frameworks.
  6. *
  7. * @package DigitalPoetry\CATT\Forge
  8. * @author Jesse LaReaux <jlareaux@gmail.com>
  9. * @copyright Copyright (c) 2016, DigitalPoetry (http://digitalpoetry.studio/).
  10. * @license http://opensource.org/licenses/MIT MIT License
  11. * @link http://codeallthethings.xyz
  12. * @version 0.1.0 Shiny Things
  13. * @filesource
  14. */
  15. use Myth\CLI as CLI;
  16. /**
  17. * Class ApiGenerator
  18. *
  19. * Asks the user a series of questions and creates any needed files based
  20. * upon the authentication type.
  21. *
  22. * Auth Type | Files Created
  23. * ----------+----------------------------------
  24. * basic none
  25. * digest migration, modifies adds event to config
  26. * keys migration, modifies adds event to config
  27. */
  28. class ApiGenerator extends \Myth\Forge\BaseGenerator {
  29. protected $auth_type;
  30. protected $destination;
  31. //--------------------------------------------------------------------
  32. /**
  33. * @param array $segments
  34. * @param bool $quiet
  35. */
  36. public function run($segments=[], $quiet=false)
  37. {
  38. // Show an index?
  39. if (empty($segments[0]))
  40. {
  41. return $this->displayIndex();
  42. }
  43. $action = array_shift($segments);
  44. switch ($action)
  45. {
  46. case 'install':
  47. $this->install();
  48. break;
  49. case 'scaffold':
  50. $this->scaffold($segments, $quiet);
  51. break;
  52. default:
  53. if (! $quiet)
  54. {
  55. CLI::write('Nothing to do.', 'green');
  56. }
  57. break;
  58. }
  59. }
  60. private function displayIndex()
  61. {
  62. CLI::write("\nAvailable API Generators");
  63. CLI::write( CLI::color('install', 'yellow') .' install Creates migrations, alters config file for desired authentication' );
  64. CLI::write( CLI::color('scaffold', 'yellow') .' scaffold <name> Creates basic CRUD controller, routes, and optionally API Blueprint files' );
  65. }
  66. /**
  67. * Scaffold Methods
  68. */
  69. /**
  70. * Quickly creates boilerplate code for a new API resource.
  71. *
  72. * @param $segments
  73. * @param $quiet
  74. */
  75. private function scaffold( $segments, $quiet )
  76. {
  77. $this->load->helper('inflector');
  78. /*
  79. * Gather needed info
  80. */
  81. // Resource name
  82. list($resource, $version, $blueprint, $model) = array_pad($segments, 4, null);
  83. if (empty($resource))
  84. {
  85. $resource = strtolower( CLI::prompt('Resource name') );
  86. }
  87. // Resources should be plural, but we'll need a singular name also.
  88. $resource_single = singular($resource);
  89. $resource_plural = plural($resource);
  90. // API version
  91. if (empty($version))
  92. {
  93. $version = $this->askVersion();
  94. }
  95. if (empty($blueprint))
  96. {
  97. $blueprint = $this->askBlueprint();
  98. }
  99. if (empty($model))
  100. {
  101. $model = $this->detectModel( $resource_single );
  102. }
  103. /*
  104. * Start Building
  105. */
  106. $this->destination = APPPATH .'controllers/';
  107. if (! empty($version))
  108. {
  109. $this->destination .= $version .'/';
  110. }
  111. // Controller
  112. if (! $this->createController($resource_single, $resource_plural, $version) )
  113. {
  114. CLI::error('Unknown error creating Controller.');
  115. exit(1);
  116. }
  117. // Language Files
  118. if (! $this->createLanguage($resource_single, $resource_plural, $version, $model) )
  119. {
  120. CLI::error('Unknown error creating Language File.');
  121. exit(1);
  122. }
  123. // Blueprint File
  124. if ($blueprint)
  125. {
  126. if (! $this->createBlueprint($resource_single, $resource_plural, $version, $model) )
  127. {
  128. CLI::error('Unknown error creating Blueprint file.');
  129. exit(1);
  130. }
  131. }
  132. // Modify Routes
  133. if (! $this->addRoutes($resource_plural, $version) )
  134. {
  135. CLI::error('Unknown error adding Routes.');
  136. exit(1);
  137. }
  138. }
  139. /**
  140. * Gets the version number/folder name for the controller to live.
  141. *
  142. * @return string
  143. */
  144. private function askVersion()
  145. {
  146. CLI::write("\nVersions are simply controller folders (i.e. controllers/v1/...)");
  147. CLI::write("(Enter 'na' for no version folder)");
  148. $version = strtolower( CLI::prompt('Version name', 'v1') );
  149. $version = $version == 'na' ? '' : $version;
  150. return $version;
  151. }
  152. /**
  153. * Should we create an API Blueprint file for this controller?
  154. *
  155. * @return bool
  156. */
  157. private function askBlueprint()
  158. {
  159. CLI::write("\nAPI Blueprint is a plain-text API documentation starter.");
  160. CLI::write("See: ". CLI::color('https://apiblueprint.org', 'light_blue'));
  161. $make_blueprint = CLI::prompt('Create Blueprint file?', ['y', 'n']);
  162. return $make_blueprint == 'y' ? true : false;
  163. }
  164. private function detectModel($name)
  165. {
  166. $model_name = ucfirst($name) .'_model.php';
  167. if (! file_exists(APPPATH .'models/'. $model_name))
  168. {
  169. CLI::write("\nUnable to find model named: {$model_name}");
  170. $model_name = CLI::prompt('Model filename');
  171. }
  172. else
  173. {
  174. CLI::write("Using model: ". CLI::color($model_name, 'yellow') );
  175. }
  176. return $model_name;
  177. }
  178. /**
  179. * Creates the new APIController - based resource with basic CRUD.
  180. *
  181. * @param $single
  182. * @param $plural
  183. * @param $version
  184. *
  185. * @return $this
  186. * @internal param $name
  187. *
  188. */
  189. private function createController( $single, $plural, $version )
  190. {
  191. $data = [
  192. 'today' => date( 'Y-m-d H:ia' ),
  193. 'model_name' => strtolower($single) .'_model',
  194. 'plural' => $plural,
  195. 'single' => $single,
  196. 'class_name' => ucfirst($plural),
  197. 'version' => $version
  198. ];
  199. $destination = $this->destination . ucfirst($plural) .'.php';
  200. return $this->copyTemplate( 'controller', $destination, $data, $this->overwrite );
  201. }
  202. /**
  203. * Creates the language file to accompany the controller.
  204. *
  205. * @param $single
  206. * @param $plural
  207. * @param $version
  208. *
  209. * @return $this
  210. */
  211. private function createLanguage( $single, $plural, $version )
  212. {
  213. $data = [
  214. 'plural' => $plural,
  215. 'single' => $single,
  216. 'uc_single' => ucfirst($single),
  217. 'uc_plural' => ucfirst($plural),
  218. ];
  219. $destination = APPPATH ."language/english/api_{$plural}_lang.php";
  220. return $this->copyTemplate( 'lang', $destination, $data, $this->overwrite );
  221. }
  222. /**
  223. * Creates the API Blueprint file for that resource in
  224. * APPPATH/docs/api
  225. *
  226. * @param $single
  227. * @param $plural
  228. * @param $version
  229. * @param $model
  230. *
  231. * @return $this
  232. * @internal param $name
  233. *
  234. */
  235. private function createBlueprint( $single, $plural, $version, $model )
  236. {
  237. $version = rtrim($version, '/');
  238. if (! empty($version))
  239. {
  240. $version .= '/';
  241. }
  242. // Load the model so we can use the correct table to use
  243. $model = strtolower( str_replace('.php', '', $model) );
  244. $this->load->model( $model, $model, true );
  245. $obj = $this->formatObject($model);
  246. $data = [
  247. 'plural' => $plural,
  248. 'single' => $single,
  249. 'uc_single' => ucfirst($single),
  250. 'uc_plural' => ucfirst($plural),
  251. 'version' => $version,
  252. 'site_url' => site_url(),
  253. 'formatted' => $obj
  254. ];
  255. $destination = APPPATH .'docs/api/'. $version . $plural .'.md';
  256. $success = $this->copyTemplate( 'blueprint', $destination, $data, $this->overwrite );
  257. if (! $this->updateTOC($plural, $version))
  258. {
  259. CLI::write("\tUnable to modify the toc file.", 'light_red');
  260. }
  261. return $success;
  262. }
  263. /**
  264. * Modifies the _toc.ini file (or creates) for the specified Blueprint docs.
  265. *
  266. * @param $plural
  267. * @param $version
  268. *
  269. * @return $this|bool
  270. */
  271. private function updateTOC( $plural, $version )
  272. {
  273. $path = APPPATH .'docs/_toc.ini';
  274. // We need a TOC file to exist if we're going to modify it silly.
  275. if (! file_exists($path))
  276. {
  277. if (! $this->copyTemplate('toc', $path))
  278. {
  279. return false;
  280. }
  281. }
  282. $version = rtrim($version, '/ ');
  283. if (! empty($version))
  284. {
  285. $version .= '/';
  286. }
  287. $ucname = ucfirst($plural);
  288. $content = "api/{$version}{$plural}\t= {$ucname}\n";
  289. return $this->injectIntoFile($path, $content);
  290. }
  291. /**
  292. * Creates a generic representation of the object from the database
  293. * table.
  294. *
  295. * @param $model
  296. *
  297. * @return string
  298. */
  299. private function formatObject( $model )
  300. {
  301. $fields = $this->db->field_data($this->$model->table_name);
  302. $obj = '';
  303. foreach ($fields as $field)
  304. {
  305. $obj .= "\"$field->name\": ";
  306. switch ($field->type)
  307. {
  308. case 'tinyint':
  309. $obj .= "0,\n";
  310. break;
  311. case 'int':
  312. case 'bigint':
  313. $obj .= "1234,\n";
  314. break;
  315. case 'float':
  316. case 'double':
  317. $obj .= "123.45,\n";
  318. break;
  319. case 'date':
  320. $obj .= date("\"Y-m-d\",\n");
  321. break;
  322. case 'datetime':
  323. $obj .= date("\"Y-m-d H:i:s\",\n");
  324. break;
  325. }
  326. if ($field->name == 'email')
  327. {
  328. $obj .= "\"someone@example.com\",\n";
  329. }
  330. else if (strpos('name', $field->name) !== false)
  331. {
  332. $obj .= "\"Lefty\",\n";
  333. }
  334. else if (in_array($field->type, ['char', 'varchar', 'text']))
  335. {
  336. $obj .= "\"Some default string\",\n";
  337. }
  338. }
  339. return $obj;
  340. }
  341. /**
  342. * Modifies the routes file to include a line for the API endpoints
  343. * for this resource.
  344. *
  345. * @param string $plural
  346. * @param string $version
  347. *
  348. * @return $this
  349. */
  350. private function addRoutes( $plural, $version )
  351. {
  352. $path = APPPATH .'config/routes.php';
  353. $version = rtrim($version, ', ');
  354. if (! empty($version))
  355. {
  356. $version .='/';
  357. }
  358. $content = "\$routes->resources('{$version}{$plural}');\n";
  359. return $this->injectIntoFile($path, $content, ['after' => "// Auto-generated routes go here\n"]);
  360. }
  361. /**
  362. * Installer Methods
  363. */
  364. /**
  365. * Handles actually installing the tables and correct authentication
  366. * type.
  367. */
  368. private function install( )
  369. {
  370. CLI::write("Available Auth Types: ". CLI::color('basic, digest, none', 'yellow') );
  371. $this->auth_type = trim( CLI::prompt('Auth type') );
  372. switch ($this->auth_type)
  373. {
  374. case 'basic':
  375. $this->setupBasic();
  376. $this->readme('readme_basic.txt');
  377. break;
  378. case 'digest':
  379. $this->setupDigest();
  380. $this->readme('readme_digest.txt');
  381. break;
  382. }
  383. $this->setupLogging();
  384. }
  385. private function setupBasic()
  386. {
  387. $this->setAuthType('basic');
  388. }
  389. private function setupDigest()
  390. {
  391. $this->makeMigration('migration', 'Add_digest_key_to_users');
  392. $this->setAuthType('digest');
  393. }
  394. /**
  395. * Utility Methods
  396. */
  397. private function setupLogging()
  398. {
  399. $shouldWe = CLI::prompt('Enable Request Logging?', ['y', 'n']);
  400. if (strtolower($shouldWe) != 'y')
  401. {
  402. return;
  403. }
  404. $this->makeMigration('log_migration', 'Create_api_log_table');
  405. // Update the config setting
  406. $content = "config['api.enable_logging'] = true;";
  407. $this->injectIntoFile(APPPATH .'config/api.php', $content, ['regex' => "/config\['api.enable_logging']\s+=\s+[a-zA-Z]+;/u"] );
  408. }
  409. private function makeMigration( $tpl, $name )
  410. {
  411. // Create the migration
  412. $this->load->library('migration');
  413. $destination = $this->migration->determine_migration_path('app', true);
  414. $file = $this->migration->make_name( $name );
  415. $destination = rtrim($destination, '/') .'/'. $file;
  416. if (! $this->copyTemplate( $tpl, $destination, [], true) )
  417. {
  418. CLI::error('Error creating migration file.');
  419. }
  420. $this->setAuthType('digest');
  421. }
  422. /**
  423. * Modifies the config/api.php file and sets the Auth type
  424. *
  425. * @param $type
  426. */
  427. private function setAuthType( $type )
  428. {
  429. $content = "config['api.auth_type'] = '{$type}';";
  430. $this->injectIntoFile(APPPATH .'config/api.php', $content, ['regex' => "/config\['api.auth_type']\s+=\s+'[a-zA-Z]+';/u"] );
  431. }
  432. }