How to make UrlManager createAbsoluteUrl work with sub-domains

Upon creating my Yii2 Members System, I have ran into a few snags along the way that forced me to extend and bend Yii2 to my will.

I will describe how my app is intended to work, so you know how this article fits into your project needs. My members system is designed to resemble the good ole' days, when we had site.com/members and site.com/admin. It is common to also have them on sub-domains as members.site.com and admin.site.com . The sub-domains add an extra layer of security, especially with the admin on it's own sub-domain separate from the users. Now, that alone wont save you from everything, but as I said, it's just an extra layer. Some of us need them separated like this, because the sub-domains are on different servers. For example, your API could be on an independent server. Or maybe your users and admin sections are on different servers or IPs. If you look at large systems like Microsoft or Adobe, they have many servers for many different uses. Users, API, time, activation, fonts, and many more... So it isn't crazy to want your apps on different sub-domains.

Unfortunately, Yii2 is flat out retarded when it comes to sub-domains. Hopefully they will implement what I am about to show you, or a better version of this. I am always open to a better way to do this.

When I Googled this issue, there wasn't much out there. I found info related to how to link to backend from frontend, and that is even in the official Yii2 docs. That DOES NOT work with sub-domains though! It only works for directories, ie: what comes after your domain, not the sub-domain. I did find one GitHub repo from Postor. Problem was, it wasn't very intuitive and looks like a broken unfinished repo. So I feel the one I made was better.

So in this guide, I am going to show you the best and only way I found to create links to another one of your Yii apps (ie: backend or frontend) that are on a sub-domain.

My method for handling sub-domains

Unfortunately, there isn't an accurate and reliable way to grab only the domain name. A bunch of regex or stripping, or pulling from a huge list of valid domain extensions, etc. Nothing you want running on a site with tons of users, it will create a bottle neck real quick. Yeah, you could implement caching and you should for large volume sites. However, I prefer to cache lean efficient code instead of using caching to save my rear on terrible logic.

So this REQUIRES us to define our domain and sub-domains in our common bootstrap file.

common/config/bootstrap.php

// URL Manager Aliases
Yii::setAlias('@domainName', (YII_ENV === 'dev') ? 'yii2-members-system.dev' : 'yourlivesite.com');
Yii::setAlias('@frontendSubdomain', 'users');
Yii::setAlias('@backendSubdomain', 'admin');

Create common/components/UrlManager.php

<?php
namespace common\components;

use Yii;
use yii\helpers\Url;
use yii\base\InvalidConfigException;

class UrlManager extends yii\web\UrlManager
{
    public $subDomain;
    public $domainName;

    protected $_hostInfo;

    public function getProperDomain()
    {
        if ( ! isset($this->domainName) || empty($this->domainName) ) {
            throw new InvalidConfigException('Request requires a domain name to be configured!');
        }

        $subDomain = (isset($this->subDomain) && !empty($this->subDomain)) ? $this->subDomain : '';
        $domain = empty($subDomain) ? '' : $subDomain . '.';
        $domain .= $this->domainName;

        return $domain;
    }

    public function getHostInfo()
    {
        if ($this->_hostInfo === null)
        {
            $secure = Yii::$app->getRequest()->getIsSecureConnection();
            $http = $secure ? 'https' : 'http';

            if (isset($_SERVER['HTTP_HOST'])) {
                $this->_hostInfo = $http . '://' . $this->getProperDomain();
            } elseif (isset($_SERVER['SERVER_NAME'])) {
                $this->_hostInfo = $http . '://' . $this->getProperDomain();
                $port = $secure ? $this->getSecurePort() : $this->getPort();

                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
                    $this->_hostInfo .= ':' . $port;
                }
            }
        }
        return $this->_hostInfo;
    }

}

Now in whichever app your needing to link to another subdomain, you need to edit it's main config. So if in backend you need to link to frontend (or mainsite needs to link to frontend as in my members system), then edit your backend config.

Add this to your config under the components array:

'urlManagerFrontend' => [
    'class' => 'common\components\UrlManager',
    'subDomain' => Yii::getAlias('@frontendSubdomain'),
    'domainName' => Yii::getAlias('@domainName'),
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
    ],
],

Now, wherever you need to create a link (or get the url for any reason) use CreateAbsoluteUrl inside the new UrlManager like so:

Yii::$app->urlManagerFrontend->createAbsoluteUrl(['site/login'])

In my app, it generates a link for my mainsite to the users app: http://users.yii2-members-system.dev/site/login. This was so on my mainsite I can link to the user's subdomain for them to login.

I hope this helps someone, and I hope Yii actually adopts a solution for this sort of linking and handling sub-domains.