Permalink
Browse files

Added base functionality for Zend Framework applications to be organi…

…zed as a library
  • Loading branch information...
1 parent d1b492b commit 54f162a3c0b1ee23b843efa57bff1503a17fa23b @epixa committed May 20, 2010
View
24 LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2010, Court Ewing - Epixa.com
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of Epixa nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTES BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
41 library/Epixa/Application.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa;
+
+require_once 'Zend/Application.php';
+require_once 'Zend/Loader/Autoloader.php';
+
+/**
+ * Extension of Zend_Application that determines config path by environment
+ *
+ * @category Epixa
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+class Application extends \Zend_Application
+{
+ /**
+ * {@inheritdoc}
+ *
+ * Generate the path to the application settings based on the current
+ * application environment.
+ *
+ * @param string $environment
+ * @param string|array|Zend_Config $options Array/Zend_Config of configuration options
+ * @throws Zend_Application_Exception When invalid options are provided
+ * @return void
+ */
+ public function __construct($environment, $options = null)
+ {
+ \Zend_Loader_Autoloader::getInstance()
+ ->registerNamespace('Epixa\\')
+ ->registerNamespace('Zend_');
+
+ $options = array('config' => APPLICATION_ROOT . '/config/settings/' . APPLICATION_ENV . '.php');
+ parent::__construct($environment, $options);
+ }
+}
View
37 library/Epixa/Application/Bootstrap.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa\Application;
+
+/**
+ * Extension of Zend_Application_Bootstrap_Bootstrap that adds Epixa
+ * application resource prefix paths to the pluginloader.
+ *
+ * @category Epixa
+ * @package Application
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+class Bootstrap extends \Zend_Application_Bootstrap_Bootstrap
+{
+ /**
+ * Constructor
+ *
+ * Ensure FrontController resource is registered
+ *
+ * @param Zend_Application|Zend_Application_Bootstrap_Bootstrapper $application
+ * @return void
+ */
+ public function __construct($application)
+ {
@ArondeParon
ArondeParon Jan 31, 2011

Hi,

I am trying currently experimenting with a multiple module setup after getting some inspiration from Epixa, but I cannot seem to figure out why your code does what it does. When I the following in the constructor, it seems to return false, but I don't understand how this can be. Could you enlighten me?

var_dump($this instanceof Zend_Application_Bootstrap_Bootstrap); // returns false --> huh?
@epixa
epixa Jan 31, 2011

Try var_dump($this instanceof \Zend_Application_Bootstrap_Bootstrap); instead. Since we are no longer in the global namespace (and Zend is), you need to use the fully qualified class name.

@ArondeParon
ArondeParon Jan 31, 2011

Thanks for the quick reply, this makes sense! I appreciate it :)

