diff --git a/lib/Pi/Application/Engine/Command.php b/lib/Pi/Application/Engine/Command.php new file mode 100644 index 0000000000..ca5ec04e0c --- /dev/null +++ b/lib/Pi/Application/Engine/Command.php @@ -0,0 +1,48 @@ + + */ +class Command extends Standard +{ + /** + * {@inheritDoc} + */ + const SECTION = 'command'; + + /** + * {@inheritDoc} + */ + protected $fileIdentifier = 'command'; + + /** + * {@inheritDoc} + */ + public function application() + { + if (!$this->application) { + $options = isset($this->options['application']) + ? $this->options['application'] : array(); + $this->application = Application::load($options); + $this->application->setEngine($this)->setSection($this->section()); + } + + return $this->application; + } +} diff --git a/lib/Pi/Command/Mvc/Application.php b/lib/Pi/Command/Mvc/Application.php new file mode 100755 index 0000000000..3dff4dc902 --- /dev/null +++ b/lib/Pi/Command/Mvc/Application.php @@ -0,0 +1,86 @@ + + */ +class Application extends PiApplication +{ + // Default listenser, @see Zend\Mvc\Application + + /** + * Load application handler + * + * @param array $configuration + * @return $this + */ + public static function load($configuration = array()) + { + $smConfig = isset($configuration['service_manager']) + ? $configuration['service_manager'] : array(); + $listeners = isset($configuration['listeners']) + ? $configuration['listeners'] : array(); + $serviceManager = new ServiceManager( + new Service\ServiceManagerConfig($smConfig) + ); + + return $serviceManager->get('Application')->setListeners($listeners); + } + + /** + * Bootstrap application + * + * @param array $listeners + * @return \Pi\Command\Mvc\Application + */ + public function bootstrap(array $listeners = array()) + { + $serviceManager = $this->serviceManager; + $events = $this->events; + + $listeners = array_unique(array_merge($this->defaultListeners, $listeners)); + + foreach ($listeners as $listener) { + $events->attach($serviceManager->get($listener)); + } + + // Set custom router + $router = $serviceManager->get('ConsoleRouter'); + $router->addRoute('Standard', [ + 'name' => 'default', + 'type' => 'Pi\Command\Mvc\Router\Http\Standard', + 'options' => [ + 'route' => 'default', + ], + ], 0); + + // Setup MVC Event + $this->event = $event = new MvcEvent(); + $event->setTarget($this); + $event->setApplication($this) + ->setRequest($this->request) + ->setRouter($router); + + // Trigger bootstrap events + $events->trigger(MvcEvent::EVENT_BOOTSTRAP, $event); + return $this; + } +} diff --git a/lib/Pi/Command/Mvc/RouteListener.php b/lib/Pi/Command/Mvc/RouteListener.php new file mode 100644 index 0000000000..7ce2fdd8f9 --- /dev/null +++ b/lib/Pi/Command/Mvc/RouteListener.php @@ -0,0 +1,68 @@ + + */ +class RouteListener extends AbstractListenerAggregate +{ + /** + * Attach to an event manager + * + * @param EventManagerInterface $events + * @return void + */ + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute')); + } + + /** + * Listen to the "route" event and attempt to route the request + * + * If no matches are returned, triggers "dispatch.error" in order to + * create a 404 response. + * + * Seeds the event with the route match on completion. + * + * @param MvcEvent $e + * @return null|Router\RouteMatch + */ + public function onRoute($e) + { + $target = $e->getTarget(); + $request = $e->getRequest(); + $router = $e->getRouter(); + $routeMatch = $router->match($request); + + if (!$routeMatch instanceof RouteMatch) { + $e->setError(Application::ERROR_ROUTER_NO_MATCH); + + $results = $target->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $e); + if (count($results)) { + $return = $results->last(); + } else { + $return = $e->getParams(); + } + return $return; + } + + $e->setRouteMatch($routeMatch); + return $routeMatch; + } +} diff --git a/lib/Pi/Command/Mvc/Router/Http/Standard.php b/lib/Pi/Command/Mvc/Router/Http/Standard.php new file mode 100755 index 0000000000..154a2c0d57 --- /dev/null +++ b/lib/Pi/Command/Mvc/Router/Http/Standard.php @@ -0,0 +1,219 @@ + + */ +class Standard implements RouteInterface +{ + /** + * List of assembled parameters. + * + * @var array + */ + protected $assembledParams = array(); + + /** + * @var RouteMatcherInterface + */ + protected $matcher; + + /** + * Path prefix + * @var string + */ + protected $prefix = ''; + + /** + * Delimiter between structured values of module, controller and action. + * @var string + */ + protected $structureDelimiter = '/'; + + /** + * Delimiter between keys and values. + * @var string + */ + protected $keyValueDelimiter = '/'; + + /** + * Delimiter before parameters. + * @var array + */ + protected $paramDelimiter = '/'; + + /** + * Default values. + * @var array + */ + protected $defaults = array( + 'module' => 'system', + 'controller' => 'index', + 'action' => 'index' + ); + + /** + * Create a new simple console route. + * + * @param string|RouteMatcherInterface $routeOrRouteMatcher + * @param array $constraints + * @param array $defaults + * @param array $aliases + * @param null|array|Traversable|FilterChain $filters + * @param null|array|Traversable|ValidatorChain $validators + * @throws InvalidArgumentException + */ + public function __construct( + $routeOrRouteMatcher, + array $constraints = array(), + array $defaults = array(), + array $aliases = array(), + $filters = null, + $validators = null + ) { + if (is_string($routeOrRouteMatcher)) { + $this->matcher = new DefaultRouteMatcher($routeOrRouteMatcher, $constraints, $defaults, $aliases); + } elseif ($routeOrRouteMatcher instanceof RouteMatcherInterface) { + $this->matcher = $routeOrRouteMatcher; + } else { + throw new InvalidArgumentException( + "routeOrRouteMatcher should either be string, or class implementing RouteMatcherInterface. " + . gettype($routeOrRouteMatcher) . " was given." + ); + } + } + + /** + * factory(): defined by Route interface. + * + * @see \Zend\Mvc\Router\RouteInterface::factory() + * @param array|Traversable $options + * @throws InvalidArgumentException + * @return self + */ + public static function factory($options = array()) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (!is_array($options)) { + throw new InvalidArgumentException(__METHOD__ . ' expects an array or Traversable set of options'); + } + + if (!isset($options['route'])) { + throw new InvalidArgumentException('Missing "route" in options array'); + } + + foreach (array( + 'constraints', + 'defaults', + 'aliases', + ) as $opt) { + if (!isset($options[$opt])) { + $options[$opt] = array(); + } + } + + if (!isset($options['validators'])) { + $options['validators'] = null; + } + + if (!isset($options['filters'])) { + $options['filters'] = null; + } + + + return new static( + $options['route'], + $options['constraints'], + $options['defaults'], + $options['aliases'], + $options['filters'], + $options['validators'] + ); + } + + /** + * match(): defined by Route interface. + * + * @see Route::match() + * @param Request $request + * @param null|int $pathOffset + * @return RouteMatch + */ + public function match(Request $request, $pathOffset = null) + { + if (!$request instanceof ConsoleRequest) { + return null; + } + + $params = $request->getParams()->toArray(); + //$matches = $this->matcher->match($params); + + $path = array_shift($params); + $structureParams = explode($this->structureDelimiter, $path); + if (count($structureParams) < 3) { + return null; + } + $matches = [ + 'module' => array_shift($structureParams), + 'controller' => array_shift($structureParams), + 'action' => array_shift($structureParams), + ]; + if (!empty($structureParams)) { + return null; + } + + $this->defaults = array_merge($this->defaults, $matches); + $matches['args'] = $params; + + if (null !== $matches) { + return new RouteMatch($matches); + } + return null; + } + + /** + * assemble(): Defined by Route interface. + * + * @see \Zend\Mvc\Router\RouteInterface::assemble() + * @param array $params + * @param array $options + * @return mixed + */ + public function assemble(array $params = array(), array $options = array()) + { + $this->assembledParams = array(); + } + + /** + * getAssembledParams(): defined by Route interface. + * + * @see RouteInterface::getAssembledParams + * @return array + */ + public function getAssembledParams() + { + return $this->assembledParams; + } +} diff --git a/lib/Pi/Command/Mvc/Service/ApplicationFactory.php b/lib/Pi/Command/Mvc/Service/ApplicationFactory.php new file mode 100755 index 0000000000..f67b13623a --- /dev/null +++ b/lib/Pi/Command/Mvc/Service/ApplicationFactory.php @@ -0,0 +1,33 @@ + + */ +class ApplicationFactory extends ZendApplicationFactory +{ + /** + * {@inheritDoc} + */ + public function createService(ServiceLocatorInterface $serviceLocator) + { + return new Application( + $serviceLocator->get('Config'), + $serviceLocator + ); + } +} diff --git a/lib/Pi/Command/Mvc/Service/RouterFactory.php b/lib/Pi/Command/Mvc/Service/RouterFactory.php new file mode 100755 index 0000000000..4d357f7e6d --- /dev/null +++ b/lib/Pi/Command/Mvc/Service/RouterFactory.php @@ -0,0 +1,62 @@ +has('Config') ? $serviceLocator->get('Config') : array(); + + // Defaults + $routerClass = 'Zend\Mvc\Router\Http\TreeRouteStack'; + $routerConfig = isset($config['router']) ? $config['router'] : array(); + + // Console environment? + if ($rName === 'ConsoleRouter' // force console router + || ($cName === 'router' && Console::isConsole()) // auto detect console + ) { + // We are in a console, use console router defaults. + $routerClass = 'Zend\Mvc\Router\Console\SimpleRouteStack'; + $routerConfig = isset($config['console']['router']) ? $config['console']['router'] : array(); + } + + // Obtain the configured router class, if any + if (isset($routerConfig['router_class']) && class_exists($routerConfig['router_class'])) { + $routerClass = $routerConfig['router_class']; + } + + // Inject the route plugins + if (!isset($routerConfig['route_plugins'])) { + $routePluginManager = $serviceLocator->get('RoutePluginManager'); + $routerConfig['route_plugins'] = $routePluginManager; + } + + // Obtain an instance + $factory = sprintf('%s::factory', $routerClass); + return call_user_func($factory, $routerConfig); + } +} diff --git a/lib/Pi/Mvc/Controller/CommandController.php b/lib/Pi/Mvc/Controller/CommandController.php new file mode 100755 index 0000000000..512924dcf7 --- /dev/null +++ b/lib/Pi/Mvc/Controller/CommandController.php @@ -0,0 +1,94 @@ + + */ +abstract class CommandController extends AbstractActionController +{ + /** + * Execute the request + * + * @param MvcEvent $e + * @return mixed + * @throws \DomainException + */ + public function onDispatch(MvcEvent $e) + { + $actionResponse = null; + $result = $this->preAction($e); + if (false !== $result) { + //$actionResponse = parent::onDispatch($e); + + $routeMatch = $e->getRouteMatch(); + if (!$routeMatch) { + /** + * @todo Determine requirements for when route match is missing. + * Potentially allow pulling directly from request metadata? + */ + throw new Exception\DomainException('Missing route matches; unsure how to retrieve action'); + } + + $action = $routeMatch->getParam('action', 'not-found'); + $method = static::getMethodFromAction($action); + + if (!method_exists($this, $method)) { + $method = 'notFoundAction'; + } + $args = $routeMatch->getParam('args', []); + + $actionResponse = call_user_func_array([$this, $method], $args); + $e->setResult($actionResponse); + + $this->postAction($e, $actionResponse); + } + + return $actionResponse; + } + + public function preAction($e) + { + Pi::service('log')->mute(); + } + + public function postAction($e) + { + return true; + } + + /** + * Get name of current module + * + * @return string + */ + public function getModule() + { + return $this->getEvent()->getRouteMatch()->getParam('module'); + } + + /** + * Get database model + * + * @param string $name + * @param array $options + * @return Pi\Application\Model\Model + */ + public function getModel($name, $options = array()) + { + return Pi::db()->model($this->getModule() . '/' . $name, $options); + } +} \ No newline at end of file diff --git a/var/config/application.command.php b/var/config/application.command.php new file mode 100755 index 0000000000..96b11459c2 --- /dev/null +++ b/var/config/application.command.php @@ -0,0 +1,102 @@ + + */ + +/** + * For more info, @see application.front.php + */ +return array( + 'config' => array(), + 'service' => array(), + 'resource' => array( + 'database' => array(), + 'config' => array(), + 'i18n' => array( + 'translator' => array( + 'global' => array('default'), + 'module' => array('default'), + ), + ), + // Module resource, instantiate module service and load module configs + 'module' => array(), + // Modules resource, to boot up module bootstraps + 'modules' => array(), + ), + + // Service Manager configuration, and Application service configurations managed by Configuration service {@Pi\Mvc\Service\ConfigurationFactory} + 'application' => array( + // ServiceMananger configuration + 'service_manager' => array( + // Services that can be instantiated without factories + 'invokables' => array( + 'SharedEventManager' => 'Zend\EventManager\SharedEventManager', + + + // From ServiceListenerFactory + 'DispatchListener' => 'Zend\Mvc\DispatchListener', + 'RouteListener' => 'Pi\Command\Mvc\RouteListener', + 'SendResponseListener' => 'Zend\Mvc\SendResponseListener', + + // Pi custom service + 'Config' => 'Pi\Mvc\Service\Config', + 'ViewStrategyListener' => 'Pi\Mvc\View\Http\ViewStrategyListener', + 'ConsoleViewManager' => 'Zend\Mvc\View\Console\ViewManager', + ), + + // Service factories + 'factories' => array( + 'EventManager' => 'Zend\Mvc\Service\EventManagerFactory', + 'ModuleManager' => 'Zend\Mvc\Service\ModuleManagerFactory', + + // From ServiceListenerFactory + 'ConsoleAdapter' => 'Zend\Mvc\Service\ConsoleAdapterFactory', + 'ConsoleRouter' => 'Pi\Command\Mvc\Service\RouterFactory', + 'Request' => 'Zend\Mvc\Service\RequestFactory', + 'Response' => 'Zend\Mvc\Service\ResponseFactory', + 'Router' => 'Pi\Command\Mvc\Service\RouterFactory', + 'RoutePluginManager' => 'Zend\Mvc\Service\RoutePluginManagerFactory', + 'ViewManager' => 'Zend\Mvc\Service\ViewManagerFactory', + + // Pi custom service + 'Application' => 'Pi\Command\Mvc\Service\ApplicationFactory', + 'ControllerLoader' => 'Pi\Mvc\Service\ControllerLoaderFactory', + 'ControllerPluginManager' => 'Pi\Mvc\Service\ControllerPluginManagerFactory', + ), + + // Aliases + 'aliases' => array( + 'Zend\EventManager\EventManagerInterface' => 'EventManager', + + // From ServiceListenerFactory + 'Configuration' => 'Config', + 'Console' => 'ConsoleAdapter', + 'Zend\Mvc\Controller\PluginManager' => 'ControllerPluginManager', + 'ControllerManager' => 'ControllerLoader' + ), + + ), + + // Listeners to be registered on Application::bootstrap + 'listeners' => array(), + + // ViewManager configuration + 'view_manager' => array( + 'display_not_found_reason' => true, + 'display_exceptions' => true, + ), + + // ViewHelper config placeholder + 'view_helper_config' => array(), + + // Response sender config + 'send_response' => array(), + ), +); diff --git a/www/pi b/www/pi new file mode 100755 index 0000000000..0309ce051b --- /dev/null +++ b/www/pi @@ -0,0 +1,8 @@ +#!/usr/bin/env php +