CRUD.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <?php
  2. /**
  3. * User: Mike
  4. * Email: m@9026.com
  5. * Date: 2016/7/28
  6. * Time: 15:09
  7. */
  8. namespace App\Services\CRUD;
  9. use App\Models\AdminMenusModel;
  10. use App\Models\ModuleTagModel;
  11. use App\Services\Base\BaseProcess;
  12. class CRUD extends BaseProcess
  13. {
  14. private $_data;
  15. private $_date;
  16. private $_modelTpl;
  17. private $_repositoriesTpl;
  18. private $_CriteriaMultiWhereTpl;
  19. private $_controlTpl;
  20. private $_viewTpl;
  21. private $_fieldData;
  22. private $_fieldContentData;
  23. private $_fieldPrimary;
  24. public $notFillable = ['created_at', 'updated_at', "deleted_at"];
  25. private $_coverage = true; //强制覆盖
  26. /**
  27. * 创建CRUD
  28. * @param $data
  29. *
  30. */
  31. public function create($data)
  32. {
  33. $this->_dealData($data);
  34. \DB::beginTransaction();
  35. if (!$this->_addMenu()) {
  36. dd(1);
  37. $this->setMsg("添加菜单失败");
  38. \DB::rollback();
  39. return false;
  40. }
  41. if (!$this->_addModel()) {
  42. dd(2);
  43. $this->setMsg("添加模型失败");
  44. \DB::rollback();
  45. return false;
  46. }
  47. if (!$this->_addService()) {
  48. dd(3);
  49. $this->setMsg("添加Service失败");
  50. \DB::rollback();
  51. return false;
  52. }
  53. if (!$this->_addCriteriaMultiWhere()) {
  54. dd(4);
  55. $this->setMsg("添加Service失败");
  56. \DB::rollback();
  57. return false;
  58. }
  59. if (!$this->_addControl()) {
  60. dd(5);
  61. $this->setMsg("添加控制器失败");
  62. \DB::rollback();
  63. return false;
  64. }
  65. \DB::commit();
  66. return true;
  67. }
  68. public function _dealData($data)
  69. {
  70. $data['modelPath'] = app_path() . "/Models/" . $data['model'] . ".php";
  71. $data['modelUse'] = 'App\Models\\' . $data['model'];
  72. $data['modelName'] = $data['model'];
  73. $data['repositoriesPath'] = app_path() . "/Repositories/" . $data['repositories'] . ".php";
  74. $data['repositoriesUse'] = 'App\Repositories\\' . str_replace('/', '\\', $data['repositories']);
  75. $data['CriteriaMultiWherePath'] = app_path() . "/Repositories/" . substr($data['repositories'], 0, strrpos($data['repositories'], "Repository")) . "/Criteria/MultiWhere" . ".php";;
  76. $data['CriteriaMultiWhereUse'] = 'App\Repositories\\' .str_replace('/', '\\', substr($data['repositories'], 0, strrpos($data['repositories'], "Repository"))) . "\\Criteria\\MultiWhere";
  77. $data['repositoriesName'] = substr(strrchr($data['repositories'], '/'), 1);
  78. $data['controlPath'] = app_path() . "/Http/Controllers/Admin/" . $data['control'] . ".php";
  79. $data['controlUse'] = 'App\Repositories\\' . str_replace('/', '\\', $data['control']);
  80. $data['controlName'] = substr(strrchr($data['control'], '/'), 1);
  81. $data['viewBaseSort'] = 'admin/' . strtolower(substr($data['control'], 0, -10));
  82. $data['viewPath'] = base_path() . "/resources/views/" . $data['viewBaseSort'];
  83. $data['viewSortPath'] = str_replace('/', '.', $data['viewBaseSort']);
  84. $this->_modelTpl = __DIR__ . "/tpl/model.tpl";
  85. $this->_repositoriesTpl = __DIR__ . "/tpl/repositories.tpl";
  86. $this->_CriteriaMultiWhereTpl = __DIR__ . "/tpl/creteriaMultiWhere.tpl";
  87. $this->_controlTpl = __DIR__ . "/tpl/controller.tpl";
  88. $this->_viewTpl = __DIR__ . "/tpl/view/";
  89. $this->_data = $data;
  90. $this->_date = date("Y-m-d H:i:s");
  91. $this->_fieldData = $resultRow = \DB::select("SELECT COLUMN_NAME,COLUMN_KEY,DATA_TYPE,COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA= ? AND TABLE_NAME= ? ", [env('DB_DATABASE'), $this->_data['table']]);
  92. foreach ($this->_fieldData as $data) {
  93. if ($data->COLUMN_KEY == "PRI") {
  94. $this->_fieldPrimary = $data->COLUMN_NAME;
  95. break;
  96. }
  97. }
  98. }
  99. /**
  100. *
  101. * 添加菜单
  102. *
  103. */
  104. public function _addMenu()
  105. {
  106. $obj = new AdminMenusModel();
  107. $addData['pid'] = $this->_data['menu_pid'];
  108. $addData['display'] = 1;
  109. $addData['name'] = $this->_data['desc'];
  110. $addData['path'] = $this->_data['path'] . "/index";
  111. $baseObj = $obj->create($addData);
  112. if (!$baseObj) {
  113. return false;
  114. }
  115. //添加
  116. $addData['display'] = 0;
  117. $addData['path'] = $this->_data['path'] . "/create";
  118. $addData['pid'] = $baseObj->id;
  119. $addData['name'] = "添加";
  120. $ok = $obj->create($addData);
  121. if (!$ok) {
  122. return false;
  123. }
  124. //修改
  125. $addData['display'] = 0;
  126. $addData['path'] = $this->_data['path'] . "/update";
  127. $addData['pid'] = $baseObj->id;
  128. $addData['name'] = "修改";
  129. $ok = $obj->create($addData);
  130. if (!$ok) {
  131. return false;
  132. }
  133. //删除
  134. $addData['display'] = 0;
  135. $addData['path'] = $this->_data['path'] . "/destroy";
  136. $addData['pid'] = $baseObj->id;
  137. $addData['name'] = "删除";
  138. $ok = $obj->create($addData);
  139. if (!$ok) {
  140. return false;
  141. }
  142. //查看
  143. $addData['display'] = 0;
  144. $addData['path'] = $this->_data['path'] . "/view";
  145. $addData['pid'] = $baseObj->id;
  146. $addData['name'] = "查看";
  147. $ok = $obj->create($addData);
  148. if (!$ok) {
  149. return false;
  150. }
  151. if (!$ok) {
  152. return false;
  153. }
  154. if (dict($this->_data['table'], 'status')) {
  155. //check
  156. $addData['display'] = 0;
  157. $addData['path'] = $this->_data['path'] . "/status";
  158. $addData['pid'] = $baseObj->id;
  159. $addData['name'] = "状态变更";
  160. if (!$ok) {
  161. return false;
  162. }
  163. }
  164. return true;
  165. }
  166. /**
  167. * 添加模型
  168. */
  169. public function _addModel()
  170. {
  171. $filePath = $this->_data['modelPath'];
  172. if (file_exists($filePath) && $this->_coverage === false) {
  173. return true;
  174. }
  175. $notFillable = $this->notFillable;
  176. $date = $this->_date;
  177. $tpl = file_get_contents($this->_modelTpl);
  178. $table_primary = '';
  179. $fillable = "";
  180. foreach ($this->_fieldData as $data) {
  181. if ($data->COLUMN_KEY == "PRI") {
  182. $table_primary = $data->COLUMN_NAME;
  183. } elseif (!in_array($data->COLUMN_NAME, $notFillable)) {
  184. $fillable .= "\r\n '{$data->COLUMN_NAME}',";
  185. }
  186. }
  187. $fillable = $fillable ? '[' . substr($fillable, 0, -1) . "\r\n ]" : $fillable;
  188. $str = str_replace("{{table_comment}}", $this->_data['desc'], $tpl);
  189. $str = str_replace("{{table_name}}", $this->_data['table'], $str);
  190. $str = str_replace("{{table_primary}}", $table_primary, $str);
  191. $str = str_replace("{{fillable}}", $fillable, $str);
  192. $str = str_replace("{{class_name}}", $this->_data['model'], $str);
  193. $str = str_replace("{{date}}", $this->_date, $str);
  194. $ok = file_put_contents($filePath, $str);
  195. if (!$ok) {
  196. return true;
  197. }
  198. return true;
  199. }
  200. /**
  201. * 添加Service
  202. */
  203. public function _addService()
  204. {
  205. if (file_exists($this->_data['repositoriesPath']) && $this->_coverage === false) {
  206. return true;
  207. }
  208. $tpl = file_get_contents($this->_repositoriesTpl);
  209. $queryKeyord = "";
  210. foreach ($this->_fieldData as $data) {
  211. $queryKeyord = " if(isset(\$this->search['{$data->COLUMN_NAME}']) && \$this->search['{$data->COLUMN_NAME}']) {
  212. \$model = \$model->where('{$data->COLUMN_NAME}',\$this->search['{$data->COLUMN_NAME}']);
  213. }\r\n";
  214. }
  215. $this->customMkdir(substr($this->_data['repositoriesPath'], 0, strrpos($this->_data['repositoriesPath'], "/")), $mode = 0777, $recursive = true);
  216. $str = str_replace("{{desc}}", $this->_data['desc'], $tpl);
  217. $str = str_replace("{{modelUse}}", $this->_data['modelUse'], $str);
  218. $str = str_replace("{{sortPath}}", str_replace('/', '\\', substr($this->_data['repositories'], 0, strrpos($this->_data['repositories'], "/"))), $str);
  219. $str = str_replace("{{repositoriesName}}", $this->_data['repositoriesName'], $str);
  220. $str = str_replace("{{modelName}}", $this->_data['modelName'], $str);
  221. $str = str_replace("{{primaryKey}}", $this->_fieldPrimary, $str);
  222. $str = str_replace("{{queryKeyord}}", $queryKeyord, $str);
  223. $str = str_replace("{{date}}", $this->_date, $str);
  224. $ok = file_put_contents($this->_data['repositoriesPath'], $str);
  225. return true;
  226. }
  227. /**
  228. * 添加Service
  229. */
  230. public function _addCriteriaMultiWhere()
  231. {
  232. if (file_exists($this->_data['CriteriaMultiWherePath']) && $this->_coverage === false) {
  233. return true;
  234. }
  235. $tpl = file_get_contents($this->_CriteriaMultiWhereTpl);
  236. $queryKeyord = "";
  237. foreach ($this->_fieldData as $k =>$data) {
  238. if($data->DATA_TYPE != 'timestamp'){
  239. if($k == 0){
  240. $queryKeyord .= " if(isset(\$this->search['keyword']) && \$this->search['keyword']) {
  241. \$model = \$model->where('{$data->COLUMN_NAME}','like','%'.\$this->search['keyword'].'%')\r\n";
  242. }else{
  243. $queryKeyord .= " ->orWhere('{$data->COLUMN_NAME}','like','%'.\$this->search['keyword'].'%')\r\n";
  244. }
  245. }
  246. }
  247. $queryKeyord .=";}";
  248. $this->customMkdir(substr($this->_data['CriteriaMultiWherePath'], 0, strrpos($this->_data['CriteriaMultiWherePath'], "/")), $mode = 0777, $recursive = true);
  249. $str = str_replace("{{desc}}", $this->_data['desc'], $tpl);
  250. $str = str_replace("{{sortPath}}", str_replace('/', '\\', substr($this->_data['repositories'], 0, strrpos($this->_data['repositories'], "Repository"))), $str);
  251. $str = str_replace("{{queryKeyord}}", $queryKeyord, $str);
  252. $ok = file_put_contents($this->_data['CriteriaMultiWherePath'], $str);
  253. return true;
  254. }
  255. public function _addControl()
  256. {
  257. if (file_exists($this->_data['controlPath']) && $this->_coverage === false) {
  258. return true;
  259. }
  260. $this->customMkdir(substr($this->_data['controlPath'], 0, strrpos($this->_data['controlPath'], "/")), $mode = 0777, $recursive = true);
  261. $str = file_get_contents($this->_controlTpl);
  262. $str = str_replace("{{desc}}", $this->_data['desc'], $str);
  263. $str = str_replace("{{path}}", $this->_data['path'], $str);
  264. $str = str_replace("{{repositoriesUse}}", $this->_data['repositoriesUse'], $str);
  265. $str = str_replace("{{sortPath}}", str_replace('/', '\\', substr($this->_data['control'], 0, strrpos($this->_data['control'], "/"))), $str);
  266. $str = str_replace("{{repositoriesName}}", $this->_data['repositoriesName'], $str);
  267. $str = str_replace("{{CriteriaMultiWhereUse}}", $this->_data['CriteriaMultiWhereUse'], $str);
  268. $str = str_replace("{{controlName}}", $this->_data['controlName'], $str);
  269. $str = str_replace("{{viewSortPath}}", $this->_data['viewSortPath'], $str);
  270. $str = str_replace("{{date}}", $this->_date, $str);
  271. $ok = file_put_contents($this->_data['controlPath'], $str);
  272. $this->_addView();
  273. return true;
  274. }
  275. public function _addView()
  276. {
  277. if (file_exists($this->_data['viewPath']) && $this->_coverage === false) {
  278. return true;
  279. }
  280. $this->customMkdir($this->_data['viewPath'], $mode = 0777, $recursive = true);
  281. ###index
  282. $str = file_get_contents($this->_viewTpl . "/index.blade.php");
  283. $listThStr = "";
  284. $listTdStr = "";
  285. $i = 0;
  286. foreach ($this->_fieldData as $key => $data) {
  287. if (!in_array($data->COLUMN_NAME, ["deleted_at"]) && $data->DATA_TYPE != 'text') {
  288. if ($data->COLUMN_NAME == "id") $this->_fieldData[$key]->COLUMN_COMMENT = "ID";
  289. if ($data->COLUMN_NAME == "created_at") $this->_fieldData[$key]->COLUMN_COMMENT = "创建时间";
  290. if ($data->COLUMN_NAME == "updated_at") $this->_fieldData[$key]->COLUMN_COMMENT = "更新时间";
  291. $i++;
  292. if ($i > 7) break;
  293. $listThStr .= "\r\n <th class=\"sorting\" data-sort=\"{$data->COLUMN_NAME}\"> {$data->COLUMN_COMMENT} </th>";
  294. switch ($data->DATA_TYPE) {
  295. case "tinyint":
  296. if (dict()->get($this->_data['table'], $data->COLUMN_NAME)) {
  297. $listTdStr .= "\r\n <span class='label label-primary'>{{ dict()->get('{$this->_data['table']}','{$data->COLUMN_NAME}',\$item->{$data->COLUMN_NAME}) }}</span></td>";
  298. } elseif (dict()->get('global', $data->COLUMN_NAME)) {
  299. $listTdStr .= "\r\n <td><span class='label label-primary'>{{ dict()->get('global','{$data->COLUMN_NAME}',\$item->{$data->COLUMN_NAME}) }}</span></td>";
  300. } else {
  301. $listTdStr .= "\r\n <td>{{ \$item->{$data->COLUMN_NAME} }}</td>";
  302. }
  303. break;
  304. default:
  305. if ($data->COLUMN_NAME == 'image' || $data->COLUMN_NAME == 'pic' || $data->COLUMN_NAME == 'picture' || $data->COLUMN_NAME == 'photo' || $data->COLUMN_NAME == 'avatar' || $data->COLUMN_NAME == 'img'){
  306. $listTdStr .= "\r\n <td><img src='{{ \$item->{$data->COLUMN_NAME} }}' style='height: 50px'></td>";
  307. }else{
  308. $listTdStr .= "\r\n <td>{{ \$item->{$data->COLUMN_NAME} }}</td>";
  309. }
  310. break;
  311. }
  312. }
  313. }
  314. $str = str_replace("{{desc}}", $this->_data['desc'], $str);
  315. $str = str_replace("{{path}}", $this->_data['path'], $str);
  316. $str = str_replace("{{primaryKey}}", $this->_fieldPrimary, $str);
  317. $str = str_replace("{{listThStr}}", $listThStr, $str);
  318. $str = str_replace("{{listTdStr}}", $listTdStr, $str);
  319. $ok = file_put_contents($this->_data['viewPath'] . "/index.blade.php", $str);
  320. #view
  321. $str = file_get_contents($this->_viewTpl . "/view.blade.php");
  322. $str = str_replace("{{desc}}", $this->_data['desc'], $str);
  323. $str = str_replace("{{path}}", $this->_data['path'], $str);
  324. $str = str_replace("{{primaryKey}}", $this->_fieldPrimary, $str);
  325. $viewTdStr = "";
  326. foreach ($this->_fieldData as $key => $data) {
  327. if ($data->COLUMN_NAME == 'id') continue;
  328. if ($data->COLUMN_NAME == 'created_at') continue;
  329. if ($data->COLUMN_NAME == 'updated_at') continue;
  330. if ($data->COLUMN_NAME == 'deleted_at') continue;
  331. $viewTdStr .= " \r\n <div class=\"list-group-item\">
  332. \r\n <h3 class=\"list-group-item-heading\">{$data->COLUMN_COMMENT}</h3>
  333. \r\n <p class=\"list-group-item-text\">";
  334. switch ($data->DATA_TYPE) {
  335. case "tinyint":
  336. if (dict()->get($this->_data['table'], $data->COLUMN_NAME)) {
  337. $viewTdStr .= "<span class='label label-primary'>{{ dict()->get('{$this->_data['table']}','{$data->COLUMN_NAME}',\$data['{$data->COLUMN_NAME}']) }}</span>";
  338. } elseif (dict()->get('global', $data->COLUMN_NAME)) {
  339. $viewTdStr .= "<span class='label label-primary'>{{ dict()->get('global','{$data->COLUMN_NAME}',\$data['{$data->COLUMN_NAME}']) }}</span>";
  340. } else {
  341. $viewTdStr .= " {{ \$data['{$data->COLUMN_NAME}'] or ''}}";
  342. }
  343. break;
  344. case "text":
  345. $viewTdStr .= " {!! \$data['{$data->COLUMN_NAME}'] or '' !!}";
  346. break;
  347. default:
  348. if ($data->COLUMN_NAME == 'image' || $data->COLUMN_NAME == 'pic' || $data->COLUMN_NAME == 'picture' || $data->COLUMN_NAME == 'photo' || $data->COLUMN_NAME == 'avatar' || $data->COLUMN_NAME == 'img'){
  349. $viewTdStr .= "<img src=\"{{ \$data['{$data->COLUMN_NAME}'] }}\" style='height: 80px'>";
  350. }else{
  351. $viewTdStr .= " {{ \$data['{$data->COLUMN_NAME}'] or ''}}";
  352. }
  353. break;
  354. }
  355. $viewTdStr .= "</p>
  356. \r\n </div>";
  357. }
  358. $str = str_replace("{{viewTdStr}}", $viewTdStr, $str);
  359. $ok = file_put_contents($this->_data['viewPath'] . "/view.blade.php", $str);
  360. #edit
  361. $str = file_get_contents($this->_viewTpl . "/edit.blade.php");
  362. $str = str_replace("{{desc}}", $this->_data['desc'], $str);
  363. $str = str_replace("{{path}}", $this->_data['path'], $str);
  364. $str = str_replace("{{primaryKey}}", $this->_fieldPrimary, $str);
  365. $editTdStr = "";
  366. foreach ($this->_fieldData as $data) {
  367. if (!in_array($data->COLUMN_NAME, [$this->_fieldPrimary, 'created_at', 'updated_at', "deleted_at"])) {
  368. $editTdStr .= " \r\n <div class=\"form-group row\">
  369. \r\n <label class=\"col-form-label col-sm-3\">{$data->COLUMN_COMMENT}</label>
  370. \r\n <div class=\"col-sm-9\">";
  371. switch ($data->DATA_TYPE) {
  372. case "tinyint":
  373. if (dict()->get($this->_data['table'], $data->COLUMN_NAME)) {
  374. $editTdStr .= " @foreach(dict()->get('{$this->_data['table']}','{$data->COLUMN_NAME}') as \$key=>\$val)
  375. <label class=\"radio-inline\">
  376. <input type=\"radio\" name=\"data[{$data->COLUMN_NAME}]\" value=\"\$key\" @if(isset(\$data['{$data->COLUMN_NAME}']) && \$data['{$data->COLUMN_NAME}'] == \$key)checked=\"checked\" @endif/>{{\$val}}
  377. </label>
  378. @endforeach";
  379. } elseif (dict()->get('global', $data->COLUMN_NAME)) {
  380. $editTdStr .= " @foreach(dict()->get('global','{$data->COLUMN_NAME}') as \$key=>\$val)
  381. <label class=\"radio-inline\">
  382. <input type=\"radio\" name=\"data[{$data->COLUMN_NAME}]\" value=\"\{{\$key}}\" @if(isset(\$data['{$data->COLUMN_NAME}']) && \$data['{$data->COLUMN_NAME}'] == \$key)checked=\"checked\" @endif/>{{\$val}}
  383. </label>
  384. @endforeach";
  385. } else {
  386. $editTdStr .= "\r\n <input id=\"data_{$data->COLUMN_NAME}\" name=\"data[{$data->COLUMN_NAME}]\" class=\"form-control\" value=\"{{ \$data['{$data->COLUMN_NAME}'] or ''}}\" required=\"\" aria-required=\"true\" placeholder=\"\">";
  387. }
  388. break;
  389. case "text":
  390. $editTdStr .= "\r\n {!! editor('') !!}
  391. \r\n <script id=\"container\" name=\"data[{$data->COLUMN_NAME}]\" type=\"text/plain\">{!! \$data['{$data->COLUMN_NAME}'] or '' !!}</script>
  392. ";
  393. break;
  394. case "datetime":
  395. $editTdStr .= "\r\n <input name=\"data[{$data->COLUMN_NAME}]\" class=\"form-control laydate-icon help-block m-b-none\" style=\"width:200px; height:34px;\" value=\"{{ \$data['{$data->COLUMN_NAME}'] or ''}}\" placeholder=\"{$data->COLUMN_COMMENT}\" onclick=\"laydate({istime: true, format: 'YYYY-MM-DD hh:mm:ss'})\" aria-invalid=\"false\">";
  396. break;
  397. default:
  398. if ($data->COLUMN_NAME == 'image' || $data->COLUMN_NAME == 'pic' || $data->COLUMN_NAME == 'picture' || $data->COLUMN_NAME == 'photo' || $data->COLUMN_NAME == 'avatar' || $data->COLUMN_NAME == 'img') {
  399. $editTdStr .= "\r\n {!! widget('Tools.ImgUpload')->single('{$data->COLUMN_NAME}','data[{$data->COLUMN_NAME}]', isset(\$data['{$data->COLUMN_NAME}'])? \$data['{$data->COLUMN_NAME}'] : '') !!} ";
  400. } else {
  401. $editTdStr .= "\r\n <input id=\"data_{$data->COLUMN_NAME}\" name=\"data[{$data->COLUMN_NAME}]\" class=\"form-control\" value=\"{{ \$data['{$data->COLUMN_NAME}'] or ''}}\" required=\"\" aria-required=\"true\" placeholder=\"\">";
  402. }
  403. break;
  404. }
  405. $editTdStr .= " \r\n </div>
  406. \r\n </div>";
  407. }
  408. }
  409. $str = str_replace("{{editTdStr}}", $editTdStr, $str);
  410. $ok = file_put_contents($this->_data['viewPath'] . "/edit.blade.php", $str);
  411. return true;
  412. }
  413. public function customMkdir($dir)
  414. {
  415. if (!file_exists($dir)) {
  416. \File::makeDirectory($dir, $mode = 0755, $recursive = true);
  417. }
  418. }
  419. }