Location: PHPKode > projects > PWF > PWF_0.3.0/Core/FrontController.php
<?php
/**
 * _Core_FrontController Performs all the needed operations for PWF flow. Decides which Controller will be used and what method. 
 * It also builds a communication port with the Controller that instantiate providing its instance as constructor parameter. 
 * Also decides the view template and includes it. 
 * @copyright	Copyright (c) 2012, PWF
 * @license		http://phpwebframework.com/License 
 * @version PWF_0.3.0
 */
class _Core_FrontController
{


  /**
   * Instance of _Core_Path with properties and methods relative to the url path
   * @var _Core_Path
   */
  public $path;

  /**
   * Instance of _Core_Server with properties and methods relative to the variables passed by the server
   * @var _Core_Server
   */
  public $server;

  /**
   * Instance of _Properties generated by the PROPERTIES_FILE constant difined in the index.php
   * @var _Properties
   */
  public $properties;

  /**
   * Instance of _Core_Post with properties and methods relative to data received by post
   * @var _Core_Post
   */
  public $post;

  /**
   * The unique id of the application
   * @var string
   */
  public $applicationId;

  /**
   * If the Controller decides to use an alternative template file than the one normally used 
   * from the properties (MAIN_TEMPLATE or SPECIAL_TEMPLATES) then it sets this variable and 
   * FrontController will use that instead.
   * @var string
   */
  public $specialTemplate;

  /**
   * The server’s path of the directory where index.php is 
   * @var string
   */
  private $publicPath;

  /**
   * The instance of the _Core_ClassLoader that is responsible for autoloading , caching public 
   * static Controller's properties and caching binds. 
   * @var _Core_ClassLoader
   */
  private $loader;

  /**
   * The instance of the current Controller
   * @var _Core_ControllerAbstract
   */
  private $controller;

  /**
   * The reflection class of the current Controller
   * @var ReflectionClass
   */
  private $controllerReflection;

  /**
   * The properAfterRootUrl is the after root url with Capitalized the first letter after / 
   * Controllers paths that may used in properties (SPECIAL_TEMPLATES and PATHS_MAP) must fallow this rule
   * @var string
   */
  private $properAfterRootUrl;

  /**
   * Contructs the FrontController instance (called outside the class - near the end of this file) 
   * Initialize needed parameters and objects and dictates the flow. 
   * @param string $publicPath
   */
  public function __construct($publicPath)
  {
    $this->loader = new _Core_ClassLoader();
    session_start();
    $this->publicPath = $publicPath;
    $this->loadProperties();
    $this->loader->init($this->properties);
    $this->applicationId = _Core_ClassLoader::$applicationId;
    $this->server = new _Core_Server();
    $this->post = new _Core_Post();

    $this->setController();
    $this->startReflection();
    $this->setPublicProperties();
    $this->checkInternalPost();
    $this->invokeMethods();
    $this->includeTempate();
    $this->caching();
  }



