We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Multisite Set up with shared views, controllers and models

We had a situation where we need to setup multi site with shared views , templates , controllers, models with site specific templates ,controllers and models too.

edited Aug '16

https://monkpal.com/Multisite-Set-up-with-shared-views-controllers-and-modals-Phalcon

multisite/shared
├── apps
│   ├── common
│   │   ├── controllers        (Register namespace Common/Controller)
│   │   │   ├── IndexController.php   
│   │   │   ├── LoginController.php
│   │   │   └── ProductsController.php
│   │   ├── models             (Register namespace Common/Model)
│   │   │   └── Products.php  
│   │   └── views
│   │       ├── login
│   │       │   └── index.volt
│   │       └── products
│   │       |    └── index.volt
|   |       └──index.volt   
│   ├── example.com
│   │   ├── controllers
│   │   │   ├── IndexController.php (extend Common/Controller)
│   │   │   ├── LoginController.php  (extend Common/Controller)
│   │   │   ├── ProductsController.php (extend Common/Controller)
│   │   │   └── UsersController.php   Site Specific Controller
│   │   ├── models
│   │   │   └── Products.php (extend Common/Model)
|   |   |   └── Users.php (Site Specific Model)
│   │   └── views
│   │       └── products             (Other view templates will refer to Common view folder)
│   │           └── index.volt
│   ├── example2.com
│   │   ├── controllers
│   │   │   ├── IndexController.php (extend Common/Controller)
│   │   │   ├── ProductsController.php (extend Common/Controller)
│   │   │   └── SitespecificController.php   Site Specific Controller
│   │   ├── models
│   │   │   └── Products.php (extend Common/Model)
|   |   |   └── SiteSpecific.php (Site Specific Model)
│   │   └── views
│   │       └── sitespecific        (Other view templates will refer to Common view folder)
│   │           └── index.volt
└── public
    └── example.com   (Will contain Js CS Images to support site specific theme)
    └── example2.com  (Will contain Js CS Images to support site specific theme)
    └── index.php


85.5k
edited Aug '16

Steps to setup multiple site with different domain name

Steps to acheive it

  • Step 1 : Register the namespaces of common controllers and models
  • Step 2 : Extend the phalcon view engine to cascading the view (say for example View engine will look for specific template file in site specific view folder if its not exist it will look in common views folder, there is no need to replicate all the template files in all sites views directories, you can overwrite single template file alone).
  • Step 3 : Extend Phalcon volt to provide Skin path for tempaltes
  • Step 4: Create site specfic Volt cache folder
  • Step 5 : Create seperate folders with sitenames in public folder for js/css/images
  • Step 6: Create common contollers, views, modals
  • Step 7: Extend common controllers , modals in site specific folderss , Views wil be taken from common folder. if you want to overwrite any template you can overwiite that alone no need all view folder.
  • Step 8 : Set sitename by current domain name. this sitename will be used to register contollers models directries
  • Step 9: Set two views directory one is common and another is sitename (thi can be done only if you have extened the phalcon view to add two directories refer step 2)

Files extended are here this should be in your public directory.

custom/CustomVolt.php

  <?php

  namespace Custom;

  use Phalcon\Mvc\View\Engine\Volt;
   use Phalcon\Mvc\View\Engine\Volt\Compiler;

      class CustomVolt extends Volt
      { 
           public function getCompiler()
          {
           if (!$this->_compiler) {
              $this->_compiler = new VoltCompilerExtension($this->getView());
              $this->_compiler->setOptions($this->getOptions());
              $this->_compiler->setDI($this->getDI());
          }
          return $this->_compiler;
       }
  }

  class VoltCompilerExtension extends Volt\Compiler
      {
      public function compileFile($path, $compiledPath, $extendsMode = null)
      {
          $skinPath = $this->getOption('skinPath');
           if ($skinPath) {
              $skinTemplate = str_replace(
              $this->getDI()->getView()->getViewsDir(),
              $skinPath,
               $path
            );

               if (is_readable($skinTemplate)) {
              $path = $skinTemplate;
               }
          }
          return parent::compileFile($path, $compiledPath, $extendsMode);
       }

      }

