Laravel的包存放在vendor目录下面。
例如way,可以是一个供应商代号,其目录下面有一个generators目录。
在src目录下面有Way/Generators目录,里面存放真正的代码文件。提供一个GeneratorServiceProvider.php文件,实现了多个派生自
Illuminate\Console\Command类的命令。其中包括:
generate:view
generate:model
generate:controller
generate:migration
generate:seeder
generate:pivot
generate:resource
generate:scaffold
generate:publisher
1. GeneratorSeriveProvider
其全部代码如下:<?php namespace Way\Generators; use Illuminate\Support\ServiceProvider; use Way\Generators\Commands\ControllerGeneratorCommand; use Way\Generators\Commands\ModelGeneratorCommand; use Way\Generators\Commands\ResourceGeneratorCommand; use Way\Generators\Commands\SeederGeneratorCommand; use Way\Generators\Commands\PublishTemplatesCommand; use Way\Generators\Commands\ScaffoldGeneratorCommand; use Way\Generators\Commands\ViewGeneratorCommand; use Way\Generators\Commands\PivotGeneratorCommand; class GeneratorsServiceProvider extends ServiceProvider { /** * Indicates if loading of the provider is deferred. * * @var bool */ protected $defer = false; /** * Booting */ public function boot() { $this->package('way/generators'); } /** * Register the commands * * @return void */ public function register() { foreach([ 'Model', 'View', 'Controller', 'Migration', 'Seeder', 'Pivot', 'Resource', 'Scaffold', 'Publisher'] as $command) { $this->{"register$command"}(); } } /** * Register the model generator */ protected function registerModel() { $this->app['generate.model'] = $this->app->share(function($app) { $generator = $this->app->make('Way\Generators\Generator'); return new ModelGeneratorCommand($generator); }); $this->commands('generate.model'); } /** * Register the view generator */ protected function registerView() { $this->app['generate.view'] = $this->app->share(function($app) { $generator = $this->app->make('Way\Generators\Generator'); return new ViewGeneratorCommand($generator); }); $this->commands('generate.view'); } /** * Register the controller generator */ protected function registerController() { $this->app['generate.controller'] = $this->app->share(function($app) { $generator = $this->app->make('Way\Generators\Generator'); return new ControllerGeneratorCommand($generator); }); $this->commands('generate.controller'); } /** * Register the migration generator */ protected function registerMigration() { $this->app['generate.migration'] = $this->app->share(function($app) { return $this->app->make('Way\Generators\Commands\MigrationGeneratorCommand'); }); $this->commands('generate.migration'); } /** * Register the seeder generator */ protected function registerSeeder() { $this->app['generate.seeder'] = $this->app->share(function($app) { $generator = $this->app->make('Way\Generators\Generator'); return new SeederGeneratorCommand($generator); }); $this->commands('generate.seeder'); } /** * Register the pivot generator */ protected function registerPivot() { $this->app['generate.pivot'] = $this->app->share(function($app) { return new PivotGeneratorCommand; }); $this->commands('generate.pivot'); } /** * Register the resource generator */ protected function registerResource() { $this->app['generate.resource'] = $this->app->share(function($app) { $generator = $this->app->make('Way\Generators\Generator'); return new ResourceGeneratorCommand($generator); }); $this->commands('generate.resource'); } /** * register command for publish templates */ public function registerpublisher() { $this->app['generate.publish-templates'] = $this->app->share(function($app) { return new publishtemplatescommand; }); $this->commands('generate.publish-templates'); } /** * register scaffold command */ public function registerScaffold() { $this->app['generate.scaffold'] = $this->app->share(function($app) { return new ScaffoldGeneratorCommand; }); $this->commands('generate.scaffold'); } /** * Get the services provided by the provider. * * @return array */ public function provides() { return array(); } }
其中$this->commands('generate.scaffold')命令调用了ServiceProvider的commands.
public function commands($commands) { $commands = is_array($commands) ? $commands : func_get_args(); // To register the commands with Artisan, we will grab each of the arguments // passed into the method and listen for Artisan "start" event which will // give us the Artisan console instance which we will give commands to. $events = $this->app['events']; $events->listen('artisan.start', function($artisan) use ($commands) { $artisan->resolveCommands($commands); }); }
其中每一个命令都是派生自GeneratorCommand类。
<?php namespace Way\Generators\Commands; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class ControllerGeneratorCommand extends GeneratorCommand { <?php namespace Way\Generators\Commands; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Way\Generators\Parsers\MigrationNameParser; use Way\Generators\Parsers\MigrationFieldsParser; use Way\Generators\Generator; use Way\Generators\SchemaCreator; use Config; class MigrationGeneratorCommand extends GeneratorCommand { <?php namespace Way\Generators\Commands; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class ModelGeneratorCommand extends GeneratorCommand { <?php namespace Way\Generators\Commands; use Illuminate\Support\Facades\File; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class ViewGeneratorCommand extends GeneratorCommand {
2. GeneratorCommand
我们来看看GeneratorCommand的代码:
<?php namespace Way\Generators\Commands; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Illuminate\Console\Command; use Way\Generators\Filesystem\FileAlreadyExists; use Way\Generators\Generator; use Config; abstract class GeneratorCommand extends Command { /** * @var \Way\Generators\ModelGenerator */ protected $generator; /** * @param Generator $generator */ public function __construct(Generator $generator) { $this->generator = $generator; parent::__construct(); } /** * Fetch the template data * * @return array */ protected abstract function getTemplateData(); /** * The path where the file will be created * * @return mixed */ protected abstract function getFileGenerationPath(); /** * Get the path to the generator template * * @return mixed */ protected abstract function getTemplatePath(); /** * Compile and generate the file */ public function fire() { $filePathToGenerate = $this->getFileGenerationPath(); try { $this->generator->make( $this->getTemplatePath(), $this->getTemplateData(), $filePathToGenerate ); $this->info("Created: {$filePathToGenerate}"); } catch (FileAlreadyExists $e) { $this->error("The file, {$filePathToGenerate}, already exists! I don't want to overwrite it."); } } /** * Get a directory path either through a * command option, or from the configuration * * @param $option * @param $configName * @return string */ protected function getPathByOptionOrConfig($option, $configName) { if ($path = $this->option($option)) { return $path; } return Config::get("generators::config.{$configName}"); } /** * Get the console command options. * * @return array */ protected function getOptions() { return [ ['path', null, InputOption::VALUE_REQUIRED, 'Where should the file be created?'], ['templatePath', null, InputOption::VALUE_REQUIRED, 'The location of the template for this generator'] ]; } }
该类有三个虚函数
getTemplateData
getFileGenerationPath //创建的文件存放路径
getTemplatePath //模板文件的路径
并且最终调用了Generator类的make函数来生成相应的内容。
3. Generator
其全部代码如下:
<?php namespace Way\Generators; use Way\Generators\Filesystem\Filesystem; use Way\Generators\Compilers\TemplateCompiler; use Way\Generators\UndefinedTemplate; class Generator { /** * @var Filesystem */ protected $file; /** * @param Filesystem $file */ public function __construct(Filesystem $file) { $this->file = $file; } /** * Run the generator * * @param $templatePath * @param $templateData * @param $filePathToGenerate */ public function make($templatePath, $templateData, $filePathToGenerate) { // We first need to compile the template, // according to the data that we provide. $template = $this->compile($templatePath, $templateData, new TemplateCompiler); // Now that we have the compiled template, // we can actually generate the file. $this->file->make($filePathToGenerate, $template); } /** * Compile the file * * @param $templatePath * @param array $data * @param TemplateCompiler $compiler * @throws UndefinedTemplate * @return mixed */ public function compile($templatePath, array $data, TemplateCompiler $compiler) { return $compiler->compile($this->file->get($templatePath), $data); } }
4. generator:view 是如何执行的?
4.1 ServiceProvider Register
/** * Register the view generator */ protected function registerView() { $this->app['generate.view'] = $this->app->share(function($app) { $generator = $this->app->make('Way\Generators\Generator'); return new ViewGeneratorCommand($generator); }); $this->commands('generate.view'); }
$this->app->make是从container中取出单一实例化的对象。
4.2 ViewGeneratorCommand fire
4.2.1 getFileGenerationPath
/** * Create directory tree for views, * and fire generator */ public function fire() { $directoryPath = dirname($this->getFileGenerationPath()); if ( ! File::exists($directoryPath)) { File::makeDirectory($directoryPath, 0777, true); } parent::fire(); }
/** * The path where the file will be created * * @return mixed */ protected function getFileGenerationPath() { $path = $this->getPathByOptionOrConfig('path', 'view_target_path'); $viewName = str_replace('.', '/', $this->argument('viewName')); return sprintf('%s/%s.blade.php', $path, $viewName); }
protected function getPathByOptionOrConfig($option, $configName) { if ($path = $this->option($option)) { return $path; } return Config::get("generators::config.{$configName}"); }
为什么我们输入php artisan generate:view xxxx.yyyy的时候,新建的文件yyyy.php会出现在app/views/xxxx下面?
就是因为下面这段代码:
$viewName = str_replace('.', '/', $this->argument('viewName'));
首先判断path参数是否指定,如果没有指定的话,那么就从默认的Config里面去取generators::config.view_target_path字段。
<?php return [ /* |-------------------------------------------------------------------------- | Where the templates for the generators are stored... |-------------------------------------------------------------------------- | */ 'model_template_path' => 'vendor/way/generators/src/Way/Generators/templates/model.txt', 'scaffold_model_template_path' => 'vendor/way/generators/src/Way/Generators/templates/scaffolding/model.txt', 'controller_template_path' => 'vendor/way/generators/src/Way/Generators/templates/controller.txt', 'scaffold_controller_template_path' => 'vendor/way/generators/src/Way/Generators/templates/scaffolding/controller.txt', 'migration_template_path' => 'vendor/way/generators/src/Way/Generators/templates/migration.txt', 'seed_template_path' => 'vendor/way/generators/src/Way/Generators/templates/seed.txt', 'view_template_path' => 'vendor/way/generators/src/Way/Generators/templates/view.txt', /* |-------------------------------------------------------------------------- | Where the generated files will be saved... |-------------------------------------------------------------------------- | */ 'model_target_path' => app_path('models'), 'controller_target_path' => app_path('controllers'), 'migration_target_path' => app_path('database/migrations'), 'seed_target_path' => app_path('database/seeds'), 'view_target_path' => app_path('views') ];
4.2.2 getTemplatePath
protected function getTemplatePath() { return $this->getPathByOptionOrConfig('templatePath', 'view_template_path'); }
从template文件中读取相应的数据,并进行替换,然后再通过filesystem的make函数将数据写入到文件里面就OK.
<?php // Composer: "fzaninotto/faker": "v1.3.0" use Faker\Factory as Faker; class $CLASS$ extends Seeder { public function run() { $faker = Faker::create(); foreach(range(1, 10) as $index) { $MODEL$::create([ ]); } } }
/** * Fetch the template data * * @return array */ protected function getTemplateData() { $tableName = $this->getTableName(); return [ 'CLASS' => "{$tableName}TableSeeder", 'MODEL' => str_singular($tableName) ]; }
class Filesystem { /** * Make a file * * @param $file * @param $content * @throws FileAlreadyExists * @return int */ public function make($file, $content) { if ( $this->exists($file)) { throw new FileAlreadyExists; } return file_put_contents($file, $content); }
5. 流程图
ViewGeneratorCommand::fire()
GeneratorCommand::fire()
ViewGeneratorCommand::getFileGenerationPath()
ViewGeneratorCommand::getTemplatePath
ViewGeneratorCommand::getTemplateData
Generator::make()
TemplateCompiler::compile()
FileSystem::make()