+ $this->getPluginloader()->addPrefixPath(
+ 'Epixa\\Application\\Resource\\',
+ 'Epixa/Application/Resource'
+ );
+
+ parent::__construct($application);
+ }
+}
View
38 library/Epixa/Application/Resource/FrontController.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa\Application\Resource;
+
+use Epixa\Controller\Dispatcher;
+
+/**
+ * Extension of Zend_Application_Resource_Frontcontroller that changes a few
+ * defaults.
+ *
+ * @category Epixa
+ * @package Application
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+class FrontController extends \Zend_Application_Resource_Frontcontroller
+{
+ /**
+ * Disable view renderering, change the module controller directory name,
+ * and pass in our own dispatcher.
+ *
+ * @return \Zend_Controller_Front
+ */
+ public function init()
+ {
+ $dispatcher = new Dispatcher();
+ $this->getFrontController()
+ ->setParam('noViewRenderer', true)
+ ->setDispatcher($dispatcher)
+ ->setModuleControllerDirectoryName('Controller');
+
+ return parent::init();
+ }
+}
View
99 library/Epixa/Application/Resource/Modules.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa\Application\Resource;
+
+/**
+ * Application resource for setting up module autoloading and running module
+ * boostraps.
+ *
+ * @category Epixa
+ * @package Application
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+class Modules extends \Zend_Application_Resource_Modules
+{
+ /**
+ * Initialize the application's modules
+ *
+ * Set up the autoloader for all modules. Call all module bootstraps that
+ * exist.
+ *
+ * @return \ArrayObject
+ */
+ public function init()
+ {
+ $bootstrap = $this->getBootstrap();
+ $bootstrap->bootstrap('FrontController');
+ $front = $bootstrap->getResource('FrontController');
+
+ foreach ($front->getControllerDirectory() as $name => $path) {
+ $this->setupModule($name, dirname($path));
+ }
+
+ return $this->_bootstraps;
+ }
+
+ /**
+ * Set up the module with the given name and path
+ *
+ * @param string $name
+ * @param string $path
+ */
+ public function setupModule($name, $path)
+ {
+ $this->_addToAutoloader($name);
+ $this->_bootstrapModule($name, $path);
+ }
+
+ /**
+ * Add the given name as a 5.3 namespace to the autoloader
+ *
+ * @param string $name
+ */
+ protected function _addToAutoloader($name)
+ {
+ \Zend_Loader_Autoloader::getInstance()
+ ->registerNamespace(sprintf('%s\\', $this->_formatModuleName($name)));
+ }
+
+ /**
+ * Call the module bootstrap, if it exists.
+ *
+ * @param string $name
+ */
+ protected function _bootstrapModule($name, $path)
+ {
+ $bootstrapFile = sprintf(
+ '%s' . DIRECTORY_SEPARATOR . 'Bootstrap.php',
+ $path
+ );
+ if (!file_exists($bootstrapFile)) {
+ return;
+ }
+
+ $bootstrapClass = $this->_getModuleBootstrapClass($name);
+ $bootstrap = new $bootstrapClass();
+ if (!$bootstrap instanceof \Zend_Application_Bootstrap_Bootstrapper) {
+ throw new \LogicException('Module bootstraps must implement Zend_Application_Bootstrap_Bootstrapper');
+ }
+
+ $bootstrap->bootstrap();
+ $this->_bootstraps[$module] = $bootstrap;
+ }
+
+ /**
+ * Get the class of the given module's bootstrap
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _getModuleBootstrapClass($name)
+ {
+ return sprintf('%s\\Bootstrap', $this->_formatModuleName($name));
+ }
+}
View
32 library/Epixa/Application/Resource/View.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa\Application\Resource;
+
+/**
+ * Extension of Zend_Application_Resource_View that does not instantiate
+ * viewRenderer
+ *
+ * @category Epixa
+ * @package Application
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+class View extends \Zend_Application_Resource_View
+{
+ /**
+ * Return the configured view object
+ *
+ * The difference between this and the parent::init is that this does not
+ * isntantiate a viewRenderer object since Epixa specifically disables it.
+ *
+ * @return \Zend_View
+ */
+ public function init()
+ {
+ return $this->getView();
+ }
+}
View
155 library/Epixa/Controller/Dispatcher.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa\Controller;
+
+/**
+ * Extension of Zend_Controller_Dispatcher_Standard that changes expected module
+ * format to more of a library-esque structure.
+ *
+ * @category Epixa
+ * @package Controller
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+class Dispatcher extends \Zend_Controller_Dispatcher_Standard
+{
+ /**
+ * Default controller
+ * @var string
+ */
+ protected $_defaultController = 'Index';
+
+ /**
+ * Default module
+ * @var string
+ */
+ protected $_defaultModule = 'Core';
+
+
+ /**
+ * Formats the the provided controller class name with the appropriate
+ * namespace. Loading is left up to the autoloader.
+ *
+ * @param string $className
+ * @return string Fully qualified controller class name
+ */
+ public function loadClass($className)
+ {
+ return $this->formatClassName($this->_curModule, $className);
+ }
+
+ /**
+ * Format the module name.
+ *
+ * Unlike in Zend Framework, the default module is always namespaced.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatModuleName($unformatted)
+ {
+ return $this->_formatName($unformatted);
+ }
+
+ /**
+ * Get controller class name
+ *
+ * Try request first; if not found, try pulling from request parameter;
+ * if still not found, fallback to default
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return string|false Returns class name on success
+ */
+ public function getControllerClass(\Zend_Controller_Request_Abstract $request)
+ {
+ $controllerName = $request->getControllerName();
+ if (empty($controllerName)) {
+ if (!$this->getParam('useDefaultControllerAlways')) {
+ return false;
+ }
+ $controllerName = $this->getDefaultControllerName();
+ $request->setControllerName($controllerName);
+ }
+
+ $className = $this->formatControllerName($controllerName);
+
+ $controllerDirs = $this->getControllerDirectory();
+ $moduleName = $request->getModuleName();
+
+ if ($this->isValidModule($moduleName)) {
+ $this->_curModule = $this->formatModuleName($moduleName);
+ $this->_curDirectory = $controllerDirs[$this->_curModule];
+ } elseif ($this->isValidModule($this->_defaultModule)) {
+ $request->setModuleName($this->_defaultModule);
+ $this->_curModule = $this->_defaultModule;
+ $this->_curDirectory = $controllerDirs[$this->_defaultModule];
+ } else {
+ throw new \Zend_Controller_Exception('No default module defined for this application');
+ }
+
+ return $className;
+ }
+
+ /**
+ * Determine if a given module is valid
+ *
+ * @param string $module
+ * @return bool
+ */
+ public function isValidModule($module)
+ {
+ if (!is_string($module)) {
+ return false;
+ }
+
+ $module = $this->formatModuleName($module);
+ $controllerDir = $this->getControllerDirectory();
+ foreach (array_keys($controllerDir) as $moduleName) {
+ if ($module == $this->formatModuleName($moduleName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Format action class name
+ *
+ * Unlike in Zend Framework, controllers always have Controller in the
+ * namespace.
+ *
+ * @param string $moduleName Name of the current module
+ * @param string $className Name of the action class
+ * @return string Formatted class name
+ */
+ public function formatClassName($moduleName, $className)
+ {
+ return $this->formatModuleName($moduleName) . '\\Controller\\' . $className;
+ }
+
+ /**
+ * Formats a string from a URI into a PHP-friendly name.
+ *
+ * By default, replaces words separated by the word separator character(s)
+ * with camelCaps. If $isAction is false, it also preserves replaces words
+ * separated by the path separation character with a backslash, making
+ * the following word Title cased. All non-alphanumeric characters are
+ * removed.
+ *
+ * @param string $unformatted
+ * @param boolean $isAction Defaults to false
+ * @return string
+ */
+ protected function _formatName($unformatted, $isAction = false)
+ {
+ $implodedSegments = parent::_formatName($unformatted, $isAction);
+
+ return str_replace('_', '\\', $implodedSegments);
+ }
+}
View
114 library/Epixa/Model/AbstractModel.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Epixa Library
+ */
+
+namespace Epixa\Model;
+
+/**
+ * Abstract model that defines common functionality to access entity properties
+ * in a secure and consistent manner.
+ *
+ * @category Epixa
+ * @package Model
+ * @copyright 2010 epixa.com - Court Ewing
+ * @license http://github.com/epixa/Discuss/blob/master/LICENSE New BSD
+ * @author Court Ewing (court@epixa.com)
+ */
+abstract class AbstractModel
+{
+ /**
+ * Map a call to get a property to its corresponding accessor if it exists.
+ * Otherwise, get the property directly.
+ *
+ * Ignore any properties that begin with an underscore so not all of our
+ * protected properties are exposed.
+ *
+ * @param string $name
+ * @return mixed
+ * @throws \LogicException If no accessor/property exists by that name
+ */
+ public function __get($name)
+ {
+ if ($name[0] != '_') {
+ $accessor = 'get'. ucfirst($name);
+ if (method_exists($this, $accessor)) {
+ return $this->$accessor();
+ }
+
+ if (property_exists($this, $name)) {
+ return $this->$name;
+ }
+ }
+
+ throw new \LogicException(sprintf(
+ 'No property named `%s` exists',
+ $name
+ ));
+ }
+
+ /**
+ * Map a call to set a property to its corresponding mutator if it exists.
+ * Otherwise, set the property directly.
+ *
+ * Ignore any properties that begin with an underscore so not all of our
+ * protected properties are exposed.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return void
+ * @throws \LogicException If no mutator/property exists by that name
+ */
+ public function __set($name, $value)
+ {
+ if ($name[0] != '_') {
+ $mutator = 'set'. ucfirst($name);
+ if (method_exists($this, $mutator)) {
+ $this->$mutator($value);
+ return;
+ }
+
+ if (property_exists($this, $name)) {
+ $this->$name = $value;
+ return;
+ }
+ }
+
+ throw new \LogicException(sprintf(
+ 'No property named `%s` exists',
+ $name
+ ));
+ }
+
+ /**
+ * Map a call to a non-existent mutator or accessor directly to its
+ * corresponding property
+ *
+ * @param string $name
+ * @param array $arguments
+ * @return mixed
+ * @throws \BadMethodCallException If no mutator/accessor can be found
+ */
+ public function __call($name, $arguments)
+ {
+ if (strlen($name) > 3) {
+ if (strpos($name, 'set') === 0) {
+ $property = lcfirst(substr($name, 3));
+
+ $this->$property = array_shift($arguments);
+ return $this;
+ }
+
+ if (0 === strpos($name, 'get')) {
+ $property = lcfirst(substr($name, 3));
+
+ return $this->$property;
+ }
+ }
+
+ throw new \BadMethodCallException(sprintf(
+ 'No method named `%s` exists',
+ $name
+ ));
+ }
+}

0 comments on commit 54f162a

Please sign in to comment.