custom/CustomView.php

  <?php 
  namespace Custom;

  use Phalcon\Mvc\View\Exception;
  use Phalcon\Mvc\View;
  use Phalcon\Cache\BackendInterface;

  class CustomView extends View
  {
      protected $_viewsDirs;
      /**
      * @var
      */
       protected $_eventsManager;
      /**
      * @param $path
       *
      * @return $this
      */
       public function addViewsDir($path)
      {
          $this->_viewsDirs = $path;
           $this->setViewsDir($path);
          return $this;
      }
      /**
      * @param $view
      * @param array $vars
      *
      * @return string
      */
      public function getPartial($view, $vars = [])
      {
          ob_start();
          $this->partial($view, $vars);
           $content = ob_get_contents();
           ob_end_clean();
          return $content;
       }

protected function _engineRender($engines, $viewPath, $silence, $mustClean, BackendInterface $cache = NULL)
{
    if (is_object($cache)) {
        throw new Exception('Cache view not supported...');
        return;
    }
    $viewsDirs = is_array($this->_viewsDirs) ? array_reverse($this->_viewsDirs) : [$this->_viewsDir];
    $notExists = true;
    $viewEnginePath = null;
    foreach ($engines as $extension => $engine) {
        foreach ($viewsDirs as $viewsDir) {
            $viewsDirPath   = $this->_basePath . $viewsDir . $viewPath;
            $viewEnginePath = $viewsDirPath . $extension;
            if (is_file($viewEnginePath)) {
                if (is_object($this->_eventsManager)) {
                    $this->_activeRenderPath = $viewEnginePath;
                    if($this->_eventsManager->fire('view:beforeRenderView', $this, $viewEnginePath) === false) {
                        break;
                    }
                }
                $engine->render($viewEnginePath, $this->_viewParams, $mustClean);
                if (is_object($this->_eventsManager)) {
                    $this->_eventsManager->fire('view:afterRenderView', $this);
                }
                $notExists = false;
                break 2;
            }
        }
    }
    if ($notExists) {
        if (is_object($this->_eventsManager)) {
            $this->_activeRenderPath = $viewEnginePath;
            $this->_eventsManager->fire('view:notFoundView', $this);
        }
        if (!$silence) {
            $exceptionMessage = 'View "'.($viewPath).'" was not found in the views directories';
            throw new Exception($exceptionMessage);
            return;
          }
       }
   } 
  }

index.php

<?php

  use Phalcon\Loader;
  use Phalcon\Mvc\Application;
   use Phalcon\Di\FactoryDefault;
  use Phalcon\Mvc\Url as UrlProvider;
   use Custom\CustomVolt;
  use Custom\CustomView;

   if($_SERVER['HTTP_HOST'] == "example.com") {

      define('SITENAME',"example.com" );

  }
   if($_SERVER['HTTP_HOST'] == "example2.com") {

      define('SITENAME',"example2.com" );

   }  

      define('APP_PATH', realpath('..') . '/');

try {

  $loader = new Loader();

$loader->registerNamespaces(array(
    'Common\Controller' => '../app/common/controllers',
    'Common\Model' => '../app/common/models',
    'Custom'  => 'custom'
))->register();

$loader->registerDirs(array(
    '../app/'.SITENAME.'/controllers/',

    '../app/'.SITENAME.'/models/'
))->register();

$di = new FactoryDefault();

$di->set(
    'voltService',
    function ($view, $di) {
        $volt = new CustomVolt($view, $di);
        $volt->setOptions(
            array(
                "compiledPath"      => "../cache/volt/".SITENAME."/",
                "compiledExtension" => ".compiled",
                'compileAlways' => true,
                'skinPath' => '../app/'.SITENAME.'/views/'
            )
        );

        return $volt;
    }
);

$di->set(
    'view',
    function () {
        $view = new CustomView();
        $view->addViewsDir(array('../app/common/views/','../app/'.SITENAME.'/views/'));
        $view->registerEngines(
            array(
                ".volt" => 'voltService'
            )
        );
        return $view;
    }
);

$application = new Application($di);
$response = $application->handle();
$response->send();

  }   catch (\Exception $e) {
      echo "Exception: ", $e->getMessage();
   }

To render Js and css site specific in volt tempaltes

You use can like this

    {{ stylesheet_link(constant('SITENAME') ~'/css/main.css') }}
    {{ javascript_include(constant('SITENAME') ~'/js/main.js') }}

