I've created a base class for my migrations. At the moment I run the artisan migrate command and it creates a new migration that extends the Migrations file, however I want to include my BaseMigration and extend it from there. I've been making this changes manualy but I feel like I'm repeating myself unnecessarily.
Any advice on how to have new migrations automatically extend and load my base migration?
It's doable in a fairly logical way, at least in Laravel 5
Subclass MigrationCreator and override getStubPath(), just copying the function over from the original class (it will use your subclass's __DIR__)
<?php
namespace App\Database;
use Illuminate\Database\Migrations\MigrationCreator;
class AppMigrationCreator extends MigrationCreator
{
public function getStubPath()
{
return __DIR__.'/stubs';
}
}
Write a service provider to override migration.creator with your own subclass (it must be a deferred service provider, because you cannot override a deferred binding with an eager one):
<?php
namespace App\Database;
use Illuminate\Support\ServiceProvider;
class AppMigrationServiceProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->singleton('migration.creator', function ($app) {
return new AppMigrationCreator($app['files']);
});
}
public function provides()
{
return ['migration.creator'];
}
}
Add your service provider to config/app.php after the default ones.
Finally, copy vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs alongside your MigrationCreator subclass (in this example it would become app/Database/stubs) and edit the templates to your needs.
Keep the DummyClass and DummyTable names, as they are replaced with str_replace() to create the actual migrations files.
Since Laravel 7 you can publish stubs using php artisan stub:publish.
The published stubs will be located within a stubs directory in the root of your application. Any changes you make to these stubs will be reflected when you generate their corresponding classes using Artisan make commands.
I don't think you can, because Laravel takes migrations from the vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs folder and you cannot change that, but you have some options:
1) Create your own artisan command migrate:makemyown.
2) Use Jeffrey Way's Laravel Generators. They let you create your migrations by doing:
php artisan generate:migration create_posts_table --fields="title:string, description:text"
If you just have some fields you need to start with and not something more specific than that, it works really fine.
3) Edit Laravel stubs, but the problem is that as soon as you composer update they might get overwritten by Composer.
I believe that there is no way to override this (for now) but I think that you can create your custom command which will use Laravel logic. This was created for Laravel 5.
First you have to create Generator command app/Console/Commands/Generator.php:
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Console\Input\InputArgument;
class Generator extends Command
{
/**
* Command name
*
* #var string
*/
protected $name = 'generate';
/**
* Command description
*
* #var string
*/
protected $description = 'Custom object generator';
/**
* An array with all available generator classes
*
* #var array
*/
protected $types = ['request', 'model', 'middleware'];
/**
* Execute command
*
* #return mixed
*/
public function handle()
{
$type = $this->argument('type');
if (!in_array($type, $this->types)) {
return $this->error('Type must be one of: '.implode(', ', $this->types));
}
// Create new instance
$generatorClass = 'App\Console\Commands\Generators\\'.ucfirst($type);
$generator = new $generatorClass(new Filesystem());
// Each generator has "fire" method
$this->comment($generator->setClassName($this->argument('name'))->fire());
}
/**
* #return array
*/
public function getArguments()
{
return [
['type', InputArgument::REQUIRED, 'Type of class to generate: '.implode(', ', $this->types)],
['name', InputArgument::REQUIRED, 'Name of class to generate'],
];
}
}
Then you have to create an abstract class for all of your Generators classes app/Console/Commands/Generators/Generator.php:
<?php namespace App\Console\Commands\Generators;
use Illuminate\Console\GeneratorCommand;
abstract class Generator extends GeneratorCommand
{
// Directory name with whole application (by default app)
const APP_PATH = 'app';
/*
* Name and description of command wont be used
* Generators Commands are not loaded via Kernel
* Name and description property has been put just to avoid Exception thrown by Symfony Command class
*/
protected $name = 'fake';
protected $description = 'fake';
/**
* Class name to generate
*
* #var string
*/
protected $className;
/**
* Returns class name to generate
*
* #return string
*/
protected function getNameInput()
{
return $this->className;
}
/**
* Returns path under which class should be generated
*
* #param string $name
* #return string
*/
protected function getPath($name)
{
$name = str_replace($this->getAppNamespace(), '', $name);
return self::APP_PATH.'/'.str_replace('\\', '/', $name).'.php';
}
/**
* Sets class name to generate
*
* #param string $name
* #return $this
*/
public function setClassName($name)
{
$this->className = $name;
return $this;
}
/**
* Execute command
*
* #return string
*/
public function fire()
{
$name = $this->parseName($this->getNameInput());
if ($this->files->exists($path = $this->getPath($name)))
{
return $this->type.' already exists!';
}
$this->makeDirectory($path);
$this->files->put($path, $this->buildClass($name));
return $this->type.' '.$this->className.' created successfully.';
}
}
At the end you can create your first Generator class! app/Console/Commands/Generators/Request.php
<?php namespace App\Console\Commands\Generators;
class Request extends Generator
{
/**
* Class type to generate
*
* #var string
*/
protected $type = 'Request';
/**
* Returns default namespace for objects being generated
*
* #param string $rootNamespace
* #return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Http\Requests';
}
/**
* Returns path to custom stub
*
* #return string
*/
public function getStub()
{
return base_path('resources').'/stubs/request.stub';
}
}
Dont forget to add your generate command to Kernel app/Console/Kernel.php:
<?php namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel {
/**
* The Artisan commands provided by your application.
*
* #var array
*/
protected $commands = [
...
'App\Console\Commands\Generator',
...
];
Put your stubs under resources/stubs directory. Let's create first one for Request Generator resources/stubs/request.stub:
<?php namespace {{namespace}};
class {{class}} extends Request
{
/**
* #return bool
*/
public function authorize()
{
// CUSTOM LOGIC
return false;
}
/**
* #return array
*/
public function rules()
{
$rules = [];
// CUSTOM LOGIC
return $rules;
}
}
Then call with php artisan generate request MyRequest.
You can create your custom Model, Middleware, Controller etc. generators, it's very simple - you have to create new generator class under app/Commands/Console/Generators - take a look at Request.php generator to see how it works!
For Laravel 5 you'd edit one of the .stub files in:
vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs
There's no reason why you can't edit those files.
Search in vendor/laravel/framework/src/ for .stub files to find all of the other stubs (templates) artisan uses.
Related
I have an artisan command which gets some options, one of these options is --type=, like below:
protected $signature = 'make:procedure {name} {--type=}';
--type= contains the kind of difference, I want to check this option in the stub because each type has a different namespace which should be used in the stub.
for example, this is my stub:
<?php
namespace DummyNamespace;
class DummyClass
{
//
}
How can I do this, (of course this is an example, I just trying to explain my problem):
<?php
namespace DummyNamespace;
if ($type === 'one') {
echo 'use App\Some\Namespace\One'
}
class DummyClass
{
//
}
It would be highly appreciated if anyone can advise me!😊
your Custom command should derive from GeneratorCommand then you can use abstract Method getStub()
Your Stub File
namespace DummyNamespace;
/**
* Class DummyClass.
*/
class DummyClass
{
}
In Your Command File, you just need to use below code
/**
* Get the stub file for the generator.
*
* #return string
*/
protected function getStub()
{
return app_path('file/path/test.stub');
}
For Explanation Only
In GeneratorCommand class
/**
* Get the stub file for the generator.
*
* #return string
*/
abstract protected function getStub();
/**
* Build the class with the given name.
*
* #param string $name
* #return string
*
* #throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
protected function buildClass($name)
{
$stub = $this->files->get($this->getStub());
return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
}
A very simple way to generate files from stub.
In your stub file
namespace {{namespace}};
/**
* Class {{name}}.
*/
class {{name}}
{
}
Somewhere in your command
protected function getStub()
{
return file_get_contents(resource_path('stubs/dummy.stub'));
}
protected function generate($namespace, $name)
{
$template = str_replace(
['{{namespace}}', '{{name}}'],
[$namespace, $name],
$this->getStub()
);
file_put_contents(app_path("Dummies/$name.php"), $template);
}
So consider the service provider, yes I know I am registering this command in two places, but just give me a moment to explain:
<?php
namespace App\Modules\Core\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Foundation\AliasLoader;
use App\Modules\Core\Handlers\RedirectHandler;
use App\Modules\Core\Console\Commands\CreateAdminUser;
use App\Modules\Core\Values\IsMedicalRecordEmpty;
class CoreProvider extends ServiceProvider
{
protected $commands = [
CreateAdminUser::class,
];
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->bind(RedirectHandler::class, function($app) {
return new RedirectHandler();
});
$this->app->bind(IsMedicalRecordEmpty::class, function($app) {
return new IsMedicalRecordEmpty();
});
}
public function register() {
$this->commands($this->commands);
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
CreateAdminUser::class,
]);
}
}
}
So as stated before we can see that I am registering this command in two places, because I am trying to figure out why calling php artisan doesn't show the command, it only shows if I register it in the app\Console\Kernel, but because I am trying to take a modular approach to the code base, I want to register it in my service provider, to which is registered as such:
'providers' => [
...
/**
* Module Related Providers
*/
App\Modules\Core\Providers\CoreProvider::class,
...
],
I register the provider properly, I (yes I know I don't need to register the command twice) register the command in the way that stack has explained it, either way should in theory work.
But alas the command does not show up when I run php artisan. At all.
The command is simple:
<?php
namespace App\Modules\Core\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
use Illuminate\Auth\Events\Verified;
use App\Modules\Core\Users\Mail\GeneratedAdmin;
use App\Modules\Core\Users\Models\User;
use App\Modules\Core\Users\Services\RegisterUserService;
class CreateAdminUser extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'create:admin {first_name} {last_name} {email}';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Create one admin.';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
// do stuff here ...
}
}
Any ideas?
Your code is redeclaring register(), first in the method with the binds, and then again with your method calling $this->command() - are you even referencing your service provider correctly? PHP should have told you this - it did for me when I tried your sample code...
Whoops\Exception\ErrorException : Cannot redeclare App\Modules\Core\Providers\CoreProvider::register()
It's worth noting that removing the first method with the binds caused the command to show for me.
could you help me with a problem that I have? I have created a command with php artisan make:command to generate repository type classes from existing models. The problem is that I need that instead of generating a single file from a stub, I need to generate 2 or more files. I can't find documentation regarding the subject. Currently what I have achieved is that I only generate a single file from a template.
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;
class MakeRepository extends GeneratorCommand
{
/**
* The console command name.
*
* #var string
*/
protected $name = 'make:repository';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Create a new repository';
/**
* The type of class being generated.
*
* #var string
*/
protected $type = 'Repository';
/**
* #inheritDoc
*/
protected function getStub()
{
return __DIR__ . '/stubs/MakeRepository/ModelRepositoryInterface.stub';
}
/**
* Get the console command arguments.
*
* #return array
*/
protected function getArguments()
{
return [
['name', InputArgument::REQUIRED, 'The name of the model to which the repository will be generated'],
];
}
/**
* Get the default namespace for the class.
*
* #param string $rootNamespace
* #return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Repositories';
}
}
EDIT #1
I have 2 stub files inside the directory:
app/Console/Commands/stubs/MakeRepository
ModelRepository.stub
ModelRepositoryInterface.stub
I want that when you execute the command...ex: php artisan make:repository Blog, these 2 files are created in the following directory:
/app/Repositories/Blog/BlogRepository.php
/app/Repositories/Blog/BlogRepositoryInterface.php
You can write a new command to create repository interface, and then call it in MakeRepository.
I think this method is in line with SRP.
// In MakeRepository.php
// Override handle method
public function handle()
{
if (parent::handle() === false && ! $this->option('force')) {
return false;
}
if ($this->option('interface')) {
$this->call('make:repository-interface', ['name' => $this->getNameInput() . 'Interface']);
}
}
/**
* Get the console command arguments.
*
* #return array
*/
protected function getArguments()
{
return [
['name', InputArgument::REQUIRED, 'The name of the model to which the repository will be generated'],
['interface', 'i', InputOption::VALUE_NONE, 'Create a new interface for the repository'],
];
}
You can also refer to the code of make model of official.
https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Console/ModelMakeCommand.php
you can use glob to get your stubs in array then loop over them to create the multiple files
foreach(glob(stubs_path('stubs/Repository/*.stub') as $stub){
copy(stubs_path('stubs/Repository/'.$stub), $repositoryLocation . 'Repository.php');
}
I need to run one of my Laravel Dusk tests from an artisan command so that it processes daily. I've tried $this->call('dusk'); in my command but that runs all of dusk's tests and doesn't allow me to add a group or a filter. I need to run just 1 test. How can I add a filter?
$this->call('dusk', [ '--group' => 'communication_tests' ]);
or
$this->call('dusk', [ '--filter' => 'tests\Browser\myTestFile::myTestMethod' ]);
doesn't work and it ignores the options passed in. Any ideas on how to accomplish this?
1st create your working Laravel Dusk Test. Test it out with php artisan dusk and make sure it's working.
2nd create your own command in the app\Commands folder called DuskCommand to overwrite laravels native DuskCommand and have it's signature be 'dusk'. Have it extend Laravel\Dusk\Console\DuskCommand and write into its handle method the code below (see https://github.com/laravel/dusk/issues/371 for other version of this code). I edited mine to remove the $this->option('without-tty') ? 3 : 2 ternary statement so it just reads 2 for mine as that option didn't exist or wasn't needed for my version of laravel.
3rd add your new class to the Kernel so that when you call php artisan dusk the class is recognized.
4th create your new command to programmatically run your dusk test using the final line added by #taytus.
5th add that new class to the Kernel also.
Here's my file run down below...
My laravel dusk test (tests\Browser\CommunicationsTest.php) (STEP 1)
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use App\Models\User;
class CommunicationsTest extends DuskTestCase
{
/**
* A Dusk test example.
* #group all_communication_tests
* #return void
*/
public function test_that_all_coms_work()
{
// Test Text
$this->browse(function (Browser $browser) {
$browser->resize(1920, 1080);
$browser->loginAs(7)
->visit('/events/types/appointments')
->assertSee('Automated Messages')
->click('.text_message0')
->pause(1000)
->click('.send-eng-text-btn')
->pause(1000)
->type('phone', env('TESTING_DEVELOPER_PHONE'))
->click('.send-text')
->pause(5000)
->assertSee('Your test text has been sent.');
// Test Email
$browser->visit('/events/types/appointments')
->assertSee('Automated Messages')
->click('.email0')
->assertSee('Automated Messages')
->driver->executeScript('window.scrollTo(595, 1063);');
$browser->click('.send-eng-email-btn')
->pause(2000)
->click('.send-email')
->pause(10000)
->assertSee('Your test email has been sent.');
// Test Call
$browser->visit('/audio/testcall')
->assertSee('Test Call')
->type('phone', env('TESTING_DEVELOPER_PHONE'))
->press('Call')
->pause(3000)
->assertSee('Test Call Queued');
});
}
}
My Overwritting dusk command (app\Console\Commands\DuskCommand.php) (STEP 2)
<?php
namespace App\Console\Commands;
use Laravel\Dusk\Console\DuskCommand as VendorDuskCommand;
use Symfony\Component\Process\ProcessBuilder;
class DuskCommand extends VendorDuskCommand
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'dusk';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Run Tests on our system... by extending the Laravel Vendor DuskCommand.';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$this->purgeScreenshots();
$this->purgeConsoleLogs();
$options=array();
// This line checks if it is a direct call or if has been called from Artisan (we assume is Artisan, more checks can be added)
if($_SERVER['argv'][1]!='dusk'){
$filter=$this->input->getParameterOption('--filter');
// $filter returns 0 if has not been set up
if($filter){
$options[]='--filter';
$options[]=$filter;
// note: --path is a custom key, check how I use it in Commands\CommunicationsTest.php
$options[]=$this->input->getParameterOption('--path');
}
}else{
$options = array_slice($_SERVER['argv'], 2);
}
return $this->withDuskEnvironment(function () use ($options) {
$process = (new ProcessBuilder())
->setTimeout(null)
->setPrefix($this->binary())
->setArguments($this->phpunitArguments($options))
->getProcess();
try {
$process->setTty(true);
} catch (RuntimeException $e) {
$this->output->writeln('Warning: '.$e->getMessage());
}
return $process->run(function ($type, $line) {
$this->output->write($line);
});
});
}
}
Now add that new command to the Kernel so it will register when you call it and overwrite the functionality of the native/vendor DuskCommand. (STEP 3)
/**
* The Artisan commands provided by your application.
*
* #var array
*/
protected $commands = [
// .... preceding commands....
Commands\DuskCommand::class,
];
Now create your command that will call dusk programmatically - here's mine (STEP 4)
<?php
namespace App\Console\Commands;
use DB;
use Illuminate\Console\Command;
class TestCommunications extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'test:communications';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Test Email, Text, and Phone Calls to make sure they\'re operational.';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$response = $this->call('dusk',['--filter'=>'test_that_all_coms_work','--path'=>'tests/Browser/CommunicationsTest.php']);
}
}
and register it in the Kernel (STEP 5)...
/**
* The Artisan commands provided by your application.
*
* #var array
*/
protected $commands = [
.... preceding commands....
Commands\DuskCommand::class,
Commands\TestCommunications::class,
];
Now run php artisan dusk and it should hit the extending DuskCommand and work fine. Then call your new php artisan command that replaces my TestCommunications.php file and it should run dusk perfectly... assuming your test works of course. Let me know if you have any question or if I left anything out.
Remember Dusk only works in a local environment ... this is not something you want/should/natively can implement on a production environment
Trying to make an extension to the core db:seed command to add a couple of necessary options.
Extending the Illuminate\Database\Console\Seeds\SeedCommand
and registering my command in Kernel.php give me following output when running php artisan :
[Illuminate\Contracts\Container\BindingResolutionException]
Target [Illuminate\Database\ConnectionResolverInterface] is not instantiable while building [App\Console\Commands\TenantSeeder].
Any hints what I am missing ? the class itself below :
<?php
namespace App\Console\Commands;
use Illuminate\Database\Console\Seeds\SeedCommand;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
class TenantSeeder extends SeedCommand
{
use ConfirmableTrait;
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'tenant:seed';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Command description';
/**
* The connection resolver instance.
*
* #var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* Create a new database seed command instance.
*
* #param \Illuminate\Database\ConnectionResolverInterface $resolver
* #return void
*/
public function __construct(Resolver $resolver)
{
parent::__construct();
$this->resolver = $resolver;
}
public function getOptions()
{
$opts = parent::getOptions();
return array_merge($opts, [
['tenant', null, InputOption::VALUE_REQUIRED, 'Tenant is required to generate tenant-specific data'],
]);
}
}
You can do it like this if you are using a regular database like MySQL:
<?php
namespace App\Console\Commands;
use Illuminate\Database\Console\Seeds\SeedCommand;
use Illuminate\Database\DatabaseManager;
class CustomSeedCommand extends SeedCommand
{
public function __construct(DatabaseManager $databaseManager)
{
parent::__construct($databaseManager);
}
}