Use Symfony Components to Build WordPress Plugin Maker for CLI – Part 2

In part1, we learned how to create our own console and command. Also, we learned how to use the Symfony Finder component to find files and output results inside CLI.

Now we need to:

  • Allow users to input a new name, author and version of the plugin in CLI.
  • Copy wp-plugin-skeleton folder and rename files based on the new name from the input.
  • Manipulate files content to replace prefixes, suixes, author, version of the plugin with new input data.

Add Command Arguments

Briefly check docs for Symfony Console Arguments here.

We need two required arguments, the name of the new plugin and the author. Also, we need the version argument as optional.

Arguments should be added inside the configure method of the command class. Open our WpPluginMakerCommand and add:

protected function configure(): void
{
    $this->addArgument('plugin_name', InputArgument::REQUIRED, 'Plugin name');
    $this->addArgument('author', InputArgument::REQUIRED, 'Author <email>');
    $this->addArgument('version', InputArgument::OPTIONAL, 'Version in form v1.0.0');
}

Output Argument values in CLI

To be sure that retrieving arguments works fine, output them in CLI for now.

Inside your execute method you can use getArgument method to get the input value.

You can use getArgument method to get the input value:

$input->getArgument(string $name)

Retrieve and output arguments like this:

$plugin_name = $input->getArgument('plugin_name');
$author = $input->getArgument('author');
$version = $input->getArgument('version') ?: 'v1.0.0';
$output->writeln(sprintf('Success! Plugin with name %s, author %s and version %s is created.', $plugin_name, $author, $version ));

Add that before return of the command status:

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $plugin_name = $input->getArgument('plugin_name');
    $author = $input->getArgument('author');
    $version = $input->getArgument('version') ?: '1.0.0';
    $output->writeln(sprintf('Success! Plugin with name %s, author %s and version %s is created.', $plugin_name, $author, $version ));
    return Command::SUCCESS;
}

Command status needs to be at the end of the execute method. We will use Command::SUCCESS here. There is also Command::FAILURE that you can use if some error pops up or Command::INVALID if some argument is missing.

Now test your command in CLI:

$ php app/bin/console app:make-wp-plugin

You will get an error đź™‚ â€śNot enough arguments (missing: “plugin_name, author”)”.

Try again with arguments like this:

$ php app/bin/console app:make-wp-plugin "My new plugin" "Anka Bajurin Stiskalov" "v1.0.2"

You will get a message “Success! Plugin with name My new plugin, author Anka Bajurin Stiskalov and version v1.0.2 is created.”

Create a new plugin folder with Symfony Filesystem component

Now we can pass input arguments to the plugin maker service to create a folder with the new name. But to create a folder, we need a Symfony Filesystem component. Check it’s docs briefly here.

Install Filesystem in your project:

$ composer require symfony/filesystem

Add dependency to Filesystem in the constructor of WPPluginMaker service:

