UNPKG

generator-php-scaffold

Version:

This generator create a application with restful architecture with Zend 3 or Symfony framework

751 lines (733 loc) 29.3 kB
const chalk = require('chalk'); const memFs = require('mem-fs'); const editor = require('mem-fs-editor'); const fs = require('fs'); const _ = require('lodash'); const util = require('../util/util'); const Generator = require('yeoman-generator'); var index = 0; var itensFields = []; module.exports = class extends Generator { askForField(answers) { const done = this.async(); const prompts = [{ type: 'confirm', name: 'fieldAdd', message: 'Do you want to add a field to your entity?', default: true }, { when: response => response.fieldAdd === true, type: 'input', name: 'fieldName', validate: (input) => { if (!(/^([a-zA-Z0-9_]*)$/.test(input))) { return 'Your field name cannot contain special characters'; } else if (input === '') { return 'Your field name cannot be empty'; } else if (input.charAt(0) === input.charAt(0).toUpperCase()) { return 'Your field name cannot start with an upper case letter'; } return true; }, message: 'What is the name of your field?', }, { when: response => response.fieldAdd === true, type: 'list', name: 'fieldType', message: 'What is the type of your field?', choices: [{ value: 'string', name: 'String' }, { value: 'integer', name: 'Integer' }, { value: 'float', name: 'Float' }, { value: 'decimal', name: 'Decimal' }, { value: 'binary', name: 'Binary' }, { value: 'bool', name: 'Boolean' }, { value: 'string', name: 'Text' }, { value: 'date', name: 'Date' }, { value: 'time', name: 'Time' }, { value: 'datetime', name: 'Date Time' }, { value: 'class', name: 'Class field to relationship' }, { value: 'datetimetz', name: 'Date Timetz' }, { value: 'array', name: 'Array' }, { value: 'simple_array,', name: 'Simple Array,' }, { value: 'json_array', name: 'Json Array' }, { value: 'object', name: 'Object' }, { value: 'blob', name: 'Blob' }, { value: 'guid', name: 'Guid' }, { value: 'smallint', name: 'Smallint' }, { value: 'bigint', name: 'bigint' }, ], default: 0 }, { when: (response) => response.fieldAdd === true && response.fieldType === 'class', type: 'list', name: 'relationshipType', message: 'What is the type of the relationship?', choices: (response) => { const opts = [{ value: 'many-to-one', name: 'Many To One' }, { value: 'many-to-many', name: 'Many To Many' }, { value: 'one-to-one', name: 'One to One' } ]; return opts; }, default: 'one-to-one' }, { when: (response) => response.fieldAdd === true && response.fieldType === 'class', type: 'input', name: 'otherEntityName', validate: (input) => { if (input === '') { return 'Your class name cannot be empty.'; } if (!/^[A-Za-z0-9_]*$/.test(input)) { return 'Your class name cannot contain special characters (allowed characters: A-Z, a-z, 0-9 and _)'; } return true; }, message: 'What is the class name of your field?' }, { when: (response) => response.fieldAdd === true && response.fieldType === 'decimal', type: 'input', name: 'fieldPrecision', validate: (input) => { if (input === '') { return 'Your class precision cannot be empty.'; } if (!/^[0-9]*$/.test(input)) { return 'Your precision cannot contain letters or special characters (allowed characters: 0-9)'; } return true; }, message: 'What is the precision of field?', default: 10, }, { when: (response) => response.fieldAdd === true && response.fieldType === 'decimal', type: 'input', name: 'fieldScale', validate: (input) => { if (input === '') { return 'Your class scale cannot be empty.'; } if (!/^[0-9]*$/.test(input)) { return 'Your scale cannot contain letters or special characters (allowed characters: 0-9)'; } return true; }, message: 'What is the scale of field?' }, { when: response => response.fieldAdd === true, type: 'confirm', name: 'fieldValidate', message: 'Do you want to add validation rules to your field?', default: false }, { when: response => response.fieldAdd === true && response.fieldValidate === true, type: 'checkbox', name: 'fieldValidateRules', message: 'Which validation rules do you want to add?', choices: (response) => { const opts = [{ name: 'Required', value: 'required' }, { name: 'Unique', value: 'unique' }, { name: 'Minimum length', value: 'minlength' }, { name: 'Maximum length', value: 'maxlength' } /*, { name: 'Regular expression pattern', value: 'pattern' }*/ ]; return opts; }, default: 0 }, { when: response => response.fieldAdd === true && response.fieldValidate === true && response.fieldValidateRules.includes('minlength'), type: 'input', name: 'fieldValidateRulesMin', validate: input => (_.isNumber(parseInt(input)) ? true : 'Minimum length must be a positive number'), message: 'What is the minimum length of your field?', default: 0 }, { when: response => response.fieldAdd === true && response.fieldValidate === true && response.fieldValidateRules.includes('maxlength'), type: 'input', name: 'fieldValidateRulesMax', validate: input => (_.isNumber(parseInt(input)) ? true : 'Maximum length must be a positive number'), message: 'What is the maximum length of your field?', default: 60 }, { when: response => response.fieldAdd === true && response.fieldValidate === true && response.fieldValidateRules.includes('pattern'), type: 'input', name: 'fieldValidateRulesPattern', message: 'What is the regular expression pattern you want to apply on your field?', default: '^[a-zA-Z0-9]*$' } ]; this.prompt(prompts).then((props) => { if (props.fieldAdd) { this.addFields(props); this.askForField.call(this, done); } else { this.askToContinue(); } }); } addFields(props) { itensFields[index].fields.push({ fieldName: props.fieldName, fieldType: _.lowerCase(props.fieldType), relationshipType: _.upperFirst(_.camelCase(props.relationshipType)), otherEntityName: _.upperFirst(_.camelCase(props.otherEntityName)), fieldValidate: props.fieldValidate, fieldValidateRules: ((props.fieldValidate === true) ? props.fieldValidateRules : []), fieldValidateRulesPattern: props.fieldValidateRulesPattern, fieldValidateRulesMin: props.fieldValidateRulesMin, fieldValidateRulesMax: props.fieldValidateRulesMax }); } askForNewEntity() { this.prompt([{ type: 'list', name: 'typeCreation', message: 'What do you like, create entity or import file', choices: [{ name: 'Create new entity', value: 'create' }, { name: 'Import file\'s jdl', value: 'import' }] }, { when: response => response.typeCreation === 'create', type: 'input', name: 'className', message: 'What is the class\'s name: ', validate: function(input) { if (input.charAt(0) !== input.charAt(0).toUpperCase()) { return "Invalid Class Name! The first character must be uppercase."; } return true; } }, { when: response => response.typeCreation === 'import', type: 'input', name: 'pathName', message: 'What is the path of jdl file: ', default: 'data/json-files' }]).then((answers) => { if (answers.typeCreation === 'create') { itensFields[index] = { entity: 'App.' + answers.className, fields: [] }; this.askForField.call(this); } else { var store = memFs.create(); var fsr = editor.create(store); var listFiles = fs.readdirSync(answers.pathName); if (fs.existsSync(answers.pathName)) { listFiles.forEach(file => { var contentFile = fsr.read(answers.pathName + '/' + file); this.mapJsonEntity('App', JSON.parse(contentFile)); }); var values = ''; itensFields.forEach(element => { values = element.entity.split('.') this.writeBackendFiles(element, values); this.writeFrontendFiles(element); }); } else { console.log(chalk.red('Erro: The files path has not exist.')); } } }); } mapJsonEntity(bundle, entity) { itensFields[index] = { entity: bundle + '.' + _.upperFirst(_.camelCase(entity.entityTableName)), fields: [] }; entity.fields.forEach(element => { itensFields[index].fields.push({ fieldName: element.fieldName, fieldType: _.lowerCase(element.fieldType), otherEntityName: '', fieldValidateRules: (element.fieldValidateRules) ? element.fieldValidateRules : [], }); }); entity.relationships.forEach(element => { itensFields[index].fields.push({ fieldName: element.relationshipName, fieldType: 'class', relationshipType: _.upperFirst(_.camelCase(element.relationshipType)), otherEntityName: _.upperFirst(_.camelCase(element.otherEntityName)), fieldValidateRules: (element.fieldValidateRules) ? element.fieldValidateRules : [], }); }); index++; } askToContinue() { this.prompt([{ type: 'confirm', name: 'check', message: 'Do you want create a new entity: ', }]).then((answers) => { if (answers.check) { index++; this.askForNewEntity.call(this); } else { var values = ''; this.log('Creating files...'); itensFields.forEach(element => { values = element.entity.split('.'); this.writeBackendFiles(element, values); this.writeFrontendFiles(element); }); this.log('After execution do you have execute composer dump-autoload to update your modules'); } }); } writeBackendFiles(props, answers) { const me = this; fs.readFile('.yo-rc.json', 'utf-8', function(error, content) { let path = JSON.parse(content); if (_.toUpper(path.config.appFramework) === 'ZEND') { me.writeBackendZend(props); me.writeConfigServiceZend(answers[0], answers[1]); } else { me.writeBackendSymfony(props); me.writeConfigServiceSymfony(answers[1]); } }); } writeBackendZend(props) { var values = props.entity.split('.'); var types = [{ 'type': 'Post' }, { 'type': 'Put' }, { 'type': 'Delete' } ]; var values = props.entity.split('.'); types.forEach(element => { props.type = element.type; values[1] = _.startCase(values[1]).replace(' ', ''); this.writeFile( 'backend/zend/files/Command.php', 'api/src/App/Command/' + values[1] + '/' + element.type + 'Command.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/Handler.php', 'api/src/App/Handler/' + values[1] + '/' + element.type + 'Handler.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/MiddlewareFactory.php', 'api/src/App/Factory/' + values[1] + '/' + element.type + 'MiddlewareFactory.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/Middleware.php', 'api/src/App/Middleware/' + values[1] + '/' + element.type + 'Middleware.php', values[0], values[1], props ); }); this.writeFile( 'backend/zend/files/GetMiddlewareFactory.php', 'api/src/App/Factory/' + values[1] + '/GetMiddlewareFactory.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/GetMiddleware.php', 'api/src/App/Middleware/' + values[1] + '/GetMiddleware.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/Entity.php', 'api/src/App/Entity/' + values[1] + '.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/Query.php', 'api/src/App/Query/' + values[1] + 'Query.php', values[0], values[1], props ); this.writeFile( 'backend/zend/files/Repository.php', 'api/src/App/Repository/' + values[1] + 'Repository.php', values[0], values[1], props ); } writeBackendSymfony(props) { var types = [{ 'type': 'Post' }, { 'type': 'Put' }, { 'type': 'Delete' } ]; var values = props.entity.split('.'); values[1] = _.startCase(values[1]).replace(' ', ''); types.forEach(element => { props.type = element.type; this.writeFile( 'backend/symfony/files/Command.php', 'api/src/Service/Command/' + values[1] + '/' + element.type + 'Command.php', values[0], values[1], props ); this.writeFile( 'backend/symfony/files/Handler.php', 'api/src/Service/Handler/' + values[1] + '/' + element.type + 'Handler.php', values[0], values[1], props ); }); this.writeFile( 'backend/symfony/files/Query.php', 'api/src/Service/Query/' + _.upperFirst(_.camelCase(values[1])) + 'Query.php', values[0], values[1], props ); this.writeFile( 'backend/symfony/files/Repository.php', 'api/src/Repository/' + _.upperFirst(_.camelCase(values[1])) + 'Repository.php', values[0], values[1], props ); this.writeFile( 'backend/symfony/files/Controller.php', 'api/src/Controller/' + _.upperFirst(_.camelCase(values[1])) + 'Controller.php', values[0], values[1], props ); this.writeFile( 'backend/symfony/files/Entity.php', 'api/src/Entity/' + _.upperFirst(_.camelCase(values[1])) + '.php', values[0], values[1], props ); } writeFrontendFiles(props) { const me = this; fs.readFile('.yo-rc.json', 'utf-8', function(error, content) { let path = JSON.parse(content); let config = path.config; var values = props.entity.split('.'); values[1] = _.kebabCase(values[1]).toLowerCase(); me.writeFile( 'frontend/files/entity-detail.component.html', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '-detail.component.html', values[0], values[1], props ); me.writeFile( 'frontend/files/entity-detail.component.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '-detail.component.ts', values[0], values[1], props ); me.writeFile( 'frontend/files/entity-form.component.html', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '-form.component.html', values[0], values[1], props ); me.writeFile( 'frontend/files/entity-form.component.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '-form.component.ts', values[0], values[1], props ); me.writeFile( 'frontend/files/entity.component.html', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '.component.html', values[0], values[1], props ); me.writeFile( 'frontend/files/entity.component.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '.component.ts', values[0], values[1], props ); me.writeFile( 'frontend/files/entity.model.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/models/' + _.toLower(values[1]) + '.model.ts', values[0], values[1], props ); me.writeFile( 'frontend/files/entity.module.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '.module.ts', values[0], values[1], props ); me.writeFile( 'frontend/files/entity.route.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/' + _.toLower(values[1]) + '.route.ts', values[0], values[1], props ); me.writeFile( 'frontend/files/entity.service.ts', config.frontendDir + '/src/app/' + _.toLower(values[1]) + '/services/' + _.toLower(values[1]) + '.service.ts', values[0], values[1], props ); me.writeConfigFrontend(values[1]); }); } writeConfigFrontend(entity) { const me = this; fs.readFile('.yo-rc.json', 'utf-8', function(error, content) { let path = JSON.parse(content); let config = path.config; util.rewriteFile({ path: config.frontendDir + '/src/app/layouts/admin-layout/', file: 'admin-layout.routing.ts', needle: 'needle-add-router', splicable: [ `{ path: '${_.kebabCase(entity).toLowerCase()}', loadChildren: '../../${_.kebabCase(entity).toLowerCase()}/${_.kebabCase(entity).toLowerCase()}.module#${_.startCase(entity).replace(' ', '')}Module' },` ] }, me); util.rewriteFile({ path: config.frontendDir + '/src/app/components/sidebar/', file: 'sidebar.component.ts', needle: 'needle-menu-item', splicable: [ `{ path: '/${_.kebabCase(entity).toLowerCase()}', title: '${_.upperFirst(entity)}', icon: 'bookmark', class: '' },` ] }, me); }); } writeFile(origin, destination, bundle, entity, props) { var fs = require('fs'); if (!fs.existsSync(destination)) { this.fs.copyTpl( this.templatePath(origin), this.destinationPath(destination), { packageName: bundle, className: values[1] = _.startCase(entity).replace(' ', ''), attributs: props, _: _ } ); } else { console.log(chalk.yellow('Warning the file: ') + destination + chalk.yellow(' already exist')); } } writeConfigServiceSymfony(entity) { util.rewriteFile({ path: 'api/config/', file: 'services.yaml', needle: 'Map-Services-Entity', splicable: [ `# ${entity} App\\Service\\Query\\${entity}Query: class: App\\Service\\Query\\${entity}Query arguments: - '@doctrine.orm.entity_manager' App\\Service\\Handler\\${entity}\\PostHandler: class: App\\Service\\Handler\\${entity}\\PostHandler arguments: - '@doctrine.orm.entity_manager' tags: - { name: tactician.handler, command: App\\Service\\Command\\${entity}\\PostCommand } App\\Service\\Handler\\${entity}\\PutHandler: class: App\\Service\\Handler\\${entity}\\PutHandler arguments: - '@doctrine.orm.entity_manager' tags: - { name: tactician.handler, command: App\\Service\\Command\\${entity}\\PutCommand } App\\Service\\Handler\\${entity}\\DeleteHandler: class: App\\Service\\Handler\\${entity}\\DeleteHandler arguments: - '@doctrine.orm.entity_manager' tags: - { name: tactician.handler, command: App\\Service\\Command\\${entity}\\DeleteCommand }` ] }, this); } writeConfigServiceZend(module, entity) { util.rewriteFile({ path: 'api/config/autoload', file: 'doctrine.global.php', needle: 'new-entity-mapper', splicable: [ `'App\\Entity\\${_.startCase(entity).replace(' ', '')}' => 'annotation_driver',`, ] }, this); util.rewriteFile({ path: 'api/src/App', file: 'ConfigProvider.php', needle: 'new-factories-mapper', splicable: [ `\\App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\GetMiddleware::class => \\App\\Factory\\${_.startCase(entity).replace(' ', '')}\\GetMiddlewareFactory::class, \\App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\PostMiddleware::class => \\App\\Factory\\${_.startCase(entity).replace(' ', '')}\\PostMiddlewareFactory::class, \\App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\PutMiddleware::class => \\App\\Factory\\${_.startCase(entity).replace(' ', '')}\\PutMiddlewareFactory::class, \\App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\DeleteMiddleware::class => \\App\\Factory\\${_.startCase(entity).replace(' ', '')}\\DeleteMiddlewareFactory::class,`, ] }, this); util.rewriteFile({ path: 'api/config', file: 'routes.php', needle: `new-route-mapper`, splicable: [ `$app->post('/api/${ _.kebabCase(entity) }', App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\PostMiddleware::class, '${ _.kebabCase(entity) }.post'); $app->get('/api/${ _.kebabCase(entity) }[/{id:[0-9]{1,99}}]', App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\GetMiddleware::class, '${ _.kebabCase(entity) }.get'); $app->put('/api/${ _.kebabCase(entity) }[/{id:[0-9]{1,99}}]', App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\PutMiddleware::class, '${ _.kebabCase(entity) }.put'); $app->delete('/api/${ _.kebabCase(entity) }[/{id:[0-9]{1,99}}]', App\\Middleware\\${_.startCase(entity).replace(' ', '')}\\DeleteMiddleware::class, '${ _.kebabCase(entity) }.delete');` ] }, this); } writeFile(origin, destination, bundle, entity, props) { var fs = require('fs'); if (!fs.existsSync(destination)) { this.fs.copyTpl( this.templatePath(origin), this.destinationPath(destination), { packageName: bundle, className: entity, attributs: props, _: _ } ); } else { console.log(chalk.yellow('Warning the file: ') + destination + chalk.yellow(' already exist')); } } }