VendorPublishCommand.php
TLDR
The file VendorPublishCommand.php
is a part of the Illuminate Foundation Console namespace in the Laravel Illuminate framework. It is a command class that publishes publishable assets from vendor packages. It is responsible for determining the provider or tag(s) to publish, prompting the user to select a provider or tag if not provided, and then publishing the assets for the selected provider or tag.
Methods
handle
The handle
method executes the console command. It sets the start time of the command, determines what should be published (provider or tag), and then calls the publishTag
method for each tag.
determineWhatShouldBePublished
The determineWhatShouldBePublished
method sets the provider and tags to publish based on the command line options. If the --all
option is set, it returns early. Otherwise, it checks if the --provider
and --tag
options are provided. If not, it prompts the user to select a provider or tag using the promptForProviderOrTag
method.
promptForProviderOrTag
The promptForProviderOrTag
method prompts the user to select a provider or tag to publish assets for. It calls the publishableChoices
method to get a list of available choices, and then uses the search
or select
method from the Laravel\Prompts
package to prompt the user for their choice. It parses the user's choice using the parseChoice
method.
publishTag
The publishTag
method publishes the assets for a given tag. It calls the pathsToPublish
method to get the paths to publish for the tag, and then loops over each path and calls the publishItem
method to publish each item. After publishing, it dispatches a VendorTagPublished
event using Laravel's event dispatcher.
pathsToPublish
The pathsToPublish
method returns an array of paths to publish for a given tag. It calls the pathsToPublish
method on the ServiceProvider
class, passing the provider and tag as parameters.
publishItem
The publishItem
method publishes a given item from a source path to a destination path. It checks if the item is a file or a directory, and then calls either the publishFile
or publishDirectory
method accordingly.
publishFile
The publishFile
method publishes a file to a given destination path. It checks if the file should be published based on the --existing
and --force
options. If the file should be published, it creates the parent directory if needed and copies the file to the destination path. It also updates the migration name if needed.
publishDirectory
The publishDirectory
method publishes a directory to a given destination directory. It creates a MountManager
instance with a source and destination filesystem, and then calls the moveManagedFiles
method to move the files within the directory.
moveManagedFiles
The moveManagedFiles
method moves all the files within a given MountManager
instance. It checks if a file should be published based on the --existing
and --force
options. If the file should be published, it writes the file to the destination path.
ensureMigrationNameIsUpToDate
The ensureMigrationNameIsUpToDate
method ensures that the migration name is up-to-date. It checks if the source path matches any of the publishable migration paths and if the destination path contains a migration pattern. If both conditions are met, it updates the destination path with the current timestamp.
status
The status
method writes a status message to the console indicating the item type, source path, and destination path.
Classes
VendorPublishCommand
The VendorPublishCommand
class extends the Command
class and is responsible for publishing publishable assets from vendor packages. It has properties for the filesystem, provider, tags, publishedAt time, command signature, and command description. It implements the handle
method to execute the command and has several helper methods for determining what should be published, prompting the user, and publishing items.
<?php
namespace Illuminate\Foundation\Console;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Foundation\Events\VendorTagPublished;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use League\Flysystem\Filesystem as Flysystem;
use League\Flysystem\Local\LocalFilesystemAdapter as LocalAdapter;
use League\Flysystem\MountManager;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\Visibility;
use Symfony\Component\Console\Attribute\AsCommand;
use function Laravel\Prompts\search;
use function Laravel\Prompts\select;
#[AsCommand(name: 'vendor:publish')]
class VendorPublishCommand extends Command
{
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The provider to publish.
*
* @var string
*/
protected $provider = null;
/**
* The tags to publish.
*
* @var array
*/
protected $tags = [];
/**
* The time the command started.
*
* @var \Illuminate\Support\Carbon|null
*/
protected $publishedAt;
/**
* The console command signature.
*
* @var string
*/
protected $signature = 'vendor:publish
{--existing : Publish and overwrite only the files that have already been published}
{--force : Overwrite any existing files}
{--all : Publish assets for all service providers without prompt}
{--provider= : The service provider that has assets you want to publish}
{--tag=* : One or many tags that have assets you want to publish}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Publish any publishable assets from vendor packages';
/**
* Create a new command instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(Filesystem $files)
{
parent::__construct();
$this->files = $files;
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->publishedAt = now();
$this->determineWhatShouldBePublished();
foreach ($this->tags ?: [null] as $tag) {
$this->publishTag($tag);
}
}
/**
* Determine the provider or tag(s) to publish.
*
* @return void
*/
protected function determineWhatShouldBePublished()
{
if ($this->option('all')) {
return;
}
[$this->provider, $this->tags] = [
$this->option('provider'), (array) $this->option('tag'),
];
if (! $this->provider && ! $this->tags) {
$this->promptForProviderOrTag();
}
}
/**
* Prompt for which provider or tag to publish.
*
* @return void
*/
protected function promptForProviderOrTag()
{
$choices = $this->publishableChoices();
$choice = windows_os()
? select(
"Which provider or tag's files would you like to publish?",
$choices,
scroll: 15,
)
: search(
label: "Which provider or tag's files would you like to publish?",
placeholder: 'Search...',
options: fn ($search) => array_values(array_filter(
$choices,
fn ($choice) => str_contains(strtolower($choice), strtolower($search))
)),
scroll: 15,
);
if ($choice == $choices[0] || is_null($choice)) {
return;
}
$this->parseChoice($choice);
}
/**
* The choices available via the prompt.
*
* @return array
*/
protected function publishableChoices()
{
return array_merge(
['All providers and tags'],
preg_filter('/^/', '<fg=gray>Provider:</> ', Arr::sort(ServiceProvider::publishableProviders())),
preg_filter('/^/', '<fg=gray>Tag:</> ', Arr::sort(ServiceProvider::publishableGroups()))
);
}
/**
* Parse the answer that was given via the prompt.
*
* @param string $choice
* @return void
*/
protected function parseChoice($choice)
{
[$type, $value] = explode(': ', strip_tags($choice));
if ($type === 'Provider') {
$this->provider = $value;
} elseif ($type === 'Tag') {
$this->tags = [$value];
}
}
/**
* Publishes the assets for a tag.
*
* @param string $tag
* @return mixed
*/
protected function publishTag($tag)
{
$pathsToPublish = $this->pathsToPublish($tag);
if ($publishing = count($pathsToPublish) > 0) {
$this->components->info(sprintf(
'Publishing %sassets',
$tag ? "[$tag] " : '',
));
}
foreach ($pathsToPublish as $from => $to) {
$this->publishItem($from, $to);
}
if ($publishing === false) {
$this->components->info('No publishable resources for tag ['.$tag.'].');
} else {
$this->laravel['events']->dispatch(new VendorTagPublished($tag, $pathsToPublish));
$this->newLine();
}
}
/**
* Get all of the paths to publish.
*
* @param string $tag
* @return array
*/
protected function pathsToPublish($tag)
{
return ServiceProvider::pathsToPublish(
$this->provider, $tag
);
}
/**
* Publish the given item from and to the given location.
*
* @param string $from
* @param string $to
* @return void
*/
protected function publishItem($from, $to)
{
if ($this->files->isFile($from)) {
return $this->publishFile($from, $to);
} elseif ($this->files->isDirectory($from)) {
return $this->publishDirectory($from, $to);
}
$this->components->error("Can't locate path: <{$from}>");
}
/**
* Publish the file to the given path.
*
* @param string $from
* @param string $to
* @return void
*/
protected function publishFile($from, $to)
{
if ((! $this->option('existing') && (! $this->files->exists($to) || $this->option('force')))
|| ($this->option('existing') && $this->files->exists($to))) {
$to = $this->ensureMigrationNameIsUpToDate($from, $to);
$this->createParentDirectory(dirname($to));
$this->files->copy($from, $to);
$this->status($from, $to, 'file');
} else {
if ($this->option('existing')) {
$this->components->twoColumnDetail(sprintf(
'File [%s] does not exist',
str_replace(base_path().'/', '', $to),
), '<fg=yellow;options=bold>SKIPPED</>');
} else {
$this->components->twoColumnDetail(sprintf(
'File [%s] already exists',
str_replace(base_path().'/', '', realpath($to)),
), '<fg=yellow;options=bold>SKIPPED</>');
}
}
}
/**
* Publish the directory to the given directory.
*
* @param string $from
* @param string $to
* @return void
*/
protected function publishDirectory($from, $to)
{
$visibility = PortableVisibilityConverter::fromArray([], Visibility::PUBLIC);
$this->moveManagedFiles($from, new MountManager([
'from' => new Flysystem(new LocalAdapter($from)),
'to' => new Flysystem(new LocalAdapter($to, $visibility)),
]));
$this->status($from, $to, 'directory');
}
/**
* Move all the files in the given MountManager.
*
* @param string $from
* @param \League\Flysystem\MountManager $manager
* @return void
*/
protected function moveManagedFiles($from, $manager)
{
foreach ($manager->listContents('from://', true) as $file) {
$path = Str::after($file['path'], 'from://');
if (
$file['type'] === 'file'
&& (
(! $this->option('existing') && (! $manager->fileExists('to://'.$path) || $this->option('force')))
|| ($this->option('existing') && $manager->fileExists('to://'.$path))
)
) {
$path = $this->ensureMigrationNameIsUpToDate($from, $path);
$manager->write('to://'.$path, $manager->read($file['path']));
}
}
}
/**
* Create the directory to house the published files if needed.
*
* @param string $directory
* @return void
*/
protected function createParentDirectory($directory)
{
if (! $this->files->isDirectory($directory)) {
$this->files->makeDirectory($directory, 0755, true);
}
}
/**
* Ensure the given migration name is up-to-date.
*
* @param string $from
* @param string $to
* @return string
*/
protected function ensureMigrationNameIsUpToDate($from, $to)
{
$from = realpath($from);
foreach (ServiceProvider::publishableMigrationPaths() as $path) {
$path = realpath($path);
if ($from === $path && preg_match('/\d{4}_(\d{2})_(\d{2})_(\d{6})_/', $to)) {
$this->publishedAt->addSecond();
return preg_replace(
'/\d{4}_(\d{2})_(\d{2})_(\d{6})_/',
$this->publishedAt->format('Y_m_d_His').'_',
$to,
);
}
}
return $to;
}
/**
* Write a status message to the console.
*
* @param string $from
* @param string $to
* @param string $type
* @return void
*/
protected function status($from, $to, $type)
{
$from = str_replace(base_path().'/', '', realpath($from));
$to = str_replace(base_path().'/', '', realpath($to));
$this->components->task(sprintf(
'Copying %s [%s] to [%s]',
$type,
$from,
$to,
));
}
}