<?php
namespace App\Service;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
class WPPluginMaker
{
    /**
     * @var Finder
     */
    protected $finder;
    /**
     * @var Filesystem
     */
    protected $fileSystem;
    public function __construct()
    {
        $this->finder = new Finder();
        $this->fileSystem = new Filesystem();
    }

Pass the input values to the createNewPluginFromWpPluginSkeleton method:

public function createNewPluginFromWpPluginSkeleton(string $plugin_name, string $author, string $version): \Generator

from the WpPluginMakerCommand:

protected function execute(InputInterface $input, OutputInterface $output): int
{
   $plugin_name = $input->getArgument('plugin_name');
   $author = $input->getArgument('author');
   $version = $input->getArgument('version') ?: 'v1.0.0';
   $fileNames = $this->wpPluginMaker->createNewPluginFromWpPluginSkeleton($plugin_name, $author, $version);

For the folder and file names, you will need a slug version of the name. Add private method slugify to the WPPluginMaker service:

/**
 * Slugify string.
 *
 * @param string $text String to slugify.
 * @param string $replacement
 * @param string $type
 * @return string
 */
 private function slugify(string $text, string $replacement = '-', string $type = 'strtolower'): string
 {
     switch ($type) {
         case 'ucwords':
             $text = ucwords($text);
             break;
         case 'strtoupper':
             $text = strtoupper($text);
             break;
         default:
             $text = strtolower($text);
             break;
     }
      $text = preg_replace('~[^\\pL\d]+~u', $replacement, $text);
      $text = trim($text, $replacement);
      if (function_exists('iconv')) {
         $text = iconv('UTF-8', 'ASCII//TRANSLIT', $text);
     }
      return preg_replace('~[^-\w]+~', '', $text);
 }

Slugify plugin name from the input and create a new folder with Filesystem:

public function createNewPluginFromWpSkeletonPlugin( string $pluginName ): \Generator
{
   $pluginSlug = $this->slugify($pluginName);
   $newPluginLocation = __DIR__.'/../../'.$pluginSlug.'/';
   $this->fileSystem->mkdir($newPluginLocation, 0775);

Run command again:

$ php app/bin/console app:make-wp-plugin "My new plugin" "Anka Bajurin Stiskalov" "v1.0.2"

You should see a new folder at the root of your project:

Wrap $this->fileSystem->mkdir in try catch so you can output an error message in CLI in case of an error.

try {
    if (!$this->fileSystem->exists($newPluginLocation))
    {
        $this->fileSystem->mkdir($newPluginLocation, 0775);
    }else{
        yield "Error creating directory at location ". $newPluginLocation.' Directory already exists.';
    }
} catch (IOExceptionInterface $exception) {
    yield $exception->getMessage();
}

Copy and rename files into a new folder

We created a new folder, but it is empty. So, we need to copy all the files to the new location.

Some classes inside dierent plugins created from this skeleton will always have the same name. Like Service_Container or abstract classes and interfaces that developers can extend and implement. It can be annoying if you are using some smart IDE like PHPStorm. When you start typing Service_Container, you will get confused for a second about which one to pick when all Service Containers from other plugins begin to appear in the list.

That is why wp-plugin-skeleton files have prefixes. Also, class names, constants, package names, etc., use the same prefix too. So we can easily rename them.

Add a private method that handles rename of each of those special cases where we have a prefix or plugin name inside the description:

/**
 * @param string $pluginName
 * @param string $fileContent
 * @return string
 */
 private function replaceContent(string $pluginName, string $fileContent): string
 {
     $fileContent = str_replace( 'Wp Plugin Skeleton', $pluginName, $fileContent);
     $fileContent = str_replace( 'Wp_Plugin_Skeleton', $this->slugify($pluginName, '_', 'ucwords'), $fileContent);
     $fileContent = str_replace( 'wp_plugin_skeleton', $this->slugify($pluginName, '_'), $fileContent);
     $fileContent = str_replace( 'wp-plugin-skeleton', $this->slugify($pluginName), $fileContent);
     return str_replace( 'WP_PLUGIN_SKELETON', $this->slugify($pluginName, '_', 'strtoupper'), $fileContent);
 }

Now inside the loop we need to get files content. We will use str_replace to replace content with our arguments from command line. Then create new files with that content using filesystem dumpFile method:

foreach ($this->finder as $file) {
    $fileNameWithExtension = $file->getRelativePathname();
    $fileContent = $file->getContents();
    $newFileContent = $this->replaceContent($pluginName, $fileContent);
    $newFileNameWithExtension = str_replace('wp-plugin-skeleton', $pluginSlug, $fileNameWithExtension);
    $this->fileSystem->dumpFile($newPluginLocation.$newFileNameWithExtension, $newFileContent);
    yield $newFileNameWithExtension;
}

Run command again and check file names and content inside to see if everything is renamed correctly:

$ php app/bin/console app:make-wp-plugin "My new plugin" "Anka Bajurin Stiskalov" "v1.0.2"

Rename author

You are almost done. The only thing that is left is to rename the author and plugin version.

Renaming the author is plain simple. First, add the author parameter to the replaceContent and pass the input argument author to it.

Replace old author with str_replace method:

/**
* @param string $pluginName
* @param string $fileContent
* @param string $author
* @return string
*/
private function replaceContent(string $pluginName, string $fileContent, string $author): string
{
   $fileContent = str_replace( 'Anka Bajurin Stiskalov', $author, $fileContent);

Rename plugin version

The plugin version visible in the WordPress admin page is located in file wp-plugin-skeleton/wp-plugin-skeleton.php, so you can target that file only to change the version.

if($fileNameWithExtension === 'wp-plugin-skeleton.php'){
    $newFileContent = preg_replace("%[0-9.]+[0-9.]+[0-9]%i", $version, $newFileContent);
}

Or you can remove conditions and target all version number occurrences in all files.

Create hidden files

The only thing that is left is to create hidden files .gitignore and .deployignore:

private function createIgnoredFiles( string $newPluginLocation ): void
{
    $this->fileSystem->dumpFile($newPluginLocation.'.gitignore', '/vendor');
    $this->fileSystem->dumpFile($newPluginLocation.'.deployignore', '# /vendor');
}

Final conclusion

Run your command again with dierent arguments and check the files:

$ php app/bin/console app:make-wp-plugin "Final plugin" "Mark Markić" "2.10.12"

You can now test your new plugin. Copy/paste the final-plugin folder into your plugins folder of the WordPress project. Then, run composer install inside final-plugin folder and activate it inside WordPress admin.

Final solution

You made it! You have your plugin maker!

Whenever you build some good code design and you know that you will need the same pattern or solution over and over, upgrade wp-plugin-skeleton with an example and tag it with a new version in the git repository. Each version should have a clear name and description.

Whenever you need to add new stu inside a plugin, that is created from older versions of your wp plugin skeleton, it will be much easier if you just run a command to create a plugin with the same name as existing one and copy/paste necessary files and code that you need.

That is what I do, especially because of the Service_Container singleton. When you are building a WordPress plugin, you don’t have any framework that handles service containers and other stu for you. If you are developing something dierent than the default WordPress stu, you are out of luck.

You can check our final solution here.


Leave a Reply

Your email address will not be published. Required fields are marked *