  /**
   * Desides which Controller is going to be used 
   * @throws _Exception_FileSystem
   */
  private function setController()
  {

    $rootUrl = _String::toLowerCase($this->server->getProtocol())."://".$this->server->getHttpHost() . "/" . _String::beforeLast($this->server->getPhpSelf(), "/");
    $afterRootUrl = _String::afterFirst(_String::toLowerCase($this->server->getProtocol())."://".$this->server->getHttpHost() . "/" .$this->server->getRedirectUrl(),$rootUrl."/");

    $controllerClass = null;
    $controllerUrl = null;
    $controllerExtension = "";
    $pathVars = array();

    if($afterRootUrl == "")
    {
      // Root Controller
      $controllerCheck = $this->properties->get("CONTROLLERS_FOLDER")."/".$this->properties->get("ROOT_CONTROLLER");
      if(_File::exists($controllerCheck.".php"))
      {
        $controllerClass = _String::replace($controllerCheck, "/", "_");
        $controllerUrl = "";
        $pathVars = _String::split($afterRootUrl, "/");
        $this->properAfterRootUrl = "";
      }
      else
      {
        throw new _Exception_FileSystem($controllerCheck.".php "._Exception_Messages::$fileNotFound, 1234);
      }
    }
    else
    {
      // Find Controller
      $properPathVars = _String::split(_String::capitalizeWordsFirstLetter(_String::replace($afterRootUrl, "/", " ")), " ");
      $this->properAfterRootUrl = _Array::joinValues($properPathVars, "/");
      $properAfterRootUrl = $this->properAfterRootUrl;
      // Search in paths map
      $replacementFrom = null;
      $replacementTo = null;
      $disabledPath = false;
      if($this->properties->containsKey("PATHS_MAP"))
      {
        foreach ($this->properties->get("PATHS_MAP") as $path => $controllerReplacement)
        {
          if(_String::startsWith($properAfterRootUrl, $path,false))
          {
            $disabledPath = false;
            $properAfterRootUrl = trim($controllerReplacement._String::afterFirst($properAfterRootUrl, $path),"/");
            $properPathVars = _String::split($properAfterRootUrl, "/");
            $replacementFrom = $path;
            $replacementTo = $controllerReplacement;
          }
          else if(_String::startsWith($properAfterRootUrl, $controllerReplacement,false))
          {
            $disabledPath = true;
          }
        }
      }

      // Search controller using proper path variables
      while(!$disabledPath && count($properPathVars) > 0 && $controllerClass == null)
      {
        $controllerExtension = "";
        if(_String::contains($properPathVars[count($properPathVars) - 1], "."))
        {
          $proper = $properPathVars[count($properPathVars) - 1];
          $properPathVars[count($properPathVars) - 1] = _String::beforeFirst($proper, ".");
          $controllerExtension = "."._String::afterFirst($proper, ".");
        }
        $properJoinValues = _Array::joinValues($properPathVars, "/");
        $fileToSearch = _String::insesitiveCasePattern($this->properties->get("CONTROLLERS_FOLDER")."/".$properJoinValues);
        $match = _File::match($fileToSearch.".php");
        if(count($match) > 0)
        {

          $controllerClass = _String::replace(_String::beforeLast($match[0],"."), "/", "_");
          if($replacementFrom == null)
          {
            $controllerUrl = _String::substring($afterRootUrl, 0,_String::length($properJoinValues.$controllerExtension));
          }
          else
          {
            $controllerUrl = $replacementFrom._String::substring($properJoinValues.$controllerExtension, _String::length($replacementTo));
          }
          $pathVars = _String::split(trim(_String::afterFirst($afterRootUrl, $controllerUrl),"/"), "/");
        }
        else
        {
          _Array::removeLastElement($properPathVars);
        }
      }

      //If there isn't yet controller it will be the NOT_FOUND_CONTROLLER from properties
      if($controllerClass == null || _String::endsWith($controllerClass, "Abstract",false) || _String::endsWith($controllerClass, "Interface",false))
      {
        $controllerCheck = $this->properties->get("CONTROLLERS_FOLDER")."/".$this->properties->get("NOT_FOUND_CONTROLLER");
        if(_File::exists($controllerCheck.".php"))
        {
          $controllerClass = _String::replace($controllerCheck, "/", "_");
          $controllerUrl = "";
          $pathVars = _String::split($afterRootUrl, "/");
        }
        else
        {
          throw new _Exception_FileSystem($controllerCheck.".php "._Exception_Messages::$fileNotFound, 1234);
        }
      }
    }
    if(count($pathVars)==1 && $pathVars[0] == "")
    {
      $pathVars = array();
    }
    $controllerPath = _String::replace(_String::afterFirst($controllerClass, $this->properties->get("CONTROLLERS_FOLDER")."_"), "_", "/");
    $this->path = new _Core_Path($this->publicPath, $rootUrl, $pathVars, $afterRootUrl, $controllerClass, $controllerPath, $controllerUrl,$controllerExtension);
  }

