Magento 2 loses the problem Less file name in a «Compilation from source / Cannot read contents from file» error report

An example of the error report:

Compilation from source: 
adminhtml/Magento/backend/en_US/Df_Framework/main.less
Cannot read contents from file "C:/work/mage2.pro/store/" Warning!file_get_contents(C:/work/mage2.pro/store/): failed to open stream: No such file or directory

As you can see, the problem file name is missed it the strings Cannot read contents from file "C:/work/mage2.pro/store/" and Warning!file_get_contents(C:/work/mage2.pro/store/).

$e->getMessage() here is already misses the problem file name.
Let’s look the exception trace:

Step 1


The $path is already misses the file name here and contains a directory path only.


Step 2

The $path is empty string here, and $this->driver->getAbsolutePath($this->path, $path) returns a directory path without a file name.

See: The \Magento\Framework\Filesystem\Directory\Read::readFile() method should firsly check whether the $path argument is empty string and raise an exception with a proper and clear message in this case instead of the current cryptic message


Step 3

The \Magento\Framework\View\Asset\Source::getContent() method gets an asset name in the $asset argument:

Then $result = $this->preProcess($asset) tries to evaluate the directory and file name for the asset and returns an array with 2 items: the directory and the file name:

But then an asset name is incorrect then the second entry of the array returned by the $result = $this->preProcess($asset) call is empty string and Magento does not check it here.


Step 4

The line $sourceFile = $this->findSourceFile($asset); returns false for a wrong asset name but Magento does not check the result.


Step 5


Step 6


https://github.com/magento/magento2/issues/2467

My fix 1

I propose to fix the Step 4 above.
See the code:


On the first look it seems that we could fix the issue with the code:

if (!$sourceFile) {
	throw new File\NotFoundException(
		new \Magento\Framework\Phrase(
			'Unable to resolve the source file for for "%1"', [$asset->getFilePath()]
		)
	);
}

But it is wrong because Magento 2 supports the virtual assets: a developer can use a virtual name

<css src="Df_Core::core.css"/>

and Magento will looks for core.less.

The right fix is to place a similar code just after the line

And the right fix code is:

if (!$path) {
	throw new File\NotFoundException(
		new \Magento\Framework\Phrase(
			'Unable to resolve the source file for for "%1"', [$asset->getFilePath()]
		)
	);
}

Now we will have a clear message like:

Compilation from source: 
adminhtml/Magento/backend/en_US/Df_Framework/main.less
Unable to resolve the source file for "_df-fieldset/_font.less"

My fix 2

While the issue is not fixed in the core (see the fix 1 above) you can fix it with a plugin:

<type name='Magento\Framework\View\Asset\Source'>
	<plugin
		name='Df\Framework\ViewA\Asset\SourcePlugin'
		type='Df\Framework\ViewA\Asset\SourcePlugin'
	/>
</type>
<?php
namespace Df\Framework\ViewA\Asset;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Phrase;
use Magento\Framework\View\Asset\File\NotFoundException;
use Magento\Framework\View\Asset\Source;
use Magento\Framework\View\Asset\LocalInterface;
class SourcePlugin {
	/**
	 * https://mage2.pro/t/233
	 * «Magento 2 loses the problem Less file name in a «Compilation from source / Cannot read contents from file» error report»
	 * @see \Magento\Framework\View\Asset\Source::getContent()
	 * https://github.com/magento/magento2/blob/2.0.0/lib/internal/Magento/Framework/View/Asset/Source.php#L94-L108
	 * @param Source $subject
	 * @param \Closure $proceed
	 * @param LocalInterface $asset
	 * @return bool|string
	 */
	public function aroundGetContent(Source $subject, \Closure $proceed, LocalInterface $asset) {
		/** @var bool|string $result */
		try {
			$result = $proceed($asset);
		}
		/**
		 * @see \Magento\Framework\Filesystem\Driver\File::fileGetContents()
		 * https://github.com/magento/magento2/blob/2.0.0/lib/internal/Magento/Framework/Filesystem/Driver/File.php#L148-L153
		 */
		catch (FileSystemException $e) {
			throw new NotFoundException(new Phrase(
				'Unable to resolve the source file for "%1"', [$asset->getFilePath()]
			), 0, $e);
		}
		return $result;
	}
}