skeleton in github https://github.com/karthikeyan-unimity/phalcon

something complicated.

Drupal approach: https://www.drupal.org/docs/7/multisite-drupal/multi-site-sharing-the-same-code-base

in Phalcon my recomendation:

- app
    - default
        - config
            - config.php
            - loader.php
            - services.php
        - lang
    - other_site
    sites.php
- core
- web
    index.php

site.php

<?php

$sites['u-w-u.local'] = 'default';

index.php

<?php

use Phalcon\Di;
use Phalcon\Mvc\Micro;

error_reporting(E_ALL);

define('APP_PATH', realpath('..'));
define('WEB_PATH', realpath('.'));

/**
 *  Enable multisite
 */
$sites = [];

include '../app/sites.php';

$siteDomain = $_SERVER['SERVER_NAME'];

if (isset($sites[$siteDomain]) && file_exists(APP_PATH . '/app/' . $sites[$siteDomain] . '/config/config.php')) {
    $sitePath = APP_PATH . '/app/' . $sites[$siteDomain];

    try {
        $di = new Di();

        /**
         * Include Services
         */
        include $sitePath . '/config/services.php';

        require '../autoload.php';

        /**
         * Call the autoloader service.  We don't need to keep the results.
         */
        $di->getLoader();

        /**
         * Starting the application
         * Assign service locator to the application
         */
        $app = new Micro($di);

        /**
         * Debug
         */
        if ($app->config->dev_environment) {
            Kint::$theme = 'solarized';
            $debug = new \Phalcon\Debug();
            $debug->listen();
        }

        /**
         * Include Application
         */
        foreach ($app->config->modules as $module => $config) {
            if ($config['active'] == true) {
                include APP_PATH . '/core/modules/' . $module . '/' . $module . '.routing.php';
                if ($config['api'] == true) {
                    include APP_PATH . '/core/modules/' . $module . '/' . $module . '.routing.api.php';
                }
            }
        }

        /**
         * Handle the request
         */
        $app->handle();
    } catch (\Exception $e) {
        echo $e->getMessage() . '<br>';
        echo '<pre>' . $e->getTraceAsString() . '</pre>';
    }

} else {
    // TODO: Mejorar el mensaje de site no encontrado
    echo "Index.php: Acceso no permitido.";
}

hopefully help you.



12.1k

Hi all. I've worked around https://monkpal.com/Multisite-Set-up-with-shared-views-controllers-and-modals-Phalcon doing some change.

I have a new directory for templates in the root

templates
├── frontend
│ ├── common
│ │ ├── layouts
│ │ │ ├── index.volt
│ │ │ ├── cart.volt
| │ | └── ... etc etc
│ │ │
│ │ ├── views
│ │ │
│ │ └── partials
│ │ │
│ │ └── layouts.volt │ │
│ ├── sitename1
│ │ ├── layouts
│ │ │ ├── index.volt
│ │ │ ├── cart.volt
| │ | └── ... etc etc
│ │ │
│ │ ├── views
│ │ │
│ │ └── partials │ │ │
│ │ └── layouts.volt │ ├── sitename2
│ │ ├── layouts
│ │ │ ├── index.volt
│ │ │ ├── cart.volt
| │ | └── ... etc etc
│ │ │
│ │ ├── views
│ │ │
│ │ └── partials
│ │ │
│ │ └── layouts.volt

there is a common directory for default templates and a directory for every site. Sitename files , if exist, override common. In every dir there is a file layouts.volt for the general template. In layouts subdir there are the general controller templates. In views subdir there are the action templates. In partials subdir there are the partials templates.

The view service init is:

<?php

    $di->setShared(
        'view',
        function () {
              $conf=$this->get('config');
              $mainView='layout';
              $engines = array(
                  ".volt" => 'voltService',
                  ".phtml" => "Phalcon\Mvc\View\Engine\Php"
              );
              $view = new \Extended\ExtendedView();
              $view->addViewsDir(array(TEMPLATE_PATH.'frontend/common/'.$conf->application->viewsDir, TEMPLATE_PATH.'frontend/'.SITENAME.'/'.$conf->application->viewsDir));
              $view->addTemplatesDir(array(TEMPLATE_PATH.'frontend/common/', TEMPLATE_PATH.'frontend/'.SITENAME.'/'));
              // Set the engine
              $view->registerEngines($engines);
              $view->setMainView($view->getRightPath($mainView,$engines).$mainView); //main layout 
            return $view;
        }
    );

