* initial commit

This commit is contained in:
Danyi Dávid 2018-05-02 11:01:38 +02:00
commit ff2721f67c
39 changed files with 5308 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.idea
composer.phar
clover.xml
coveralls-upload.json
phpunit.xml
vendor/
_local/

28
LICENSE.md Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2015-2018, Zend Technologies USA, Inc.
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 Zend Technologies USA, Inc. 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 CONTRIBUTORS 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.

150
README.md Normal file
View File

@ -0,0 +1,150 @@
# Expressive Skeleton and Installer
[![Build Status](https://secure.travis-ci.org/zendframework/zend-expressive-skeleton.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-expressive-skeleton)
[![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-expressive-skeleton/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-expressive-skeleton?branch=master)
*Begin developing PSR-15 middleware applications in seconds!*
[zend-expressive](https://github.com/zendframework/zend-expressive) builds on
[zend-stratigility](https://github.com/zendframework/zend-stratigility) to
provide a minimalist PSR-15 middleware framework for PHP with routing, DI
container, optional templating, and optional error handling capabilities.
This installer will setup a skeleton application based on zend-expressive by
choosing optional packages based on user input as demonstrated in the following
screenshot:
![screenshot-installer](https://cloud.githubusercontent.com/assets/459648/10410494/16bdc674-6f6d-11e5-8190-3c1466e93361.png)
The user selected packages are saved into `composer.json` so that everyone else
working on the project have the same packages installed. Configuration files and
templates are prepared for first use. The installer command is removed from
`composer.json` after setup succeeded, and all installer related files are
removed.
## Getting Started
Start your new Expressive project with composer:
```bash
$ composer create-project zendframework/zend-expressive-skeleton <project-path>
```
After choosing and installing the packages you want, go to the
`<project-path>` and start PHP's built-in web server to verify installation:
```bash
$ composer run --timeout=0 serve
```
You can then browse to http://localhost:8080.
> ### Linux users
>
> On PHP versions prior to 7.1.14 and 7.2.2, this command might not work as
> expected due to a bug in PHP that only affects linux environments. In such
> scenarios, you will need to start the [built-in web
> server](http://php.net/manual/en/features.commandline.webserver.php) yourself,
> using the following command:
>
> ```bash
> $ php -S 0.0.0.0:8080 -t public/ public/index.php
> ```
> ### Setting a timeout
>
> Composer commands time out after 300 seconds (5 minutes). On Linux-based
> systems, the `php -S` command that `composer serve` spawns continues running
> as a background process, but on other systems halts when the timeout occurs.
>
> As such, we recommend running the `serve` script using a timeout. This can
> be done by using `composer run` to execute the `serve` script, with a
> `--timeout` option. When set to `0`, as in the previous example, no timeout
> will be used, and it will run until you cancel the process (usually via
> `Ctrl-C`). Alternately, you can specify a finite timeout; as an example,
> the following will extend the timeout to a full day:
>
> ```bash
> $ composer run --timeout=86400 serve
> ```
## Troubleshooting
If the installer fails during the ``composer create-project`` phase, please go
through the following list before opening a new issue. Most issues we have seen
so far can be solved by `self-update` and `clear-cache`.
1. Be sure to work with the latest version of composer by running `composer self-update`.
2. Try clearing Composer's cache by running `composer clear-cache`.
If neither of the above help, you might face more serious issues:
- Info about the [zlib_decode error](https://github.com/composer/composer/issues/4121).
- Info and solutions for [composer degraded mode](https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode).
## Application Development Mode Tool
This skeleton comes with [zf-development-mode](https://github.com/zfcampus/zf-development-mode).
It provides a composer script to allow you to enable and disable development mode.
### To enable development mode
**Note:** Do NOT run development mode on your production server!
```bash
$ composer development-enable
```
**Note:** Enabling development mode will also clear your configuration cache, to
allow safely updating dependencies and ensuring any new configuration is picked
up by your application.
### To disable development mode
```bash
$ composer development-disable
```
### Development mode status
```bash
$ composer development-status
```
## Configuration caching
By default, the skeleton will create a configuration cache in
`data/config-cache.php`. When in development mode, the configuration cache is
disabled, and switching in and out of development mode will remove the
configuration cache.
You may need to clear the configuration cache in production when deploying if
you deploy to the same directory. You may do so using the following:
```bash
$ composer clear-config-cache
```
You may also change the location of the configuration cache itself by editing
the `config/config.php` file and changing the `config_cache_path` entry of the
local `$cacheConfig` variable.
## Skeleton Development
This section applies only if you cloned this repo with `git clone`, not when you
installed expressive with `composer create-project ...`.
If you want to run tests against the installer, you need to clone this repo and
setup all dependencies with composer. Make sure you **prevent composer running
scripts** with `--no-scripts`, otherwise it will remove the installer and all
tests.
```bash
$ composer update --no-scripts
$ composer test
```
Please note that the installer tests remove installed config files and templates
before and after running the tests.
Before contributing read [the contributing guide](docs/CONTRIBUTING.md).

View File

@ -0,0 +1,48 @@
<?php
/**
* Script for clearing the configuration cache.
*
* Can also be invoked as `composer clear-config-cache`.
*
* @see https://github.com/zendframework/zend-expressive-skeleton for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-skeleton/blob/master/LICENSE.md New BSD License
*/
declare(strict_types=1);
chdir(__DIR__ . '/../');
require 'vendor/autoload.php';
$config = include 'config/config.php';
if (! isset($config['config_cache_path'])) {
echo "No configuration cache path found" . PHP_EOL;
exit(0);
}
if (! file_exists($config['config_cache_path'])) {
printf(
"Configured config cache file '%s' not found%s",
$config['config_cache_path'],
PHP_EOL
);
exit(0);
}
if (false === unlink($config['config_cache_path'])) {
printf(
"Error removing config cache file '%s'%s",
$config['config_cache_path'],
PHP_EOL
);
exit(1);
}
printf(
"Removed configured config cache file '%s'%s",
$config['config_cache_path'],
PHP_EOL
);
exit(0);

92
composer.json Normal file
View File

@ -0,0 +1,92 @@
{
"name": "zendframework/zend-expressive-skeleton",
"description": "Zend expressive skeleton. Begin developing PSR-15 middleware applications in seconds!",
"type": "project",
"homepage": "https://github.com/zendframework/zend-expressive-skeleton",
"license": "BSD-3-Clause",
"keywords": [
"skeleton",
"middleware",
"psr",
"psr-7",
"psr-11",
"psr-15",
"zf",
"zendframework",
"zend-expressive"
],
"config": {
"sort-packages": true
},
"extra": {
"zf": {
"component-whitelist": [
"zendframework/zend-expressive",
"zendframework/zend-expressive-helpers",
"zendframework/zend-expressive-router",
"zendframework/zend-httphandlerrunner",
"zendframework/zend-expressive-fastroute",
"zendframework/zend-expressive-platesrenderer"
]
}
},
"support": {
"issues": "https://github.com/zendframework/zend-expressive-skeleton/issues",
"source": "https://github.com/zendframework/zend-expressive-skeleton",
"rss": "https://github.com/zendframework/zend-expressive-skeleton/releases.atom",
"slack": "https://zendframework-slack.herokuapp.com",
"forum": "https://discourse.zendframework.com/c/questions/expressive"
},
"require": {
"php": "^7.1",
"knplabs/knp-menu": "^2.3",
"roave/security-advisories": "dev-master",
"zendframework/zend-component-installer": "^2.1.1",
"zendframework/zend-config-aggregator": "^1.0",
"zendframework/zend-diactoros": "^1.7.1",
"zendframework/zend-expressive": "^3.0.1",
"zendframework/zend-expressive-fastroute": "^3.0",
"zendframework/zend-expressive-helpers": "^5.0",
"zendframework/zend-expressive-platesrenderer": "^2.0",
"zendframework/zend-servicemanager": "^3.3",
"zendframework/zend-stdlib": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^7.0.1",
"squizlabs/php_codesniffer": "^2.9.1",
"zendframework/zend-expressive-tooling": "^1.0",
"zfcampus/zf-development-mode": "^3.1",
"filp/whoops": "^2.1.12"
},
"autoload": {
"psr-4": {
"App\\": "src/App/"
}
},
"autoload-dev": {
"psr-4": {
"AppTest\\": "test/AppTest/"
}
},
"scripts": {
"post-create-project-cmd": [
"@development-enable"
],
"development-disable": "zf-development-mode disable",
"development-enable": "zf-development-mode enable",
"development-status": "zf-development-mode status",
"expressive": "expressive --ansi",
"check": [
"@cs-check",
"@test",
"@analyze"
],
"analyze": "phpstan analyze -l max -c ./phpstan.installer.neon ./src ./config",
"clear-config-cache": "php bin/clear-config-cache.php",
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"serve": "php -S 0.0.0.0:8080 -t public/",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
}
}

3564
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

1
config/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
development.config.php

2
config/autoload/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
local.php
*.local.php

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
return [
// Provides application-wide services.
// We recommend using fully-qualified class names whenever possible as
// service names.
'dependencies' => [
// Use 'aliases' to alias a service name to another service. The
// key is the alias name, the value is the service to which it points.
'aliases' => [
// Fully\Qualified\ClassOrInterfaceName::class => Fully\Qualified\ClassName::class,
],
// Use 'invokables' for constructor-less services, or services that do
// not require arguments to the constructor. Map a service name to the
// class name.
'invokables' => [
// Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
],
// Use 'factories' for services provided by callbacks/factory classes.
'factories' => [
// Fully\Qualified\ClassName::class => Fully\Qualified\FactoryName::class,
],
],
];

View File

@ -0,0 +1,35 @@
<?php
/**
* Development-only configuration.
*
* Put settings you want enabled when under development mode in this file, and
* check it into your repository.
*
* Developers on your team will then automatically enable them by calling on
* `composer development-enable`.
*/
declare(strict_types=1);
use Zend\Expressive\Container;
use Zend\Expressive\Middleware\ErrorResponseGenerator;
return [
'dependencies' => [
'invokables' => [
],
'factories' => [
ErrorResponseGenerator::class => Container\WhoopsErrorResponseGeneratorFactory::class,
'Zend\Expressive\Whoops' => Container\WhoopsFactory::class,
'Zend\Expressive\WhoopsPageHandler' => Container\WhoopsPageHandlerFactory::class,
],
],
'whoops' => [
'json_exceptions' => [
'display' => true,
'show_trace' => true,
'ajax_only' => true,
],
],
];

View File

@ -0,0 +1,12 @@
<?php
/**
* Local configuration.
*
* Copy this file to `local.php` and change its settings as required.
* `local.php` is ignored by git and safe to use for local and sensitive data like usernames and passwords.
*/
declare(strict_types=1);
return [
];

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
return [
'plates' => [
'extensions' => [
App\Plates\NavigationExtension::class,
]
],
];

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use Zend\ConfigAggregator\ConfigAggregator;
return [
// Toggle the configuration cache. Set this to boolean false, or remove the
// directive, to disable configuration caching. Toggling development mode
// will also disable it by default; clear the configuration cache using
// `composer clear-config-cache`.
ConfigAggregator::ENABLE_CACHE => true,
// Enable debugging; typically used to provide debugging information within templates.
'debug' => false,
'zend-expressive' => [
// Provide templates for the error handling middleware to use when
// generating responses.
'error_handler' => [
'template_404' => 'error::404',
'template_error' => 'error::error',
],
],
];

41
config/config.php Normal file
View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
use Zend\ConfigAggregator\ArrayProvider;
use Zend\ConfigAggregator\ConfigAggregator;
use Zend\ConfigAggregator\PhpFileProvider;
// To enable or disable caching, set the `ConfigAggregator::ENABLE_CACHE` boolean in
// `config/autoload/local.php`.
$cacheConfig = [
'config_cache_path' => 'data/cache/config-cache.php',
];
$aggregator = new ConfigAggregator([
\Zend\Expressive\Router\FastRouteRouter\ConfigProvider::class,
\Zend\HttpHandlerRunner\ConfigProvider::class,
\Zend\Expressive\Plates\ConfigProvider::class,
// Include cache configuration
new ArrayProvider($cacheConfig),
\Zend\Expressive\Helper\ConfigProvider::class,
\Zend\Expressive\ConfigProvider::class,
\Zend\Expressive\Router\ConfigProvider::class,
// Default App module config
App\ConfigProvider::class,
// Load application config in a pre-defined order in such a way that local settings
// overwrite global settings. (Loaded as first to last):
// - `global.php`
// - `*.global.php`
// - `local.php`
// - `*.local.php`
new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'),
// Load development config if it exists
new PhpFileProvider(realpath(__DIR__) . '/development.config.php'),
], $cacheConfig['config_cache_path']);
return $aggregator->getMergedConfig();

14
config/container.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
use Zend\ServiceManager\ServiceManager;
// Load configuration
$config = require __DIR__ . '/config.php';
$dependencies = $config['dependencies'];
$dependencies['services']['config'] = $config;
// Build container
return new ServiceManager($dependencies);

View File

@ -0,0 +1,30 @@
<?php
/**
* File required to allow enablement of development mode.
*
* For use with the zf-development-mode tool.
*
* Usage:
* $ composer development-disable
* $ composer development-enable
* $ composer development-status
*
* DO NOT MODIFY THIS FILE.
*
* Provide your own development-mode settings by editing the file
* `config/autoload/development.local.php.dist`.
*
* Because this file is aggregated last, it simply ensures:
*
* - The `debug` flag is _enabled_.
* - Configuration caching is _disabled_.
*/
declare(strict_types=1);
use Zend\ConfigAggregator\ConfigAggregator;
return [
'debug' => true,
ConfigAggregator::ENABLE_CACHE => false,
];

76
config/pipeline.php Normal file
View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\Handler\NotFoundHandler;
use Zend\Expressive\Helper\ServerUrlMiddleware;
use Zend\Expressive\Helper\UrlHelperMiddleware;
use Zend\Expressive\MiddlewareFactory;
use Zend\Expressive\Router\Middleware\DispatchMiddleware;
use Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware;
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
use Zend\Expressive\Router\Middleware\MethodNotAllowedMiddleware;
use Zend\Expressive\Router\Middleware\RouteMiddleware;
use Zend\Stratigility\Middleware\ErrorHandler;
/**
* Setup middleware pipeline:
*/
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
// The error handler should be the first (most outer) middleware to catch
// all Exceptions.
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
// Pipe more middleware here that you want to execute on every request:
// - bootstrapping
// - pre-conditions
// - modifications to outgoing responses
//
// Piped Middleware may be either callables or service names. Middleware may
// also be passed as an array; each item in the array must resolve to
// middleware eventually (i.e., callable or service name).
//
// Middleware can be attached to specific paths, allowing you to mix and match
// applications under a common domain. The handlers in each middleware
// attached this way will see a URI with the matched path segment removed.
//
// i.e., path of "/api/member/profile" only passes "/member/profile" to $apiMiddleware
// - $app->pipe('/api', $apiMiddleware);
// - $app->pipe('/docs', $apiDocMiddleware);
// - $app->pipe('/files', $filesMiddleware);
// Register the routing middleware in the middleware pipeline.
// This middleware registers the Zend\Expressive\Router\RouteResult request attribute.
$app->pipe(RouteMiddleware::class);
// The following handle routing failures for common conditions:
// - HEAD request but no routes answer that method
// - OPTIONS request but no routes answer that method
// - method not allowed
// Order here matters; the MethodNotAllowedMiddleware should be placed
// after the Implicit*Middleware.
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(MethodNotAllowedMiddleware::class);
// Seed the UrlHelper with the routing results:
$app->pipe(UrlHelperMiddleware::class);
// Add more middleware here that needs to introspect the routing results; this
// might include:
//
// - route-based authentication
// - route-based validation
// - etc.
// Register the dispatch middleware in the middleware pipeline
$app->pipe(DispatchMiddleware::class);
// At this point, if no Response is returned by any middleware, the
// NotFoundHandler kicks in; alternately, you can provide other fallback
// middleware to execute.
$app->pipe(NotFoundHandler::class);
};

48
config/routes.php Normal file
View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\MiddlewareFactory;
/**
* Setup routes with a single request method:
*
* $app->get('/', App\Handler\HomePageHandler::class, 'home');
* $app->post('/album', App\Handler\AlbumCreateHandler::class, 'album.create');
* $app->put('/album/:id', App\Handler\AlbumUpdateHandler::class, 'album.put');
* $app->patch('/album/:id', App\Handler\AlbumUpdateHandler::class, 'album.patch');
* $app->delete('/album/:id', App\Handler\AlbumDeleteHandler::class, 'album.delete');
*
* Or with multiple request methods:
*
* $app->route('/contact', App\Handler\ContactHandler::class, ['GET', 'POST', ...], 'contact');
*
* Or handling all request methods:
*
* $app->route('/contact', App\Handler\ContactHandler::class)->setName('contact');
*
* or:
*
* $app->route(
* '/contact',
* App\Handler\ContactHandler::class,
* Zend\Expressive\Router\Route::HTTP_METHOD_ANY,
* 'contact'
* );
*/
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
$app->get('/', App\Handler\HomePageHandler::class, 'home');
$app->get('/api/ping', App\Handler\PingHandler::class, 'api.ping');
$app->get('/the-prize', App\Handler\HomePageHandler::class, 'the-prize');
$app->get('/the-prize/background-and-purpose', App\Handler\HomePageHandler::class, 'the-prize.bg');
$app->get('/the-prize/description-and-values', App\Handler\HomePageHandler::class, 'the-prize.desc');
$app->get('/the-prize/aspect-for-selection', App\Handler\HomePageHandler::class, 'the-prize.aspect');
$app->get('/the-prize/gran-prize-award-events', App\Handler\HomePageHandler::class, 'the-prize.events');
$app->get('/judges', App\Handler\HomePageHandler::class, 'judges');
$app->get('/awardees', App\Handler\HomePageHandler::class, 'awardees');
$app->get('/awardees/{year:\d+}', App\Handler\HomePageHandler::class, 'awardees-by-year');
};

4
data/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*
!cache
!cache/.gitkeep
!.gitignore

0
data/cache/.gitkeep vendored Normal file
View File

20
phpcs.xml.dist Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<ruleset name="Expressive Skeleton coding standard">
<description>Expressive Skeleton coding standard</description>
<!-- display progress -->
<arg value="p"/>
<arg name="colors"/>
<!-- inherit rules from: -->
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
<properties>
<property name="ignoreBlankLines" value="false"/>
</properties>
</rule>
<!-- Paths to check -->
<file>src</file>
</ruleset>

17
phpunit.xml.dist Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="App\\Tests">
<directory>./test</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

19
public/.htaccess Normal file
View File

@ -0,0 +1,19 @@
RewriteEngine On
# The following rule allows authentication to work with fast-cgi
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# The following rule tells Apache that if the requested filename
# exists, simply serve it.
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
# The following rewrites all other queries to index.php. The
# condition ensures that if you are using Apache aliases to do
# mass virtual hosting, the base path will be prepended to
# allow proper resolution of the index.php file; it will work
# in non-aliased environments as well, providing a safe, one-size
# fits all solution.
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]

30
public/index.php Normal file
View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
// Delegate static file requests back to the PHP built-in webserver
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
return false;
}
chdir(dirname(__DIR__));
require 'vendor/autoload.php';
/**
* Self-called anonymous function that creates its own scope and keep the global namespace clean.
*/
(function () {
/** @var \Psr\Container\ContainerInterface $container */
$container = require 'config/container.php';
/** @var \Zend\Expressive\Application $app */
$app = $container->get(\Zend\Expressive\Application::class);
$factory = $container->get(\Zend\Expressive\MiddlewareFactory::class);
// Execute programmatic/declarative middleware pipeline and routing
// configuration statements
(require 'config/pipeline.php')($app, $factory, $container);
(require 'config/routes.php')($app, $factory, $container);
$app->run();
})();

131
public/styles/main.css Normal file
View File

@ -0,0 +1,131 @@
html,
body {
height: 100%;
}
.app-container {
margin: auto;
display: grid;
grid-column-gap: 10px;
grid-row-gap: 10px;
min-height: 100%;
}
.content-container {
grid-area: content;
display: grid;
grid-template-columns: auto;
grid-template-rows: 200px auto;
grid-template-areas:
"title"
"main";
}
.content-container article,
.content-block section {
border-left: 1px solid #003b71;
}
.content-block {
grid-area: content;
}
/* mobile */
@media only screen and (max-width: 767px) {
.app-container {
grid-template-columns: auto;
grid-template-rows: 100px auto auto auto 70px;
grid-template-areas: "header" "nav" "content" "sidebar" "footer";
}
}
/* tablet */
@media only screen and (min-width: 768px) and (max-width: 991px) {
.app-container {
grid-template-columns: 200px 600px 400px;
grid-template-rows: 100px auto 70px;
grid-template-areas:
"header header header"
"nav content sidebar"
"footer footer footer";
}
}
/* monitor */
@media only screen and (min-width: 992px) {
.app-container {
grid-template-columns: auto 200px 600px 400px auto;
grid-template-rows: 100px auto 70px;
grid-template-areas:
". header header header ."
". nav content sidebar ."
"footer footer footer footer footer";
}
.footer-content {
margin: auto;
width: 1220px;
}
}
header.main-header {
grid-area: header;
}
nav.main-nav {
grid-area: nav;
}
section.profile-title {
grid-area: title;
}
article.profile {
grid-area: main;
}
aside.profile-image {
grid-area: sidebar;
}
footer.main-footer {
grid-area: footer;
}
footer {
color: #fffffd;
background-color: #003b71;
}
nav ul {
list-style: none;
padding-left: 0;
}
nav li > ul {
display: none;
}
nav li.current > ul,
nav li.current_ancestor > ul {
display: block;
}
nav > ul > li {
font-variant: small-caps;
font-weight: 500;
}
nav ul.menu_level_1 {
padding-left: 5px;
}
nav ul.menu_level_1 > li {
font-variant: initial;
display: block;
padding-left: 15px;
}
nav ul.menu_level_1 > li.current {
border-left: 5px solid #003b71;
}

341
public/styles/normalize.css vendored Normal file
View File

@ -0,0 +1,341 @@
/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App;
/**
* The configuration provider for the App module
*
* @see https://docs.zendframework.com/zend-component-installer/
*/
class ConfigProvider
{
/**
* Returns the configuration array
*
* To add a bit of a structure, each section is defined in a separate
* method which returns an array with its configuration.
*
*/
public function __invoke() : array
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
];
}
/**
* Returns the container dependencies
*/
public function getDependencies() : array
{
return [
'invokables' => [
Handler\PingHandler::class => Handler\PingHandler::class,
],
'factories' => [
Handler\HomePageHandler::class => Handler\HomePageHandlerFactory::class,
Plates\NavigationExtension::class => Plates\NavigationExtensionFactory::class,
],
];
}
/**
* Returns the templates configuration
*/
public function getTemplates() : array
{
return [
'paths' => [
'app' => ['templates/app'],
'error' => ['templates/error'],
'layout' => ['templates/layout'],
],
];
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace App\Handler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Plates\PlatesRenderer;
use Zend\Expressive\Router;
use Zend\Expressive\Template;
use Zend\Expressive\Twig\TwigRenderer;
use Zend\Expressive\ZendView\ZendViewRenderer;
class HomePageHandler implements RequestHandlerInterface
{
private $containerName;
private $router;
private $template;
public function __construct(
Router\RouterInterface $router,
Template\TemplateRendererInterface $template = null,
string $containerName
) {
$this->router = $router;
$this->template = $template;
$this->containerName = $containerName;
}
public function handle(ServerRequestInterface $request) : ResponseInterface
{
if (! $this->template) {
return new JsonResponse([
'welcome' => 'Congratulations! You have installed the zend-expressive skeleton application.',
'docsUrl' => 'https://docs.zendframework.com/zend-expressive/',
]);
}
$data = [];
switch ($this->containerName) {
case 'Aura\Di\Container':
$data['containerName'] = 'Aura.Di';
$data['containerDocs'] = 'http://auraphp.com/packages/2.x/Di.html';
break;
case 'Pimple\Container':
$data['containerName'] = 'Pimple';
$data['containerDocs'] = 'https://pimple.symfony.com/';
break;
case 'Zend\ServiceManager\ServiceManager':
$data['containerName'] = 'Zend Servicemanager';
$data['containerDocs'] = 'https://docs.zendframework.com/zend-servicemanager/';
break;
case 'Auryn\Injector':
$data['containerName'] = 'Auryn';
$data['containerDocs'] = 'https://github.com/rdlowrey/Auryn';
break;
case 'Symfony\Component\DependencyInjection\ContainerBuilder':
$data['containerName'] = 'Symfony DI Container';
$data['containerDocs'] = 'https://symfony.com/doc/current/service_container.html';
break;
}
if ($this->router instanceof Router\AuraRouter) {
$data['routerName'] = 'Aura.Router';
$data['routerDocs'] = 'http://auraphp.com/packages/2.x/Router.html';
} elseif ($this->router instanceof Router\FastRouteRouter) {
$data['routerName'] = 'FastRoute';
$data['routerDocs'] = 'https://github.com/nikic/FastRoute';
} elseif ($this->router instanceof Router\ZendRouter) {
$data['routerName'] = 'Zend Router';
$data['routerDocs'] = 'https://docs.zendframework.com/zend-router/';
}
if ($this->template instanceof PlatesRenderer) {
$data['templateName'] = 'Plates';
$data['templateDocs'] = 'http://platesphp.com/';
} elseif ($this->template instanceof TwigRenderer) {
$data['templateName'] = 'Twig';
$data['templateDocs'] = 'http://twig.sensiolabs.org/documentation';
} elseif ($this->template instanceof ZendViewRenderer) {
$data['templateName'] = 'Zend View';
$data['templateDocs'] = 'https://docs.zendframework.com/zend-view/';
}
return new HtmlResponse($this->template->render('app::home-page', $data));
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Handler;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageHandlerFactory
{
public function __invoke(ContainerInterface $container) : RequestHandlerInterface
{
$router = $container->get(RouterInterface::class);
$template = $container->has(TemplateRendererInterface::class)
? $container->get(TemplateRendererInterface::class)
: null;
return new HomePageHandler($router, $template, get_class($container));
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Handler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\JsonResponse;
class PingHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request) : ResponseInterface
{
return new JsonResponse(['ack' => time()]);
}
}

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace App\Plates;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Matcher;
use Knp\Menu\Matcher\Voter\UriVoter;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Knp\Menu\MenuFactory;
use Knp\Menu\MenuItem;
use Knp\Menu\Renderer\ListRenderer;
use League\Plates\Engine;
use League\Plates\Extension\ExtensionInterface;
use Zend\Expressive\Router\RouterInterface;
class NavigationExtension implements ExtensionInterface
{
/** @var RouterInterface */
private $router;
/** @var MenuItem */
private $menu;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
/**
* Register functions with the Plates engine.
*
* Registers:
*
* - url($route = null, array $params = []) : string
* - serverurl($path = null) : string
*
* @param Engine $engine
*/
public function register(Engine $engine): void
{
$engine->registerFunction('navigation', [$this, 'generateNavigation']);
}
public function generateNavigation(): string
{
$this->initMainMenu();
$matcher = new Matcher([
new UriVoter($_SERVER['REQUEST_URI'])
]);
$renderer = new ListRenderer($matcher);
return $renderer->render($this->menu);
}
private function initMainMenu()
{
$factory = new MenuFactory();
$this->menu = $factory->createItem("Main-menu");
$prizeMenu = $this->menu->addChild("The prize", [
'uri' => $this->getUriFromRouter('the-prize')
]);
$prizeMenu->addChild("Background and Purpose", [
'uri' => $this->getUriFromRouter('the-prize.bg')
]);
$prizeMenu->addChild("Description and Values", [
'uri' => $this->getUriFromRouter('the-prize.desc')
]);
$prizeMenu->addChild("Aspect for Selection", [
'uri' => $this->getUriFromRouter('the-prize.aspect')
]);
$prizeMenu->addChild("Gran Prize Award and Events", [
'uri' => $this->getUriFromRouter('the-prize.events')
]);
$this->menu->addChild("Judges", [
'uri' => $this->getUriFromRouter('judges')
]);
$awardeesMenu = $this->menu->addChild("Awardees", [
'uri' => $this->getUriFromRouter('awardees')
]);
$this->populateAwardeesSubmenu($awardeesMenu);
}
private function getUriFromRouter(string $name, $param = []): string
{
return $this->router->generateUri($name, $param);
}
private function populateAwardeesSubmenu(ItemInterface $awardeesMenu)
{
$year = (int)date("Y");
for ($i = $year; $i > 2012; $i--) {
$awardeesMenu->addChild($i, [
'uri' => $this->getUriFromRouter('awardees-by-year', ['year' => $i])
]);
}
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Plates;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Plates\Exception\MissingHelperException;
use Zend\Expressive\Router\RouterInterface;
class NavigationExtensionFactory
{
public function __invoke(ContainerInterface $container) : NavigationExtension
{
if (! $container->has(RouterInterface::class)) {
throw new MissingHelperException(sprintf(
'%s requires that the %s service be present; not found',
NavigationExtension::class,
RouterInterface::class
));
}
$router = $container->get(RouterInterface::class);
return new NavigationExtension($router);
}
}

View File

@ -0,0 +1,15 @@
<?php $this->layout('layout::default', ['title' => 'Home']) ?>
<section class="content-container">
<section class="profile-title">
</section>
<article class="profile">
<section class="profile-image">
</section>
</article>
</section>
<aside class="profile-image">
</aside>

View File

@ -0,0 +1,9 @@
<?php $this->layout('layout::default', ['title' => '404 Not Found']) ?>
<h1>Oops!</h1>
<h2>This is awkward.</h2>
<p>We encountered a 404 Not Found error.</p>
<p>
You are looking for something that doesn't exist or may have moved. Check out one of the links on this page
or head back to <a href="/">Home</a>.
</p>

View File

@ -0,0 +1,11 @@
<?php $this->layout('layout::default', ['title' => sprintf('%d %s', $status, $reason)]) ?>
<h1>Oops!</h1>
<h2>This is awkward.</h2>
<p>We encountered a <?=$this->e($status)?> <?=$this->e($reason)?> error.</p>
<?php if ($status == 404) : ?>
<p>
You are looking for something that doesn't exist or may have moved. Check out one of the links on this page
or head back to <a href="/">Home</a>.
</p>
<?php endif; ?>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<title><?= $this->e($title) ?> - zend-expressive</title>
<link rel="shortcut icon" href="https://framework.zend.com/ico/favicon.ico"/>
<link rel="stylesheet" href="<?= $this->serverurl('/styles/normalize.css') ?>"/>
<link rel="stylesheet" href="<?= $this->serverurl('/styles/main.css') ?>"/>
<?= $this->section('stylesheets') ?>
</head>
<body>
<div class="app-container">
<header class="main-header">
<span>Gran Prize</span>
<span>
Interdisciplinary
Innovative
Award
</span>
GP logo
</header>
<nav class="main-nav"><?=$this->navigation()?></nav>
<?= $this->section('content') ?>
<footer class="main-footer">
<div class="footer-content">
Sponsored by:
SIGMA
ERICSSON
</div>
</footer>
</div>
<?= $this->section('javascript') ?>
</body>
</html>

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace AppTest\Handler;
use App\Handler\HomePageHandler;
use App\Handler\HomePageHandlerFactory;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageHandlerFactoryTest extends TestCase
{
/** @var ContainerInterface|ObjectProphecy */
protected $container;
protected function setUp()
{
$this->container = $this->prophesize(ContainerInterface::class);
$router = $this->prophesize(RouterInterface::class);
$this->container->get(RouterInterface::class)->willReturn($router);
}
public function testFactoryWithoutTemplate()
{
$factory = new HomePageHandlerFactory();
$this->container->has(TemplateRendererInterface::class)->willReturn(false);
$this->assertInstanceOf(HomePageHandlerFactory::class, $factory);
$homePage = $factory($this->container->reveal(), null, get_class($this->container->reveal()));
$this->assertInstanceOf(HomePageHandler::class, $homePage);
}
public function testFactoryWithTemplate()
{
$this->container->has(TemplateRendererInterface::class)->willReturn(true);
$this->container
->get(TemplateRendererInterface::class)
->willReturn($this->prophesize(TemplateRendererInterface::class));
$factory = new HomePageHandlerFactory();
$homePage = $factory($this->container->reveal(), null, get_class($this->container->reveal()));
$this->assertInstanceOf(HomePageHandler::class, $homePage);
}
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace AppTest\Handler;
use App\Handler\HomePageHandler;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageHandlerTest extends TestCase
{
/** @var ContainerInterface|ObjectProphecy */
protected $container;
/** @var RouterInterface|ObjectProphecy */
protected $router;
protected function setUp()
{
$this->container = $this->prophesize(ContainerInterface::class);
$this->router = $this->prophesize(RouterInterface::class);
}
public function testReturnsJsonResponseWhenNoTemplateRendererProvided()
{
$homePage = new HomePageHandler(
$this->router->reveal(),
null,
get_class($this->container->reveal())
);
$response = $homePage->handle(
$this->prophesize(ServerRequestInterface::class)->reveal()
);
$this->assertInstanceOf(JsonResponse::class, $response);
}
public function testReturnsHtmlResponseWhenTemplateRendererProvided()
{
$renderer = $this->prophesize(TemplateRendererInterface::class);
$renderer
->render('app::home-page', Argument::type('array'))
->willReturn('');
$homePage = new HomePageHandler(
$this->router->reveal(),
$renderer->reveal(),
get_class($this->container->reveal())
);
$response = $homePage->handle(
$this->prophesize(ServerRequestInterface::class)->reveal()
);
$this->assertInstanceOf(HtmlResponse::class, $response);
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace AppTest\Handler;
use App\Handler\PingHandler;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
class PingHandlerTest extends TestCase
{
public function testResponse()
{
$pingHandler = new PingHandler();
$response = $pingHandler->handle(
$this->prophesize(ServerRequestInterface::class)->reveal()
);
$json = json_decode((string) $response->getBody());
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertTrue(isset($json->ack));
}
}