`magento` appears in URL when "Use Web Server Rewrites" set to "No" and in CLI environment

In an internal test case, we found a strange behavior that when trying to build URL in custom CLI commands, the URL sometimes contains an unexpected magento in its URI part. In this blog, let's examine why this happens and how to workaround it.

Behavior

Run the following code in CLI environment.

/** @var \Magento\Store\Api\Data\StoreInterface $store */
echo $store->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_LINK) . "\n";

Sometimes, you will get the output like http://example.com/magento/.

However, the expected output is http://example.com/ or http://example.com/index.php/.

So where does the magento come from?

The Cause

This is due to a bug in \Magento\Store\Model\Store::_updatePathUseRewrites method.

/**
 * Append script file name to url in case when server rewrites are disabled
 *
 * @param   string $url
 * @return  string
 */
protected function _updatePathUseRewrites($url)
{
    if ($this->getForceDisableRewrites() || !$this->getConfig(self::XML_PATH_USE_REWRITES)) {
        if ($this->_isCustomEntryPoint()) {
            $indexFileName = 'index.php';
        } else {
            $scriptFilename = $this->_request->getServer('SCRIPT_FILENAME');
            // phpcs:ignore Magento2.Functions.DiscouragedFunction
            $indexFileName = is_string($scriptFilename) ? basename($scriptFilename) : '';
        }
        $url .= $indexFileName . '/';
    }
    return $url;
}

Note this line:

$indexFileName = is_string($scriptFilename) ? basename($scriptFilename) : '';

In CLI environment, $scriptFilename is bin/magento, so basename($scriptFilename) becomes magento.

That's where the magento comes from.

Conditions

By analyzing the above code, we can also figure out the conditions that can trigger this bug.

  • CLI but not in crontab area
  • System configuration "Use Web Server Rewrites" is set to "No"
  • URL type is URL_TYPE_LINK or URL_TYPE_DIRECT_LINK

Cron is unaffected, why?

However, the getBaseUrl method works correctly in crontab area.

The reason is that during the Object Manager initialization in crontab area, its "custom entry parameter" is set to true.

(\Magento\Cron\Console\Command\CronCommand#L122)

$omParams[Store::CUSTOM_ENTRY_POINT_PARAM] = true;

Then this initialization parameter is injected to \Magento\Store\Model\Store.

(vendor/magento/module-store/etc/di.xml#L98)

<type name="Magento\Store\Model\Store">
    <arguments>
        <argument name="session" xsi:type="object" shared="false">Magento\Framework\Session\Generic\Proxy</argument>
        <argument name="isCustomEntryPoint" xsi:type="init_parameter">Magento\Store\Model\Store::CUSTOM_ENTRY_POINT_PARAM</argument>
        <argument name="url" xsi:type="object" shared="false">Magento\Framework\UrlInterface</argument>
    </arguments>
</type>

Workaround

Now, we know the conditions that can trigger the bug, the workaround is pretty straightforward.

This workaround does not add extra dependencies.

$type = \Magento\Framework\UrlInterface::URL_TYPE_LINK;
/** @var \Magento\Store\Api\Data\StoreInterface $store */
$baseUrl = $store->getBaseUrl($type);
echo $baseUrl . "\n";

if ((PHP_SAPI === 'cli')
    &&
    ($store->getForceDisableRewrites()
    || !$store->getConfig(\Magento\Store\Model\Store::XML_PATH_USE_REWRITES))
    &&
    ($type === UrlInterface::URL_TYPE_LINK || $type === UrlInterface::URL_TYPE_DIRECT_LINK)
    &&
    // phpcs:ignore Magento2.Security.Superglobal.SuperglobalUsageWarning
    (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] === 'bin/magento')
) {
    $baseUrl = preg_replace(
        '|^(?i)(https?)(?-i)://([^/\?#]*)/magento/(.*)$|u',
        '${1}://${2}/index.php/${3}',
        $baseUrl
    );
}
echo $baseUrl . "\n";

Output

http://example.com/magento/
http://example.com/index.php/

Related Issues

  • Bug when "Use Web Server Rewrites" is set to "No".
    https://github.com/magento/magento2/issues/25976
  • As we confirmed, this bug is still not fixed on this blog post date.
  • This bug may not be obvious due to the fact that most production environments set "Use Web Server Rewrites" to "Yes".

If you found this blog post helpful, please share it!