The ExtendedView class modified in order to manage partials and other templates override.

<?php
namespace Extended;

use Phalcon\Mvc\View\Exception;
use Phalcon\Mvc\View;
use Phalcon\Cache\BackendInterface;

class ExtendedView extends View
{
    protected $_viewsDirs, $_templatesDirs;
    /**
     * @var
     */
    protected $_eventsManager;
    /**
     * @param $path
     *
     * @return $this
     */
    public function addViewsDir($path)
    {
       $this->_viewsDirs = $path;
       //$this->setViewsDir($path);
        return $this;
    }
    public function addTemplatesDir($path)
    {
       $this->_templatesDirs = $path;
       //$this->setViewsDir($path);
        return $this;
    }
    public function getRightPath($viewPath, $engines = [])
    {
      $templatesDirs = is_array($this->_templatesDirs) ? array_reverse($this->_templatesDirs) : [$this->_templatesDirs];
      if(empty($engines)) $engines = $this->_loadTemplateEngines();
      foreach ($engines as $extension => $engine) {
          foreach ($templatesDirs as $viewsDir) {
              $viewsDirPath   = $this->_basePath . $viewsDir . $viewPath; //directory completa con nome della vista finale
              $viewEnginePath = $viewsDirPath . $extension; //path completo del file con estensione es .volt .phtml
              if (is_file($viewEnginePath)) {
                  if($viewsDir) return $viewsDir; //imposta la dir della view
              }
          }
      }
      return null;
    }
    /**
     * @param $path
     *
     * @return $this

    public function addPartialsDir($path)
    {
       $this->_partialsDirs = $path;
       $this->setPartialsDir($path);
        return $this;
    }
     */
    /**
     * @param $view
     * @param array $vars
     *
     * @return string
     */
    public function getPartial($view, $vars = [])
    {
        ob_start();
        $this->partial($view, $vars);
        $content = ob_get_contents();
        ob_end_clean();
        return $content;
    }

    protected function _engineRender($engines, $viewPath, $silence, $mustClean, BackendInterface $cache = null){
      //$silence=false;
      //echo "($engines, $viewPath, $silence, $mustClean, BackendInterface $cache = null)";
      /* */
      $viewsDirs = is_array($this->_viewsDirs) ? array_reverse($this->_viewsDirs) : [$this->_viewsDir];
      $notExists = true; //?
      $viewEnginePath = null;
      foreach ($engines as $extension => $engine) {
          foreach ($viewsDirs as $viewsDir) {
              $viewsDirPath   = $this->_basePath . $viewsDir . $viewPath; //directory completa con nome della vista finale
              $viewEnginePath = $viewsDirPath . $extension; //path completo del file con estensione es .volt .phtml
              if (is_file($viewEnginePath)) {
                  if($viewsDir) $this->setViewsDir($viewsDir); //imposta la dir della view
                  return parent::_engineRender($engines, $viewPath, $silence, $mustClean, $cache);
              }
          }
      }
      return parent::_engineRender($engines, $viewPath, $silence, $mustClean, $cache);

    }
    public function render($controllerName, $actionName, $vars = []){
      /**
      * Check if there is a layouts directory set
      */
      $layoutsDir = $this->_layoutsDir;
      if (!$layoutsDir) {
        $layoutsDir = "layouts/";
      }
      /**
      * Check if the user has defined a custom layout
      */
      $layout = $this->_layout;
      if ($layout) {
        $layoutName = $layout;
      } else {
        $layoutName = $controllerName;
      }
      $this->_layoutsDir=$this->getRightPath($layoutsDir.$layoutName).$layoutsDir;
      return parent::render($controllerName, $actionName, $vars);
    }
    public function partial($partialPath, $vars = [])
      {
      if(!$this->_partialsDir) $this->_partialsDir='partials/';
      $this->_partialsDir=$this->getRightPath($this->_partialsDir.$partialPath).$this->_partialsDir;
      return parent::partial($partialPath, $vars);
    }
}

I think it's more complete and usable by web designers now. In my project I use modules in order to separate backend and frontend, mantaining the multisite only in the frontend module only.