Develop Magento 2 Composer Packages

Magento 2 makes use of the Composer dependency manager tool. Whilst the application will require third party packages and include them when running the composer update command, you may find yourself needing to develop Magento 2 composer packages.

You may wish to do this because you’re working on a Magento theme or module, and you don’t want to submit the package to Packagist and make it public.

Reading the Create a Composer Package Locally post shows that Composer can handle path repositories, therefore supporting local development of packages.

However, if you’re working on a package in Magento 2 and are using templates within your module or theme, you may encounter the following error.

Exception #0 (Magento\Framework\Exception\ValidatorException): Invalid template file: 'your_template.phtml' in module: '[Vendor]_[Module]' block's name: 'your.block.name'

This may look familiar if you’ve worked with modman on Magento 1, involving enabling symlinks in the admin.

Whilst there is an option to enable symlinks within Magento 2’s admin.

Develop Magento 2 Composer Packages

This does not solve the issue when setting the option to Yes.

The problem originates from the isValid() method of the vendor/magento/framework/View/Element/Template/File/Validator.php file.

Specifically, the $this->getRootDirectory()->isFile($this->getRootDirectory()->getRelativePath($filename)) condition returns false, as the package is contained outside of the Magento 2 root directory.

public function isValid($filename)
{
    $filename = str_replace('\\', '/', $filename);
    if (!isset($this->_templatesValidationResults[$filename])) {
        $this->_templatesValidationResults[$filename] =
            ($this->isPathInDirectories($filename, $this->_compiledDir)
                || $this->isPathInDirectories($filename, $this->moduleDirs)
                || $this->isPathInDirectories($filename, $this->_themesDir)
                || $this->_isAllowSymlinks)
            && $this->getRootDirectory()->isFile($this->getRootDirectory()->getRelativePath($filename));
    }
    return $this->_templatesValidationResults[$filename];
}

A couple of resolutions for this include the obvious; moving the package directory within Magento 2’s root directory.

If you do this, ensure that you update the path repository specified in Magento 2’s main composer.json file.

Should you be using Git to version control your Magento root directory, you can also ignore the package directory now within the Magento 2 root directory by adding the directory to your .gitignore file.

The only drawback with using this solution is you may be version controlling your local packages, and including multiple Git repositories within the same directory may cause some headaches when needing to push changes to either the local package’s or Magento 2’s repository.

Another solution would be to rewrite the isValid() in the form of a plugin, so that the condition returns true.

To do this, create a custom module along with the registration.php and module.xml files.

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    '[Vendor]_[Module]',
    __DIR__
);
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="[Vendor]_[Module]" setup_version="1.0.0"/>
</config>

Create a di.xml which will define the plugin.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\Template\File\Validator">
        <plugin name="[module]_view_element_template_file_validator" type="[Vendor]\[Module]\Plugin\FileValidator" sortOrder="10" />
    </type>
</config>

Finally, create the plugin file that will modify the result of the original isValid() method.

<?php 
namespace [Vendor]\[Module]\[Plugin];

class FileValidator
{    
    public function afterIsValid($subject, $result)
    {
        $result = true;
        return $result;
    }
}

It’s important to note that using the plugin should only be for local and test environments and should not be used for Magento stores on a live environment. Disabling template validation and enabling symlinks poses a security risk, so remember to disable this module before adding changes to your production environment.

Note: This article is based on Magento Open Source version 2.2.