Laminas MVC: Configure Automatic Search Engine Protection

Hello from the other side! I’ve been developing web applications for a couple of years using Laminas MVC (formerly known as Zend Framework 3) and one thing that frustrate me the most in my past experiences is, when my development and staging sites were accidentally index by one of the search engines such as Google, Bing, Yahoo, DuckDuckGo, Baidu and Yandex. A common mistake that I need to think about and begin to deal with.

Now, I’d like to share my solution through this tutorial on how you can properly configure an automatic search engine protection in Laminas MVC. This tutorial is also inspired by Symfony 4.3: Automatic Search Engine Protection.

The good thing I like about Laminas MVC is its approach of configuration over convention, it means that I have the absolute control and knowledge on what I want to execute over my application. So before it will work, we have to do some configurations first and I assume that you have the basic knowledge in PHP and Laminas MVC (formerly known as Zend Framework 3).

Now lets get started. First we need to add a few handful of array data to our local configuration.

config/autoload/local.php
<?php 

return [ 
    ...
    'seo_manager' => [
        'disallow_search_engine_index' => false,
        'disallow_google_tag_manager' => false,
    ],
];

This will be our default value when we run our application in production. As you can see I also included to disallow Google Tag Manager, this is due to the fact that I only want to see or track necessary traffic to my Google Analytics when its on production.

Next is we need to add another handful of array data to our local development configuration.

config/autoload/development.local.php
<?php 

return [ 
    ...
    'seo_manager' => [ 
        'disallow_search_engine_index' => true, 
        'disallow_google_tag_manager' => true, 
    ], 
];

And this will be our default value when we run our application in development or staging. The option name is negative, so the “true” means: “protect my site and don’t index it” and “remove Google Tag Manager to avoid recording unnecessary load of traffic”.

The next thing we have to do is to configure the application module. See the code and the explanation below.

module/Application/src/Module.php
/**
 * @param MvcEvent $mvcEvent
 */
public function onBootstrap(MvcEvent $mvcEvent) 
{
    // Get shared event manager.
    $sharedEventManager = $mvcEvent->getApplication()->getEventManager()->getSharedManager();

    // Register the event listener method.
    $sharedEventManager->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, [$this, 'onDispatch'], 100);
}

/**
 * @param MvcEvent $mvcEvent
 */
public function onDispatch(MvcEvent $mvcEvent)
{
     // Call setSeoManager and pass MvcEvent as argument when callback onDispatch is triggered from event dispatch. 
    $this->setSeoManager($mvcEvent);
}

/**
 * @param MvcEvent $mvcEvent
 */
public function setSeoManager(MvcEvent $mvcEvent) 
{
    // Get config using service manager.
    $config = $mvcEvent->getApplication()->getServiceManager()->get('config');

    // Check if seo_manager exist in our config
    if (isset($config['seo_manager'])) {
        $seoManager = $config['seo_manager'];

        // Get the ViewModel of the layout
        $viewModel = $mvcEvent->getViewModel();

        // Check if disallow_search_engine_index exist in our config 
        // and assign it to disallowSearchEngineIndex view variable, 
        // in this way we can use and access it from our layout.
        if (isset($seoManager['disallow_search_engine_index'])) { 
            $viewModel->setVariable('disallowSearchEngineIndex', $seoManager['disallow_search_engine_index']);
        }

        // Check if disallow_google_tag_manager exist in our config 
        // and assign it to disallowGoogleTagManager view variable, 
        // in this way we can use and access it from our layout.
        if (isset($seoManager['disallow_google_tag_manager'])) {
            $viewModel->setVariable('disallowGoogleTagManager', $seoManager['disallow_google_tag_manager']);
        }
    }
}

That’s probably be it and our Module.php is now setup and configure the way we expect it to behave.​

 

The final steps we will do is to utilize those assigned view variables to our layout.

module/Application/view/layout/layout.phtml
<?php echo $this->doctype(); ?>

<html lang="en">
    <head>
        // Check if disallowSearchEngineIndex is set and is equal to true, 
        // and if all conditions are met—then protect my site and don't index it.
        <?php if (isset($this->disallowSearchEngineIndex) && $this->disallowSearchEngineIndex) : ?>
            <?php $this->headMeta()->appendName('robots', 'noindex, nofollow'); ?>
        <?php endif; ?>

        <?php echo $this->headMeta(); ?>

        // Check if disallowGoogleTagManager is not set or is equal to false, 
        // and if either of the two conditions are met—then include Google Tag Manager to my layout else exclude it.
        <?php if (! isset($this->disallowGoogleTagManager) || ! $this->disallowGoogleTagManager) : ?>
        <!-- Google Tag Manager -->
        <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
                    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
                j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
                'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','dataLayer','GTM-EXAMPLE');</script>
        <!-- End Google Tag Manager -->
        <?php endif; ?>
    </head>
    <body>
         // Check if disallowGoogleTagManager is not set or is equal to false, 
         // and if either of the two conditions are met—then include Google Tag Manager to my layout else exclude it.
        <?php if (! isset($this->disallowGoogleTagManager) || ! $this->disallowGoogleTagManager) : ?>
        <!-- Google Tag Manager (noscript) -->
        <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-EXAMPLE" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
        <!-- End Google Tag Manager (noscript) -->
        <?php endif; ?>
    </body>
</html>

Now let me explain why we have two separate configuration for production (config/config/autoload/local.php) and development (config/config/autoload/development.local.php). The reason behind it is to utilize the Development Mode that Laminas MVC offers.

When we enable development mode using:

php composer.phar development-enable

or

composer development-enable

the data from this file config/autoload/development.local.php will be prioritize and overwrite any configuration that matches in config/autoload/local.php and then merged into one application config.

And when we disable development mode using:

php composer.phar development-disable

or

composer development-disable

the config/autoload/development.local.php file that were created are then removed, leaving only the .dist version. In short only config/autoload/local.php will be merge into one application config.

You can test the status of development mode using:

php composer.phar development-status

or

composer development-status

That’s it, we have now configure our automatic search engine protection and we can now switch it easily using Development Mode. Have a great day!

 

The fact that I don’t believe that I’m better than anyone else gives me an inevitable sense of superiority.