├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.xml ├── composer.json ├── coverage-checker.php ├── phpdox.xml.dist ├── phpmd.xml.dist ├── phpunit.xml.dist ├── src └── ZeffMu │ ├── App.php │ ├── ClosureController.php │ └── Module.php └── tests ├── .gitignore └── ZeffMuTest ├── AppFunctionalTest.php └── AppInitTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.phar 3 | composer.lock 4 | .idea 5 | phpunit.xml 6 | build 7 | nbproject 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | 7 | before_script: 8 | - composer self-update 9 | - composer install --prefer-source 10 | - wget http://cs.sensiolabs.org/get/php-cs-fixer.phar 11 | 12 | script: 13 | - ./vendor/bin/phpunit 14 | - php coverage-checker.php build/logs/clover.xml 1 15 | - output=$(php php-cs-fixer.phar fix -v --dry-run --level=psr2 ./src/); if [[ $output ]]; then while read -r line; do echo -e "\e[00;31m$line\e[00m"; done <<< "$output"; false; fi; 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Marco Pivetta, Kathryn Reeve - ZeffMu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 6 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZeffMu - a micro framework built on ZF2 2 | 3 | Zeff - The effective nuclear charge (often symbolize as Zeff or Z*) is the net positive charge experienced by an electron in a multi-electron atom. 4 | 5 | Mu - Micron or small 6 | 7 | Zeff is also a contraction of the British English pronounced Zed Eff (ZF) 8 | 9 | -------------------------------------------------------------------------------------------------- 10 | 11 | This project is a simple example of how ZF2 could be used to build a 12 | really simple micro-framework. It looks exactly like 13 | [silex](http://silex.sensiolabs.org/), but its core is basically a 14 | [`ServiceManager`](http://framework.zend.com/manual/2.0/en/modules/zend.service-manager.intro.html). 15 | 16 | This allows you to have simple closures returning output as complex 17 | architectures involving services and more advanced components, such as 18 | ZF2's [ModuleManager](http://framework.zend.com/manual/2.0/en/modules/zend.module-manager.intro.html) 19 | or [Doctrine2 ORM](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html). 20 | 21 | ## Important Notes 22 | 23 | Please note that this is a project started for fun and to see how similar/different the architectures of Silex and ZF2 are. 24 | 25 | We are working on new features and fixes for various things 26 | 27 | We are looking to have preliminary stableness for each Milestone, starting with Milestone v0.0.5 . 28 | Until this 1st milestone has been reached master is to be considered unstable and volatile. 29 | 30 | ## Installation: 31 | 32 | In a project with a `composer.json` file, type following in your console. 33 | 34 | ```sh 35 | $ composer require binarykitten/zeffmu 36 | ``` 37 | 38 | You can type `*` as a required version. 39 | 40 | ## Usage: 41 | 42 | In your `public/index.php` file (assuming `public` is your webroot), define 43 | following: 44 | 45 | ```php 46 | route('/', function() { 53 | return 'HEllo!'; 54 | }) 55 | ->route('/hello', function() { 56 | return 'Hi!'; 57 | }) 58 | ->route('/hello/:name', function($params) use ($app) { 59 | return 'Hello ' . $params['name']; 60 | }) 61 | ->route('/hello/:name/:surname', function($params) use ($app) { 62 | return 'Hello, Mr. ' . $params['surname'] . ', or shall I call you ' . $params['name'] . '?'; 63 | }) 64 | ->run(); 65 | ``` 66 | 67 | ## Advantages 68 | * Since the application is a fully functional ZF application, you could also return view 69 | models in your closures, thus having templates rendered. 70 | * You can attach listeners to events like in a standard ZF2 application 71 | * You can fetch services like in a standard MVC application (i.e. `$app->getService('db')` or `$app->getServiceLocator()->get('db')`) 72 | * You can load modules and have any module functionality as in typical ZF2 applications 73 | 74 | ## Limitations (for now) 75 | 76 | * ZeffMu will currently route all HTTP requests, regardless of the HTTP method. 77 | * It does not support things such as filtering output strings natively 78 | * It does not support setting controllers or retrieving them from the internal service 79 | locator (since that would require naming the controllers and basically ending up with 80 | ZF2's MVC). Routing and dispatching is also quite different from ZF2. A fallback may be 81 | interesting. 82 | * It does not support registering routes other than `Zend\Mvc\Router\Http\Part` 83 | * As a default, it has all the service provided in a default ZF2 application 84 | * Cannot define a parameter named "controller" in route matches, since it is reserved 85 | * Assembling routes does not yet work 86 | * Helper methods (view helpers/controller plugins) utilities are not yet accessible in a 87 | simple way from the application object. Some simple shortcuts may help. 88 | 89 | ## Advanced usage 90 | 91 | * TBD: services 92 | * TBD: events 93 | 94 | ## Build status 95 | 96 | [![Build Status](https://travis-ci.org/BinaryKitten/ZeffMu.png?branch=master)](undefined) 97 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binarykitten/zeffmu", 3 | "description": "Zend Framework 2 based Micro Framework", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "framework", 8 | "micro", 9 | "zf2" 10 | ], 11 | "homepage": "https://github.com/binarykitten/ZeffMu", 12 | "authors": [ 13 | { 14 | "name": "Marco Pivetta", 15 | "email": "ocramius@gmail.com", 16 | "homepage": "http://marco-pivetta.com/", 17 | "role": "Developer" 18 | }, 19 | { 20 | "name": "Kathryn Reeve", 21 | "email": "kathryn@binarykitten.com", 22 | "homepage": "http://binarykitten.com/", 23 | "role": "Developer" 24 | } 25 | ], 26 | "require": { 27 | "php": ">=5.5", 28 | "zendframework/zend-mvc": "~2.6.0", 29 | "zendframework/zend-modulemanager": "~2.6.1", 30 | "zendframework/zend-loader": "~2.5.0", 31 | "zendframework/zend-view": "~2.5.0", 32 | "zendframework/zend-serializer": "~2.5.0", 33 | "zendframework/zend-log": "~2.5.0", 34 | "zendframework/zend-i18n": "~2.5.0", 35 | "zendframework/zend-console": "~2.5.0", 36 | "zendframework/zend-http": "~2.5.0" 37 | }, 38 | "require-dev": { 39 | "phpunit/phpunit": "~4.0|~5.0" 40 | }, 41 | "autoload": { 42 | "psr-0": { 43 | "ZeffMu": "src/" 44 | } 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /coverage-checker.php: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | 20 | /** 21 | * Code coverage checker. Analyzes a given `clover.xml` report produced 22 | * by PHPUnit and checks if coverage fits expected ratio 23 | * 24 | * Usage: 25 | * php coverage-checker 26 | * 27 | * @author Marco Pivetta 28 | */ 29 | 30 | $inputFile = $argv[1]; 31 | $percentage = min(100, max(0, (int) $argv[2])); 32 | 33 | if (!file_exists($inputFile)) { 34 | throw new InvalidArgumentException('Invalid input file provided'); 35 | } 36 | 37 | if (!$percentage) { 38 | throw new InvalidArgumentException('An integer checked percentage must be given as second parameter'); 39 | } 40 | 41 | $xml = new SimpleXMLElement(file_get_contents($inputFile)); 42 | /* @var $metrics SimpleXMLElement[] */ 43 | $metrics = $xml->xpath('//metrics'); 44 | 45 | $totalElements = 0; 46 | $checkedElements = 0; 47 | 48 | foreach ($metrics as $metric) { 49 | $totalElements += (int) $metric['elements']; 50 | $checkedElements += (int) $metric['coveredelements']; 51 | } 52 | 53 | $coverage = round(($checkedElements / $totalElements) * 100); 54 | 55 | if ($coverage < $percentage) { 56 | echo 'Code coverage is ' . $coverage . '%, which is below the accepted ' . $percentage . '%' . PHP_EOL; 57 | exit(1); 58 | } 59 | 60 | echo 'Code coverage is ' . $coverage . '% - OK!' . PHP_EOL; 61 | -------------------------------------------------------------------------------- /phpdox.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /phpmd.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 1 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | 32 | 36 | 41 | 42 | -------------------------------------------------------------------------------- /src/ZeffMu/App.php: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | 20 | namespace ZeffMu; 21 | 22 | use ZeffMu\ClosureController; 23 | use Zend\Mvc\Application as ZfApplication; 24 | use Zend\Mvc\Router\RouteInterface; 25 | use Zend\Stdlib\ArrayUtils; 26 | 27 | /** 28 | * Zend Framework 2 based micro-framework application 29 | * 30 | * @license MIT 31 | * @author Marco Pivetta 32 | * @author Kathryn Reeve 33 | */ 34 | class App extends ZfApplication 35 | { 36 | /** 37 | * @param string|RouteInterface $route 38 | * @param Closure|String $controller 39 | * 40 | * @return $this 41 | */ 42 | public function route($route, $controller) 43 | { 44 | $sm = $this->getServiceManager(); 45 | /* @var $cpm \Zend\Mvc\Controller\ControllerManager */ 46 | $cpm = $sm->get('ControllerLoader'); 47 | 48 | if ($controller instanceof \Closure) { 49 | $wrappedController = new ClosureController($controller); 50 | $controller = "ZeffMu\\Controllers\\" . md5($route); 51 | $cpm->setFactory( 52 | $controller, 53 | function () use ($wrappedController) { 54 | return $wrappedController; 55 | } 56 | ); 57 | } 58 | 59 | $sm 60 | ->get('Router') 61 | ->addRoute( 62 | $route, 63 | array( 64 | 'type' => 'Zend\Mvc\Router\Http\Segment', 65 | 'options' => array( 66 | 'route' => $route, 67 | 'defaults' => array( 68 | 'controller' => $controller, 69 | ), 70 | ), 71 | ) 72 | ); 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | * @return self 80 | */ 81 | public static function init($configuration = array()) 82 | { 83 | 84 | $defaults = array( 85 | 'module_listener_options' => array(), 86 | 'modules' => array(), 87 | 'service_manager' => array(), 88 | ); 89 | 90 | $configuration = ArrayUtils::merge($defaults, $configuration); 91 | 92 | $configuration['modules'][] = 'ZeffMu'; 93 | 94 | return parent::init($configuration); 95 | } 96 | 97 | /** 98 | * @param $service 99 | * 100 | * @return array|object 101 | */ 102 | public function getService($service) 103 | { 104 | return $this->getServiceManager()->get($service); 105 | } 106 | 107 | /** 108 | * Launch the app 109 | */ 110 | public function __invoke() 111 | { 112 | $this->run(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/ZeffMu/ClosureController.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace ZeffMu; 22 | 23 | use Closure; 24 | use Zend\Mvc\Controller\AbstractController; 25 | use Zend\Mvc\MvcEvent; 26 | use Zend\View\Model\ViewModel; 27 | 28 | /** 29 | * Description of ClosureController 30 | * 31 | * @author Kathryn Reeve 32 | */ 33 | class ClosureController extends AbstractController 34 | { 35 | 36 | /** 37 | * The closure we are wrapping 38 | * @var Closure $closure 39 | */ 40 | protected $closure = null; 41 | 42 | /** 43 | * Constructor 44 | * 45 | * @param Closure $closure 46 | */ 47 | public function __construct(Closure $closure) 48 | { 49 | $this->closure = $closure; 50 | } 51 | 52 | /** 53 | * 54 | * @param \Zend\Mvc\MvcEvent $e 55 | * 56 | * @return misc 57 | */ 58 | public function onDispatch(MvcEvent $e) 59 | { 60 | $routeMatch = $e->getRouteMatch(); 61 | $application = $e->getApplication(); 62 | $request = $e->getRequest(); 63 | $response = $application->getResponse(); 64 | 65 | $closure = $this->closure->bindTo($this); 66 | 67 | $result = $closure( 68 | $routeMatch->getParams(), $request, $response 69 | ); 70 | 71 | if (is_array($result)) { 72 | $result = new ViewModel($result); 73 | $result->setTemplate($template); 74 | } elseif ( ! ( $result instanceof ViewModel )) { 75 | $response->setContent($result); 76 | 77 | return $response; 78 | } 79 | 80 | $e->setResult($result); 81 | 82 | return $result; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/ZeffMu/Module.php: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | 20 | namespace ZeffMu; 21 | 22 | use ZeffMu\App; 23 | use Zend\ModuleManager\Feature\ServiceProviderInterface; 24 | use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter; 25 | use Zend\ServiceManager\ServiceLocatorInterface; 26 | 27 | class Module implements ServiceProviderInterface 28 | { 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public function getServiceConfig() 33 | { 34 | return array( 35 | 'factories' => array( 36 | 'Application' => function (ServiceLocatorInterface $sl) { 37 | return new App($sl->get('Config'), $sl); 38 | }, 39 | 'Router' => function () { 40 | return HttpRouter::factory(array()); 41 | }, 42 | 'HttpRouter' => function (ServiceLocatorInterface $sl) { 43 | return $sl->get('Router'); 44 | } 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | TestConfiguration.php 2 | -------------------------------------------------------------------------------- /tests/ZeffMuTest/AppFunctionalTest.php: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | 20 | namespace ZeffMuTest; 21 | 22 | use PHPUnit_Framework_TestCase; 23 | use ZeffMu\App; 24 | use Zend\Console\Console; 25 | use Zend\EventManager\EventInterface; 26 | use Zend\Http\Request; 27 | use Zend\Mvc\MvcEvent; 28 | use Zend\Stdlib\RequestInterface; 29 | use Zend\Stdlib\ResponseInterface; 30 | 31 | /** 32 | * Functional test demonstrating the features of a ZeffMu application 33 | * 34 | * @license MIT 35 | * @author Marco Pivetta 36 | */ 37 | class AppFunctionalTest extends PHPUnit_Framework_TestCase 38 | { 39 | public function testSimpleClosureController() 40 | { 41 | Console::overrideIsConsole(false); 42 | 43 | $app = App::init(); 44 | $test = $this; 45 | $appRequest = new Request(); 46 | $outerTestResponse = null; 47 | 48 | $appRequest->setUri('http://localhost/test/blah'); 49 | $app->getMvcEvent()->setRequest($appRequest); 50 | 51 | $app->route( 52 | '/test/:param1', 53 | function (array $params, RequestInterface $req, ResponseInterface $res) 54 | use ($test, $appRequest, &$outerTestResponse) { 55 | $test->assertArrayHasKey('param1', $params); 56 | $test->assertSame($appRequest, $req); 57 | 58 | $outerTestResponse = $res; 59 | 60 | return 'Hello world!'; 61 | } 62 | ); 63 | 64 | // overriding send response listener 65 | $app->getEventManager()->attach( 66 | MvcEvent::EVENT_FINISH, 67 | function (EventInterface $e) { 68 | $e->stopPropagation(); 69 | }, 70 | 1000 71 | ); 72 | 73 | $appRunReturn = $app->run(); 74 | $this->assertSame($app, $appRunReturn); 75 | $this->assertSame($app->getResponse(), $outerTestResponse); 76 | $this->assertSame('Hello world!', $app->getResponse()->getContent()); 77 | } 78 | 79 | /** 80 | * @requires PHP 5.4 81 | */ 82 | public function testClosureThisIsControllerInstance() 83 | { 84 | Console::overrideIsConsole(false); 85 | 86 | $app = App::init(); 87 | $test = $this; 88 | $appRequest = new Request(); 89 | 90 | $appRequest->setUri('http://localhost/test/blah'); 91 | $app->getMvcEvent()->setRequest($appRequest); 92 | 93 | $app->route( 94 | '/test/:param1', 95 | function () use ($test) { 96 | $test->assertInstanceOf('ZeffMu\ClosureController', $this); 97 | return 'test'; 98 | } 99 | ); 100 | 101 | // overriding send response listener 102 | $app->getEventManager()->attach( 103 | MvcEvent::EVENT_FINISH, 104 | function (EventInterface $e) { 105 | $e->stopPropagation(); 106 | }, 107 | 1000 108 | ); 109 | 110 | $app->run(); 111 | } 112 | 113 | /** 114 | * @group #24 115 | * 116 | * @requires PHP 5.4 117 | */ 118 | public function testControllerPluginsAreCorrectlyInjectedInFactories() 119 | { 120 | $helper = $this->getMock( 121 | 'Zend\Mvc\Controller\Plugin\PluginInterface', 122 | array('setController', 'getController', '__invoke') 123 | ); 124 | 125 | $request = new Request(); 126 | $app = App::init(); 127 | /* @var $plugins \Zend\ServiceManager\AbstractPluginManager */ 128 | $plugins = $app->getServiceManager()->get('ControllerPluginManager'); 129 | 130 | $request->setUri('http://localhost/something'); 131 | $app->getMvcEvent()->setRequest($request); 132 | 133 | $plugins->setService('foo', $helper); 134 | 135 | $app->route('/something', function () { 136 | $this->foo('bar'); 137 | }); 138 | 139 | $helper->expects(self::once())->method('__invoke')->with('bar'); 140 | 141 | // overriding send response listener 142 | $app->getEventManager()->attach( 143 | MvcEvent::EVENT_FINISH, 144 | function (EventInterface $e) { 145 | $e->stopPropagation(); 146 | }, 147 | 1000 148 | ); 149 | 150 | $app->run(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/ZeffMuTest/AppInitTest.php: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | 20 | namespace ZeffMuTest; 21 | 22 | use PHPUnit_Framework_TestCase; 23 | use ZeffMu\App; 24 | 25 | use Zend\Console\Console; 26 | use Zend\EventManager\EventInterface; 27 | use Zend\Http\Request; 28 | use Zend\Mvc\MvcEvent; 29 | use Zend\Stdlib\RequestInterface; 30 | use Zend\Stdlib\ResponseInterface; 31 | 32 | /** 33 | * Application initailize tests to cover the start up and routing 34 | * 35 | * @license MIT 36 | * @author Marco Pivetta 37 | * @author Kathryn Reeve 38 | */ 39 | class AppInitTest extends PHPUnit_Framework_TestCase 40 | { 41 | public function testAppReturnedByInit() 42 | { 43 | $app = App::init(); 44 | $this->assertInstanceOf('ZeffMu\App', $app); 45 | } 46 | } 47 | --------------------------------------------------------------------------------