  /**
   * Starts the reflection constructing the reflection class and the Controller class instances 
   */
  private function startReflection()
  {
    $this->controllerReflection = new ReflectionClass($this->path->getControllerClass());
    $this->controller = $this->controllerReflection->newInstance($this);
  }

  /**
   * Instantiate the properties object based in the PROPERTIES_FILE constant and adds some needed properties to it 
   * if not allready exists
   */
  private function loadProperties()
  {
    $this->properties = new _Properties(PROPERTIES_FILE);
    $this->addPropertyIfNotExists("VIEWS_FOLDER", "View");
    $this->addPropertyIfNotExists("JAVASCRIPTS_FOLDER", "JavaScripts");
    $this->addPropertyIfNotExists("MAIN_TEMPLATE", "template");
    $this->addPropertyIfNotExists("CONTROLLERS_FOLDER", "Controller");
  }

  /**
   * Sets public properties values of the Controller for visit scope caching 
   * using session if those properties have been already cached. 
   * (it excludes public properties starting with underscore _ ) 
   */
  private function setPublicProperties()
  {
    $className = $this->controllerReflection->getName();
    foreach($this->controllerReflection->getProperties() as $property)
    {
      $isSetable = $property->isPublic() && !$property->isStatic() && $property->getDeclaringClass()->getName() != "_Core_ControllerAbstract" && !_String::startsWith($property->getName(), "_");
      if($isSetable)
      {
        if(isset($_SESSION[$this->applicationId."-".$className][$property->getName()]))
        {
          $property->setValue($this->controller,$_SESSION[$this->applicationId."-".$className][$property->getName()]);
        }
      }
    }

  }

  /**
   * Save public properties values of the Controller for visit scope caching 
   * using session. 
   * (it excludes public properties starting with underscore _ ) 
   */
  private function savePublicProperties()
  {
    $className = $this->controllerReflection->getName();
    foreach($this->controllerReflection->getProperties() as $property)
    {
      $isSetable = $property->isPublic() && !$property->isStatic() && $property->getDeclaringClass()->getName() != "_Core_Controller" && !_String::startsWith($property->getName(), "_");
      if($isSetable)
      {
        $value = $property->getValue($this->controller);
        if($value != null)
        {
          $_SESSION[$this->applicationId."-".$className][$property->getName()] = $value;
        }
      }
    }
  }

  /**
   * If the submit function is called by a Controller, then the post data are stored in session.
   * This function checks if there is internal post (from a Controller) and if there is it feels 
   * the $_POST array. 
   */
  private function checkInternalPost()
  {
    if(isset($_SESSION[$this->applicationId]["POST"]))
    {
      unset($_POST);
      $_POST = $_SESSION[$this->applicationId]["POST"];
      unset($_SESSION[$this->applicationId]["POST"]);
    }
  }

  /**
   * The logic of deciding the methods that will be invoked of the current Controller instance and calls to invoke function to invoke them. 
   * If there is onUse then it will be invoked 
   * 
   * 		If there is Post AND (PWF)Get then first will invoke onPost and then onGet 
   * 		Else if there is Post will invoke onPost
   * 		Else if there is (PWF)Get will invoke onGet 
   * 
   * 		If there weren't any invoke from the above logick then onEntry will be invoked 
   * 		(Meaning that it wans't Post or (PWF)Get or that the above check didn't invoked any method from 
   * 		current Controller class (not its parents).  
   * 
   * If there is onView then it will be invoked 
   * 
   */
  private function invokeMethods()
  {
    $this->invoke("onUse");
    $invoked = false;


    if(!$this->post->isEmpty() && $this->path->hasVars())
    {
      $invoked = $this->invoke("onPost") || $this->invoke("onGet");
    }
    else if(!$this->post->isEmpty())
    {
      $invoked = $this->invoke("onPost");
    }
    else if($this->path->hasVars())
    {
      $invoked = $this->invoke("onGet");
    }

    if(!$invoked)
    {
      $this->invoke("onEntry");
    }

    $this->invoke("onView");
  }

  /**
   * It checks that there is a method needed to be invoked in the current Controller , invoke it and returns true if that method 
   * invoked AND was declared in the current Controller class (NOT ITS PARENTS) 
   * @param string $methodName
   * @return boolean
   */
  private function invoke($methodName)
  {
    $invoked = false;
    if($this->controllerReflection->hasMethod($methodName))
    {
      $this->controllerReflection->getMethod($methodName)->invoke($this->controller,"");
      $invoked = $this->controllerReflection->getMethod($methodName)->getDeclaringClass()->getName() == $this->controllerReflection->getName();
    }
    return $invoked;
  }

  /**
   * It stops the flow order and redirect to the given Controller Path imitating the PWF.js submit function. 
   * It keeps post data to session variables , redirects and then FrontController will recognize them as post.
   * @param string $submitData The submit data as defined also for a submit from a page 
   * (e.g. ControlerPath?action:actionData or action or action:actionData or ControlerPath?action if ControllerPath 
   * is not provided the current controller path will be used)
   * @param boolean $clearPostData If true the current data from post will not be used.
   */
  public function submit($submitData,$clearPostData)
  {

    if($clearPostData == true)
    {
      unset($_POST);
    }
    else
    {
      $this->post->removeList("pwf");
      $_SESSION[$this->applicationId]["POST"] = $_POST;
    }

    $action = self::internalReplacement($submitData);
    $toControllerPath = $this->path->getControllerPath();
    if(_String::contains($action, "?"))
    {
      $toControllerPath = _String::beforeFirst($action, "?");
      $action = _String::afterFirst($action, "?");
    }

    if(_String::contains($action, ":"))
    {
      $action = _String::beforeFirst($action, ":");
      $actionData = _String::afterFirst($action, ":");
      if(_String::contains($actionData, "="))
      {
        mb_parse_str($actionData,$result);
        foreach ($result as $key => $value)
        {
          $_SESSION[$this->applicationId]["POST"]["pwf_ActionData_".self::internalRestoration($key)] = self::internalRestoration($value);
        }
      }
    }

    $_SESSION[$this->applicationId]["POST"]["pwf_Action"] = $action;
    $this->link($toControllerPath);

  }

  /**
   * It stops the flow order and redirect to the given url or path of the Controller relative to root()
   * @param $link The url for the redirection or the path of the Controller to redirect to
   */
  public function link($link)
  {
    $this->caching();
    header("Location: ".(_String::startsWith($link, "http",false)?"":$this->path->getRootUrl()."/").$link);
    exit;
  }

  /**
   * Sets the special Template property that will be used for template instead of what would normally would
   * @param string $template
   */
  public function setSpecialTemplate($template)
  {
    $this->specialTemplate = $template;
  }
  
  /**
   * Enter description here ...
   * @param unknown_type $propertyName
   * @param unknown_type $propertyValue
   */
  private function addPropertyIfNotExists($propertyName,$propertyValue)
  {
    if(!$this->properties->containsKey($propertyName))
    {
      $this->properties->set($propertyName, $propertyValue);
    }
  }

  /**
   * Dicides which template file will be used and includes it 
   * @throws _Exception_FileSystem
   */
  private function includeTempate()
  {
    if(!isset($this->specialTemplate))
    {
      if($this->properties->containsKey("SPECIAL_TEMPLATES"))
      {
        $properAfterRootUrl = $this->properAfterRootUrl;
        foreach ($this->properties->get("SPECIAL_TEMPLATES") as $path => $template)
        {
          if(_String::startsWith($properAfterRootUrl, $path,false))
          {
            $file = $this->properties->get("VIEWS_FOLDER")."/".$template.".php";
          }
        }
      }

      if(!isset($file))
      {
        $file = $this->properties->get("VIEWS_FOLDER")."/".$this->properties->get("MAIN_TEMPLATE").".php";
      }
    }
    else
    {
      $file = $this->properties->get("VIEWS_FOLDER")."/".$this->specialTemplate.".php";
    }

    if(_File::exists($file))
    {
      $c = $this->controller;
      include $file;
    }
    else
    {
      throw new _Exception_FileSystem(_String::prepare(_Exception_Messages::$templateFileDontExists, $file), 1234);
    }
  }


  /**
   * It saves properties values for 
   * Visit Scope Caching (for current Controller public properties - not started with underscore) using session 
   * Application Scope Caching (for current Controller public static properties - not started with underscore) using file system
   * Caching Binds (for current Controller public static properties - started with underscore and declared the binding from inside the Controller using an id) using file system
   */
  private function caching()
  {
    $this->savePublicProperties();
    $this->loader->cache();
  }

  /**
   * Performs replacements in special characters in properties and in actionData.
   * (e.g. That way if you want to have = or & in the value of a property then you just 
   * use the escape symbol prior to them \= and \&)
   * @param $str The sting to make the internal replacement
   * @return string
   */
  public static function internalReplacement($str)
  {
    return _String::replace($str, array("\&","\=","\:","[","]","\?"), array("{PWF_AMPERSAND}","{PWF_EQUALS}","{PWF_COLON}","{PWF_LSB}","{PWF_RSB}","{PWF_QUESTION_MARK}"));
  }

  /**
   * Takes a string that have been altered using internalReplacement and changes the special symbols without the 
   * escape character. 
   * @param $str The string where the special characters will be restored 
   * @return string 
   */
  public static function internalRestoration($str)
  {
    return _String::replace($str, array("{PWF_AMPERSAND}","{PWF_EQUALS}","{PWF_COLON}","{PWF_LSB}","{PWF_RSB}","{PWF_QUESTION_MARK}"), array("&","=",":","[","]","?"));
  }
}

// Set the error handler function 
set_error_handler("errorHandler");

// Set the encoding for multibyte string functions 
mb_internal_encoding("UTF-8");


require_once(PWF_VERSION."/Core/ClassLoader.php");

// Sets the publicPath (the current directory) 
$publicPath = getcwd();

// Changes the current directory with the SRC directory difined in the index.php
if(!@chdir(SRC))
{
  handleException(new _Exception_FileSystem(SRC." "._Exception_Messages::$directoryNotFound, 1568));
}

// It defines the PROPERTIES_FILE as properties.pwf index.php didn't 
if(!defined("PROPERTIES_FILE"))
{
  define("PROPERTIES_FILE","properties.pwf");
}

// Instantiate the FrontController with the publicPath as constructor argument 
try
{
  $frontController = new _Core_FrontController($publicPath);
}
catch (Exception $e)
{
  handleException($e);
}
exit;



/**
 * If an exception is allready thrown then it knows it in order not to try to 
 * log the new exception (that could lead to infinite loop in a file system exception 
 * for example writing the log file) 
 */
$exceptionOnProccess = false;

/**
 * The Exception handler function of the project if there is ErrorHandler.php 
 * then it will loaded with the exception as $e. That file can also declare 
 * as $log (boolean) if the exception will be logged into a file and with $logFile the file 
 * (and path) of the error log file. If those two won't declared then it will log the error to 
 * the exceptions.pwf file) 
 * @param Exception $e
 */
function handleException(Exception $e)
{
  global $exceptionOnProccess;
  require_once(PWF_VERSION."/File.php");
  $log = true;
  $logFile = "exceptions.pwf";
  if(!$exceptionOnProccess)
  {
    $exceptionOnProccess = true;
    $filename = "ErrorHandler.php";
    if(_File::exists($filename))
    {
      include $filename;
    }
  }
  if($log)
  {
    _File::append($logFile, "\n".date("Y-m-d H:i:s")." ".getcwd()."\n".$e."\n");
  }
  exit;
}

/**
 * The error handler function for errors generated by PHP 
 * @param $code
 * @param $errstr
 * @param $filename
 * @param $lineno
 */
function errorHandler($code, $errstr, $filename, $lineno )
{
  handleException(new _Exception_FatalError($errstr, $code, 99, $filename, $lineno));
}
?>
Return current item: PWF