diff --git a/.editorconfig b/.editorconfig
index 317595c..2c61dbd 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,20 +4,12 @@ end_of_line=lf
insert_final_newline=false
indent_style=space
indent_size=4
+trim_trailing_whitespace = true
-[{.babelrc,.stylelintrc,jest.config,.eslintrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
-indent_style=space
-indent_size=2
-
-[{*.cql,*.ddl,*.sql}]
-indent_style=space
-indent_size=2
+[*.md]
+trim_trailing_whitespace = false
-[{*.yaml,*.yml}]
-indent_style=space
+[{.babelrc,.stylelintrc,jest.config,.eslintrc,*.json,*.jsb3,*.jsb2,*.bowerrc,*.yaml,*.yml,*.sql,composer.lock}]
indent_size=2
-[{composer.lock,composer.json}]
-indent_style=space
-indent_size=2
diff --git a/.gitignore b/.gitignore
index 60e7474..b6f4392 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,34 @@
-.idea
-.php_cs.cache
+# We allow .idea to enable shared development on Jetbrains IDEs
+# but exclude the following user-specific stuff
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+# ... generated files
+.idea/**/contentModel.xml
+# .. sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+.idea/httpRequests
+
+# We don't want the logs folder populated as the contents are local only.
logs/*
-!logs/.gitkeep
!logs/README.md
+# We don't want the cache folder populated as the contents are local only.
cache/*
-!cache/.gitkeep
!cache/README.md
-vendor/*
+# We don't want the qa folder populated as the results change too often.
qa/*
-!qa/.gitkeep
!qa/README.md
+# We don't want the following cache files stored as they are local only.
.phpunit.result.cache
-!.gitkeep
\ No newline at end of file
+.php_cs.cache
+# We don't want vendor to accidentally make it into the repo.
+vendor/*
\ No newline at end of file
diff --git a/.idea/PublicWhipV2.iml b/.idea/PublicWhipV2.iml
new file mode 100644
index 0000000..1d83e6e
--- /dev/null
+++ b/.idea/PublicWhipV2.iml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..d6fc450
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/composerJson.xml b/.idea/composerJson.xml
new file mode 100644
index 0000000..1b07430
--- /dev/null
+++ b/.idea/composerJson.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/fileTemplates/code/PHP Constructor.php b/.idea/fileTemplates/code/PHP Constructor.php
new file mode 100644
index 0000000..737a7dd
--- /dev/null
+++ b/.idea/fileTemplates/code/PHP Constructor.php
@@ -0,0 +1,6 @@
+/**
+ * Constructor.
+${PARAM_DOC}
+${THROWS_DOC}
+*/
+public function __construct(${PARAM_LIST}) {${BODY}}
\ No newline at end of file
diff --git a/.idea/fileTemplates/includes/PHP Class Doc Comment.php b/.idea/fileTemplates/includes/PHP Class Doc Comment.php
new file mode 100644
index 0000000..9d97f4d
--- /dev/null
+++ b/.idea/fileTemplates/includes/PHP Class Doc Comment.php
@@ -0,0 +1,3 @@
+/**
+ * Class ${NAME}.
+ */
diff --git a/.idea/fileTemplates/includes/PHP Interface Doc Comment.php b/.idea/fileTemplates/includes/PHP Interface Doc Comment.php
new file mode 100644
index 0000000..cb387af
--- /dev/null
+++ b/.idea/fileTemplates/includes/PHP Interface Doc Comment.php
@@ -0,0 +1,3 @@
+/**
+ * Interface ${NAME}.
+ */
diff --git a/.idea/fileTemplates/includes/PHP Trait Doc Comment.php b/.idea/fileTemplates/includes/PHP Trait Doc Comment.php
new file mode 100644
index 0000000..118cf16
--- /dev/null
+++ b/.idea/fileTemplates/includes/PHP Trait Doc Comment.php
@@ -0,0 +1,3 @@
+/**
+ * Trait ${NAME}.
+ */
diff --git a/.idea/fileTemplates/internal/PHP Class.php b/.idea/fileTemplates/internal/PHP Class.php
new file mode 100644
index 0000000..860cba4
--- /dev/null
+++ b/.idea/fileTemplates/internal/PHP Class.php
@@ -0,0 +1,9 @@
+
PublicWhip inspections
+
@@ -103,7 +104,7 @@
-
+
@@ -143,7 +144,6 @@
-
@@ -153,6 +153,7 @@
+
@@ -221,6 +222,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..28a804d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..aa97e97
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml
new file mode 100644
index 0000000..ff6cf16
--- /dev/null
+++ b/.idea/php-docker-settings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml
new file mode 100644
index 0000000..9057bf8
--- /dev/null
+++ b/.idea/php-test-framework.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
new file mode 100644
index 0000000..7a9cf99
--- /dev/null
+++ b/.idea/php.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 1e223e0..17a2d82 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,8 @@ new [PublicWhip](https://www.publicwhip.org.uk) application and website.
It is nowhere near ready for usage at the moment though.
-Things that are in place:
-* A basic `docker-compose` setup (within the `Docker` folder) enabling you to have a test environment (complete
-with test database) setup within a few minutes by running `docker-compose up`.
-* Coding standards and base application.
-
-To see what else has been achieved so far, please have a look at the [Milestones](docs/Milestones.md).
+To see what has been achieved so far, please have a look at the [ChangeLog](docs/ChangeLog.md)
+ and what is planned in the [Milestones](docs/Milestones.md).
If you are considering [Contributing](docs/CONTRIBUTING.md) to the development of PublicWhip v2, please ensure you have
read the [Code of Conduct](docs/CODE_OF_CONDUCT.md) first. All contributions to PublicWhip v2 must be licensed under
diff --git a/cache/.gitkeep b/cache/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/composer.json b/composer.json
index c3f4a4b..1571a46 100644
--- a/composer.json
+++ b/composer.json
@@ -6,13 +6,14 @@
"require": {
"php": ">=7.2",
"ext-pdo": "*",
+ "ext-json" : "*",
"slim/slim": "^3",
"psr/log": "^1",
"twig/twig": "^2",
"slim/twig-view": "^2",
"monolog/monolog": "^1.24",
"php-di/php-di": "^6.0",
- "php-di/slim-bridge": "^2.0",
+ "php-di/invoker": "^2.0.0",
"psr/http-message": "^1.0",
"psr/container": "^1",
"slim/csrf": "^0.5",
@@ -39,14 +40,19 @@
"squizlabs/php_codesniffer": "^3",
"infection/infection": "^0.12",
"sebastian/phpcpd": "^4",
- "jakub-onderka/php-parallel-lint": "^1.0"
+ "jakub-onderka/php-parallel-lint": "^1.0",
+ "slevomat/coding-standard": "^5.0",
+ "sirbrillig/phpcs-variable-analysis": "^2.6",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0"
},
"scripts": {
- "style": "phpcs --standard=./phpcs.xml",
+ "phpcs": "phpcs --standard=./phpcs.xml -s",
"test": "phpunit",
"format-code": "php-cs-fixer fix --allow-risky=yes",
- "phpstan": "phpstan analyse -l 7 -c ./phpstan.neon src",
- "phpcbf": "phpcbf --standard=./phpcs.xml ./src"
+ "phpstan": "phpstan analyse -l 7 -c ./phpstan.neon src ./src ./tests",
+ "phpcbf": "phpcbf --standard=./phpcs.xml ./src",
+ "phpmd": "phpmd ./src,./tests text ./phpmd.xml",
+ "grumphp": "grumphp run"
},
"license": "MIT",
"authors": [
diff --git a/composer.lock b/composer.lock
index 2e8ad4b..f84f3ad 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "fd08615ac84c6b278a2400003c6bd7f8",
+ "content-hash": "3ec81bffd8e035c7a5545e0b67846f86",
"packages": [
{
"name": "container-interop/container-interop",
@@ -902,42 +902,6 @@
],
"time": "2018-02-18T17:39:01+00:00"
},
- {
- "name": "php-di/slim-bridge",
- "version": "2.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/PHP-DI/Slim-Bridge.git",
- "reference": "df3c116bedb325c88c01991605380b0fd77b97ed"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/PHP-DI/Slim-Bridge/zipball/df3c116bedb325c88c01991605380b0fd77b97ed",
- "reference": "df3c116bedb325c88c01991605380b0fd77b97ed",
- "shasum": ""
- },
- "require": {
- "php": "~7.0",
- "php-di/invoker": "^2.0.0",
- "php-di/php-di": "^6.0.0",
- "slim/slim": "^3.9.0"
- },
- "require-dev": {
- "phpunit/phpunit": "~6.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "DI\\Bridge\\Slim\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "PHP-DI integration in Slim",
- "time": "2018-02-24T10:54:47+00:00"
- },
{
"name": "pimple/pimple",
"version": "v3.2.3",
@@ -2370,6 +2334,72 @@
],
"time": "2019-01-28T20:25:53+00:00"
},
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
+ "reference": "e749410375ff6fb7a040a68878c656c2e610b132"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e749410375ff6fb7a040a68878c656c2e610b132",
+ "reference": "e749410375ff6fb7a040a68878c656c2e610b132",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0",
+ "php": "^5.3|^7",
+ "squizlabs/php_codesniffer": "^2|^3"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "sensiolabs/security-checker": "^4.1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "time": "2018-10-26T13:21:45+00:00"
+ },
{
"name": "doctrine/collections",
"version": "v1.5.0",
@@ -5592,6 +5622,93 @@
"description": "A security checker for your composer.lock",
"time": "2018-12-19T17:14:59+00:00"
},
+ {
+ "name": "sirbrillig/phpcs-variable-analysis",
+ "version": "v2.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git",
+ "reference": "f72e025d8d715f984d037e2b646891e7161eb64b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/f72e025d8d715f984d037e2b646891e7161eb64b",
+ "reference": "f72e025d8d715f984d037e2b646891e7161eb64b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
+ "limedeck/phpunit-detailed-printer": "^3.1",
+ "phpunit/phpunit": "^6.5",
+ "sirbrillig/phpcs-import-detection": "^1.1",
+ "squizlabs/php_codesniffer": "^3.1"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "VariableAnalysis\\": "VariableAnalysis/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Payton Swick",
+ "email": "payton@foolord.com"
+ },
+ {
+ "name": "Sam Graham",
+ "email": "php-codesniffer-variableanalysis@illusori.co.uk"
+ }
+ ],
+ "description": "A PHPCS sniff to detect problems with variables.",
+ "time": "2019-02-15T01:41:58+00:00"
+ },
+ {
+ "name": "slevomat/coding-standard",
+ "version": "5.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/slevomat/coding-standard.git",
+ "reference": "223f02b6193fe47b7b483bfa5bf75693535482dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/223f02b6193fe47b7b483bfa5bf75693535482dd",
+ "reference": "223f02b6193fe47b7b483bfa5bf75693535482dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "phpstan/phpdoc-parser": "^0.3.1",
+ "squizlabs/php_codesniffer": "^3.4.0"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "1.0.0",
+ "phing/phing": "2.16.1",
+ "phpstan/phpstan": "0.11.1",
+ "phpstan/phpstan-phpunit": "0.11",
+ "phpstan/phpstan-strict-rules": "0.11",
+ "phpunit/phpunit": "8.0.0"
+ },
+ "type": "phpcodesniffer-standard",
+ "autoload": {
+ "psr-4": {
+ "SlevomatCodingStandard\\": "SlevomatCodingStandard"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
+ "time": "2019-03-12T20:26:36+00:00"
+ },
{
"name": "squizlabs/php_codesniffer",
"version": "3.4.0",
@@ -6410,7 +6527,8 @@
"prefer-lowest": false,
"platform": {
"php": ">=7.2",
- "ext-pdo": "*"
+ "ext-pdo": "*",
+ "ext-json": "*"
},
"platform-dev": []
}
diff --git a/config/develop.php b/config/develop.php
index 4764a0c..f674ee7 100644
--- a/config/develop.php
+++ b/config/develop.php
@@ -4,7 +4,7 @@
error_reporting(E_ALL);
ini_set('display_errors', '1');
set_error_handler(
- function ($severity, $message, $file, $line) {
+ static function ($severity, $message, $file, $line): void {
if (error_reporting() & $severity) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 3f07903..b9a2c6f 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -7,6 +7,7 @@ existing tables by `PW 2` must be able to be read/interpreted by `PW 1` unless i
for example, password storage will probably not be backwards compatible but as long as `PW 2` users know to
login via `PW 2` then that will be fine. It would not be acceptable for divisions to be unreadable by either
system however.
+
## Pull Requests
1. Fork the `PublicWhipV2` repository
@@ -20,11 +21,17 @@ pull request for each branch. This allows each feature or improvement to be revi
All pull requests must adhere to the
[PSR-12 standard](https://github.com/php-fig/fig-standards/blob/master/proposed/extended-coding-style-guide.md).
+and the included php_cs.xml file (which heavily uses the
+[Slevomat coding standard](https://github.com/slevomat/coding-standard)).
No `grumPHP` tests should fail: you can check this at any time by using `\vendor\bin\grumphp run`
(or on Windows: `vendor\bin\grumphp.bat run`);
-Yoda conditions should be used. Along with the PHP Code Sniffer configuration in `php_cs.xml`, there is also a
+Yoda conditions should be used.
+
+It is preferred to 'return early' than only have one return in a method.
+
+Along with the PHP Code Sniffer configuration in `php_cs.xml`, there is also a
PHPStorm coding style and inspections configuration file.
## Unit Testing
diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md
new file mode 100644
index 0000000..ad9c5c0
--- /dev/null
+++ b/docs/ChangeLog.md
@@ -0,0 +1,71 @@
+# Change log.
+
+These are items that have been done and released. To see what's planned, have a look at the
+[Milestones](Milestones.md).
+
+* 0.4.1 - Improved coding style and fixes. DONE - 2019-03-16
+
+* 0.4 - Get accurate information about Divisions showing: Estimate 1-2 days. DONE - 2019-03-15
+
+ Division motion text is stored in a custom 'wiki' format which needs to be parsed.
+
+* 0.3.1 - Small fixes. DONE - 2019-03-14
+
+ Added continuous integration settings, missing database tables (for policies), added planned
+ milestones, fix indentation in composer.json.
+
+* 0.3 - Prepare for upload to Github. Estimate 1 day. DONE - 2019-03-12
+
+ The system should present a basic styled POC (proof-of-concept) website with
+ information about the project, there should be an appropriate [README](../README.md) for all
+ major directories with any major changes to existing directories noted (and all
+ READMEs should be able to be accessed from the website), all QA checks should pass,
+ there should be an appropriate [license file](../LICENSE.txt),
+ [code of conduct](CODE_OF_CONDUCT.md), [contact details](Contact.md)
+ (including a [Slack channel](https://publicwhip.slack.com/)) and things should just work.
+
+ Use [Parsedown](https://github.com/erusev/parsedown) for markdown formatting.
+
+ Use [Pure](https://purecss.io/) for simple layout.
+
+* 0.2 - Basic rendering with existing database. DONE - 2019-03-11
+
+ Using the base project, we should then have some basic pages rendered and some basic Divisions
+ information showing.
+
+* 0.1 - Base/skeleton project. DONE - 2019-03-10
+
+ Setup a base/skeleton project which can be used by anyone.
+
+ Using:
+ - [PHP](https://php.net) 7.2 for the main language,
+ - [Slim Framework](https://www.slimframework.com) as the framework,
+ - [PHP-DI](https://php-di.org) for dependency injection
+ - [Monolog](https://github.com/Seldaek/monolog) for logging,
+ - [Twig](https://twig.symfony.com) as the templating engine,
+ - [PHPDebugBar](http://phpdebugbar.com/) for debugging,
+ - [SwiftMailer](https://swiftmailer.symfony.com/) for email handling
+ - [Laravel Database](https://laravel.com/docs/5.8/database) for database connections
+ - [Composer](https://getcomposer.org) for package control
+
+ To enable testing, this is paired with:
+ - [Docker](http://docker.com) containers for multi-platform testing which has:
+ - [Nginx](http://nginx.org) as the web server
+ - [MariaDB](https://mariadb.org/) as the database server
+ - [PHPMyAdmin](https://www.phpmyadmin.net/) for simple database administration
+ - [MailHog](https://github.com/mailhog/MailHog) for email interception
+ Along with an anonymized copy of the live database with just core tables included.
+
+ To aid development, the project has the following code quality control tools setup:
+ - [GrumPHP](https://github.com/phpro/grumphp) to run all the following checks on commits:
+ - [PHPUnit](https://phpunit.de/) for unit testing
+ - [PHP Mess Detector](https://phpmd.org) for code inspection
+ - [PHP Stan](https://github.com/phpstan/phpstan) for static analysis
+ - [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) for code quality inspections
+ - [Roave Security Advisories](https://github.com/Roave/SecurityAdvisories) to check for outdated packages
+ - [PHP Copy/Paste Detector](https://github.com/sebastianbergmann/phpcpd) to reduce duplicated code
+ - [Infection](https://infection.github.io/) for mutation testing
+ - [PHP Parallel Lint](https://github.com/JakubOnderka/PHP-Parallel-Lint) for basic code checks
+ - [Sensiolabs Security Checker](https://github.com/sensiolabs/security-checker) to check for outdated packages
+
+ Also included are [PHPStorm](https://www.jetbrains.com/phpstorm/) code style and inspection profiles.
\ No newline at end of file
diff --git a/docs/Milestones.md b/docs/Milestones.md
index 985cdf2..8db479b 100644
--- a/docs/Milestones.md
+++ b/docs/Milestones.md
@@ -1,9 +1,11 @@
# Milestones
-[Jump to Completed Milestones](#completed).
+See completed releases in the [ChangeLog](ChangeLog.md).
## Planned Milestones (in planning order)
+* 0.4.2 - Ensure all division data extractable from PublicWhip v1 is processed correctly: Estimate 1 day.
+
* 0.4.5 - Create basic division editing system (with no authentication) : Estimate 3-4 days
* 0.5 - Show/List policies (aka "dreams" in v1 database). Estimate 1 day
@@ -50,70 +52,7 @@
* 2.1 - Move away from the [parliamentary parser](http://parser.theyworkforyou.com/)
So we can retrieve division information faster (as per [CommonsVotes](https://commonsvotes.digiminster.com/))
-
-## Completed Milestones (in recently completed order)
-
-* 0.4 - Get accurate information about Divisions showing: Estimate 1-2 days. DONE - 2019-03-15
-
- Division motion text is stored in a custom 'wiki' format which needs to be parsed.
-
-* 0.3.1 - Small fixes. DONE - 2019-03-14
-
- Added continuous integration settings, missing database tables (for policies), added planned
- milestones, fix indentation in composer.json.
-
-* 0.3 - Prepare for upload to Github. Estimate 1 day. DONE - 2019-03-12
-
- The system should present a basic styled POC (proof-of-concept) website with
- information about the project, there should be an appropriate [README](../README.md) for all
- major directories with any major changes to existing directories noted (and all
- READMEs should be able to be accessed from the website), all QA checks should pass,
- there should be an appropriate [license file](../LICENSE.txt),
- [code of conduct](CODE_OF_CONDUCT.md), [contact details](Contact.md)
- (including a [Slack channel](https://publicwhip.slack.com/)) and things should just work.
-
- Use [Parsedown](https://github.com/erusev/parsedown) for markdown formatting.
-
- Use [Pure](https://purecss.io/) for simple layout.
-
-* 0.2 - Basic rendering with existing database. DONE - 2019-03-11
-
- Using the base project, we should then have some basic pages rendered and some basic Divisions
- information showing.
-
-* 0.1 - Base/skeleton project. DONE - 2019-03-10
-
- Setup a base/skeleton project which can be used by anyone.
-
- Using:
- - [PHP](https://php.net) 7.2 for the main language,
- - [Slim Framework](https://www.slimframework.com) as the framework,
- - [PHP-DI](https://php-di.org) for dependency injection
- - [Monolog](https://github.com/Seldaek/monolog) for logging,
- - [Twig](https://twig.symfony.com) as the templating engine,
- - [PHPDebugBar](http://phpdebugbar.com/) for debugging,
- - [SwiftMailer](https://swiftmailer.symfony.com/) for email handling
- - [Laravel Database](https://laravel.com/docs/5.8/database) for database connections
- - [Composer](https://getcomposer.org) for package control
-
- To enable testing, this is paired with:
- - [Docker](http://docker.com) containers for multi-platform testing which has:
- - [Nginx](http://nginx.org) as the web server
- - [MariaDB](https://mariadb.org/) as the database server
- - [PHPMyAdmin](https://www.phpmyadmin.net/) for simple database administration
- - [MailHog](https://github.com/mailhog/MailHog) for email interception
- Along with an anonymized copy of the live database with just core tables included.
-
- To aid development, the project has the following code quality control tools setup:
- - [GrumPHP](https://github.com/phpro/grumphp) to run all the following checks on commits:
- - [PHPUnit](https://phpunit.de/) for unit testing
- - [PHP Mess Detector](https://phpmd.org) for code inspection
- - [PHP Stan](https://github.com/phpstan/phpstan) for static analysis
- - [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) for code quality inspections
- - [Roave Security Advisories](https://github.com/Roave/SecurityAdvisories) to check for outdated packages
- - [PHP Copy/Paste Detector](https://github.com/sebastianbergmann/phpcpd) to reduce duplicated code
- - [Infection](https://infection.github.io/) for mutation testing
- - [PHP Parallel Lint](https://github.com/JakubOnderka/PHP-Parallel-Lint) for basic code checks
- - [Sensiolabs Security Checker](https://github.com/sensiolabs/security-checker) to check for outdated packages
-
- Also included are [PHPStorm](https://www.jetbrains.com/phpstorm/) code style and inspection profiles.
\ No newline at end of file
+
+* 2.5 - Get really high code quality and unit tests (see
+[PHP Code Quality Tools](https://web-techno.net/code-quality-check-tools-php/) for additional checks)
+
\ No newline at end of file
diff --git a/docs/Production.md b/docs/Production.md
new file mode 100644
index 0000000..2214a22
--- /dev/null
+++ b/docs/Production.md
@@ -0,0 +1,19 @@
+# Deploying to production notes
+
+(taken from https://dev.to/elabftw/optimizing-your-php-app-speed-3hd4 )
+
+1. Use `composer install --no-dev -a`
+
+2. Ensure opcache is turned on in php.ini
+```
+opcache_enable=1
+opcache.revalidate_freq = 10 # number of seconds to check timetamps for changes
+opcache.fast_shutdown=1
+opcache.file_update=protection = 0 # prevents caching of files less than this number of seconds old
+```
+
+3. Consider an opcache dashboard as per https://haydenjames.io/php-performance-opcache-control-panels/
+
+4. Ensure Twig's cache is enabled.
+
+5. Use backslashes with standard functions.
\ No newline at end of file
diff --git a/docs/QuickStart.md b/docs/QuickStart.md
index 62f8784..afedfda 100644
--- a/docs/QuickStart.md
+++ b/docs/QuickStart.md
@@ -33,16 +33,7 @@ I personally use [PHPStorm](https://www.jetbrains.com/phpstorm/) with the
[PHP Inspections Ultimate](https://kalessil.github.io/php-inspections-ultimate.html) and
[EditorConfig](https://plugins.jetbrains.com/plugin/7294-editorconfig) plugins.
-If you use PHPStorm as well, you may find going to:
-- Mac: PHPStorm->Preferences->Editor->Inspections->(Cog)->Import Profile
-- Windows: File->Settings->Editor->Inspections->(Cog)->Import Profile
-
-and importing `phpStormInspections.xml` and
-
-- Mac: PHPStorm->Preferences->Editor->Code Style->(Cog)->Import Scheme->IntelliJ IDEA code style XML
-- Windows: File->Settings->Editor->Inspections->(Cog)->Import Scheme->IntelliJ IDEA code style XML
-
-and then importing `phpStormCodeStyle.xml` to help.
+If you use PHPStorm as well, the included `.idea` folder should automatically configure everything for you.
Other IDEs will be able to make use of the `.editorconfig` file in the root folder to help.
diff --git a/grumphp.yml b/grumphp.yml
index b6671a4..5010e49 100644
--- a/grumphp.yml
+++ b/grumphp.yml
@@ -10,9 +10,12 @@ parameters:
ignore_patterns: ['database','docker','cache','logs']
phpcs:
standard: ./phpcs.xml
- ignore_patterns: ['web','database','docker','cache','logs','.phpstorm.meta.php']
- phplint: ~
+ ignore_patterns: ['web','database','docker','cache','logs','.phpstorm.meta.php','.idea']
+ phplint:
+ exclude: ['web','database','docker','cache','logs','.phpstorm.meta.php','.idea']
+ ignore_patterns: ['web','database','docker','cache','logs','.phpstorm.meta.php','.idea']
phpmd:
+ exclude: ['./.idea/']
whitelist_patterns:
- /^src\/(.*)/
- /^tests\/(.*)/
@@ -21,7 +24,7 @@ parameters:
phpstan:
configuration: ./phpstan.neon
level: 7
- ignore_patterns: ['web','database','docker','cache','logs','.phpstorm.meta.php','NotFoundHandler.php']
+ ignore_patterns: ['web','database','docker','cache','logs','.phpstorm.meta.php','.idea']
phpversion:
project: '7.2'
phpunit:
@@ -29,7 +32,7 @@ parameters:
securitychecker: ~
phpcpd:
directory: '.'
- exclude: ['vendor','web','database','docker','cache','logs','.phpstorm.meta.php']
+ exclude: ['vendor','web','database','docker','cache','logs','.phpstorm.meta.php','.idea']
names_exclude: []
regexps_exclude: []
fuzzy: false
diff --git a/logs/.gitkeep b/logs/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/phpStormCodeStyle.xml b/phpStormCodeStyle.xml
deleted file mode 100644
index 1f95799..0000000
--- a/phpStormCodeStyle.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/phpcs.xml b/phpcs.xml
index 681448c..a087563 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -1,8 +1,252 @@
- PublicWhip Ruleset. Created with the PHP Coding Standard Generator. http://edorian.github.com/php-coding-standard-generator/
-
+ PublicWhip Ruleset.
+ src
+ tests
+ ./web/*
+ ./vendor/*
+ ./v1migrationUtils/
+ ./.idea/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/phpmd.xml b/phpmd.xml
index 90b1ceb..a3de8d6 100644
--- a/phpmd.xml
+++ b/phpmd.xml
@@ -12,9 +12,26 @@
-
+
+
+
+
+
+
+
+
+ .idea/
+ cache/
+ database/
+ docker/
+ docs/
+ logs/
+ qa/
+ v1migrationUtils/
+ vendor/
+
\ No newline at end of file
diff --git a/qa/.gitkeep b/qa/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/Config.php b/src/Config.php
index 1c60866..7fc6dc6 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -4,6 +4,12 @@
namespace PublicWhip;
use DebugBar\Bridge\MonologCollector;
+use DI\Container;
+use Invoker\Invoker;
+use Invoker\ParameterResolver\AssociativeArrayResolver;
+use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
+use Invoker\ParameterResolver\DefaultValueResolver;
+use Invoker\ParameterResolver\ResolverChain;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
@@ -13,15 +19,21 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
+use PublicWhip\Factories\DateTimeFactory;
+use PublicWhip\Factories\DateTimeFactoryInterface;
+use PublicWhip\Factories\EntityFactory;
+use PublicWhip\Factories\EntityFactoryInterface;
+use PublicWhip\Providers\CallableResolverProvider;
+use PublicWhip\Providers\ControllerInvokerProvider;
use PublicWhip\Providers\DatabaseProvider;
use PublicWhip\Providers\DatabaseProviderInterface;
use PublicWhip\Providers\DebuggerProvider;
use PublicWhip\Providers\DebuggerProviderInterface;
use PublicWhip\Providers\DebuggerTwigExtension;
-use PublicWhip\Providers\HydratorProvider;
-use PublicWhip\Providers\HydratorProviderInterface;
use PublicWhip\Providers\MailerProvider;
use PublicWhip\Providers\MailerProviderInterface;
+use PublicWhip\Providers\TemplateProvider;
+use PublicWhip\Providers\TemplateProviderInterface;
use PublicWhip\Providers\WikiParserProvider;
use PublicWhip\Providers\WikiParserProviderInterface;
use PublicWhip\Services\DivisionService;
@@ -31,9 +43,16 @@
use PublicWhip\Web\ErrorHandlers\PhpErrorHandler;
use Slim\Csrf\Guard;
use Slim\Flash\Messages;
+use Slim\Handlers\NotAllowed;
+use Slim\Http\Environment;
+use Slim\Http\Headers;
+use Slim\Http\Request;
+use Slim\Http\Response;
use Slim\HttpCache\CacheProvider;
use Slim\Interfaces\RouterInterface;
+use Slim\Router;
use Slim\Views\Twig;
+use Slim\Views\TwigExtension;
use function DI\autowire;
use function DI\create;
use function DI\get;
@@ -44,14 +63,18 @@
* Handles setting up the services/settings which are needed. This primarily populates
* a PHP-DI container.
*
- * @package PublicWhip
+ * Since we configure practically everything here, we have a high coupling between objects. We'll
+ * tell PHPMD this is okay.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Config
{
+
/**
* Settings/services configuration needed for the web environment.
*
- * @return array
+ * @return array
*/
public function getWebConfig(): array
{
@@ -61,6 +84,12 @@ public function getWebConfig(): array
// setup our twig extension
DebuggerTwigExtension::class => autowire(),
// setup twig
+ TwigExtension::class => static function (ContainerInterface $container): TwigExtension {
+ $router = $container->get('router');
+ $uri = $container->get('request')->getUri();
+ return new TwigExtension($router, $uri);
+ },
+ TemplateProviderInterface::class => autowire(TemplateProvider::class),
Twig::class => create()
->constructor(
__DIR__ . DIRECTORY_SEPARATOR . 'Web' . DIRECTORY_SEPARATOR . 'Templates',
@@ -69,7 +98,8 @@ public function getWebConfig(): array
'debug' => get('settings.debug')
]
)
- ->method('addExtension', get(DebuggerTwigExtension::class)),
+ ->method('addExtension', get(DebuggerTwigExtension::class))
+ ->method('addExtension', get(TwigExtension::class)),
// setup router interface to point to Slim's already existing router.
RouterInterface::class => get('router'),
// setup request interface to point to Slim's already existing request
@@ -92,29 +122,101 @@ public function getWebConfig(): array
// setup parsedown for markdown formatting.
Parsedown::class => create()
];
- return $return;
+ return array_merge($this->generalSlimSetup(), $return);
+ }
+
+ /**
+ * Get the general Slim settings.
+ /**
+ * Disable this PHPMD warning for simplicity when building the environment using Request::.
+ * @SuppressWarnings(PHPMD.StaticAccess)
+ *
+ * @return array):
+ */
+ private function generalSlimSetup(): array
+ {
+ return [
+
+ // Settings that can be customized by users
+ 'settings.httpVersion' => '1.1',
+ 'settings.responseChunkSize' => 4096,
+ 'settings.outputBuffering' => 'append',
+ 'settings.determineRouteBeforeAppMiddleware' => false,
+ 'settings.displayErrorDetails' => false,
+ 'settings.addContentLengthHeader' => true,
+ 'settings.routerCacheFile' => false,
+
+ 'settings' => [
+ 'httpVersion' => get('settings.httpVersion'),
+ 'responseChunkSize' => get('settings.responseChunkSize'),
+ 'outputBuffering' => get('settings.outputBuffering'),
+ 'determineRouteBeforeAppMiddleware' => get('settings.determineRouteBeforeAppMiddleware'),
+ 'displayErrorDetails' => get('settings.displayErrorDetails'),
+ 'addContentLengthHeader' => get('settings.addContentLengthHeader'),
+ 'routerCacheFile' => get('settings.routerCacheFile'),
+ ],
+
+ // Default Slim services
+ 'router' => create(Router::class)
+ ->method('setContainer', get(Container::class))
+ ->method('setCacheFile', get('settings.routerCacheFile')),
+
+ Router::class => get('router'),
+
+ 'notAllowedHandler' => create(NotAllowed::class),
+
+ 'environment' => static function () {
+ return new Environment($_SERVER);
+ },
+
+
+ 'request' => static function (ContainerInterface $container) {
+ return Request::createFromEnvironment($container->get('environment'));
+ },
+
+ 'response' => static function (ContainerInterface $container) {
+ $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']);
+ return (new Response(200, $headers))->withProtocolVersion($container->get('settings')['httpVersion']);
+ },
+ 'foundHandler' => create(ControllerInvokerProvider::class)
+ ->constructor(get('foundHandler.invoker')),
+ 'foundHandler.invoker' => static function (ContainerInterface $container) {
+ $resolvers = [
+ // Inject parameters by name first
+ new AssociativeArrayResolver(),
+ // Then inject services by type-hints for those that weren't resolved
+ new TypeHintContainerResolver($container),
+ // Then fall back on parameters default values for optional route parameters
+ new DefaultValueResolver(),
+ ];
+ return new Invoker(new ResolverChain($resolvers), $container);
+ },
+
+ 'callableResolver' => autowire(CallableResolverProvider::class),
+ ];
}
/**
* The general configurations needed to run the system (no matter what environment).
*
- * @return array
+ * @return array
*/
public function getGeneralConfig(): array
{
- $return = [
- /**
- * Providers
- */
- LoggerInterface::class => function (ContainerInterface $c): LoggerInterface {
- $settings = $c->get('settings.logger');
+ /**
+ * Providers
+ */
+ $providers = [
+ LoggerInterface::class => static function (ContainerInterface $container): LoggerInterface {
+ $settings = $container->get('settings.logger');
$logger = new Logger($settings['name']);
$logger->pushProcessor(new UidProcessor());
$logger->pushHandler(new StreamHandler($settings['path'], Logger::DEBUG));
- $debugger = $c->get(DebuggerProviderInterface::class);
+ $debugger = $container->get(DebuggerProviderInterface::class);
$debugger->addDataCollector(new MonologCollector($logger));
return $logger;
},
+
DatabaseProviderInterface::class => create(DatabaseProvider::class)
->constructor(get('settings.db'))
->method('addToDebugger', get(DebuggerProviderInterface::class)),
@@ -123,12 +225,24 @@ public function getGeneralConfig(): array
->method('addToDebugger', get(DebuggerProviderInterface::class)),
DebuggerProviderInterface::class => create(DebuggerProvider::class)
->constructor(get('settings.debug')),
+
WikiParserProviderInterface::class => autowire(WikiParserProvider::class),
- /**
- * Services.
- */
+
+
+ ];
+ /**
+ * Factories
+ */
+ $factories = [
+ EntityFactoryInterface::class => autowire(EntityFactory::class),
+ DateTimeFactoryInterface::class => autowire(DateTimeFactory::class)
+ ];
+ /**
+ * Services.
+ */
+ $services = [
DivisionServiceInterface::class => autowire(DivisionService::class)
];
- return $return;
+ return array_merge($providers, $factories, $services);
}
}
diff --git a/src/Entities/AbstractEntity.php b/src/Entities/AbstractEntity.php
deleted file mode 100644
index a5565b6..0000000
--- a/src/Entities/AbstractEntity.php
+++ /dev/null
@@ -1,148 +0,0 @@
-$property = $newValue;
- }, $object, $object)->__invoke();
- };
- $fieldsMapped = [];
- foreach ($new->requiredPropertiesMapping() as $name => $type) {
- if (!isset($data[$name])) {
- throw new EntityMissingRequiredFieldException(
- sprintf(
- 'Missing required field %s when building %s',
- $name,
- static::class
- )
- );
- }
- if (!self::checkType($name, $type, $data[$name])) {
- throw new EntityFieldWrongTypeException(
- sprintf(
- 'Expected required entity field %s to be %s, but was %s when creating %s',
- $name,
- $type,
- is_object($data[$name]) ? get_class($data[$name]) : gettype($data[$name]),
- static::class
- )
- );
- }
- $hydrate($new, $name, $data[$name]);
- $fieldsMapped[] = $name;
- }
- // now add the optional fields.
- foreach ($new->optionalPropertiesMapping() as $name => $type) {
- if (isset($data[$name])) {
- if (!self::checkType($name, $type, $data[$name])) {
- throw new EntityFieldWrongTypeException(
- sprintf(
- 'Expected entity field %s to be %s, but was %s when creating %s',
- $name,
- $type,
- is_object($data[$name]) ? get_class($data[$name]) : gettype($data[$name]),
- static::class
- )
- );
- }
- $hydrate($new, $name, $data[$name]);
- } else {
- $hydrate($new, $name, null);
- }
- $fieldsMapped[] = $name;
- }
- $difference = array_diff(array_keys($data), $fieldsMapped);
- if (count($difference) > 0) {
- throw new EntityPassedUnrecognisedFieldException(
- sprintf(
- 'The entity %s was passed the following field(s) which it does not know how to handle: %s',
- static::class,
- implode(', ', $difference)
- )
- );
- }
- return $new;
- }
-
- /**
- * Checks that the field is of the expected type.
- *
- * @param string $fieldName Name of the field (for error reporting).
- * @param string $expectedType String of the expected type.
- * @param mixed $value The value to check.
- * @return bool
- */
- private static function checkType(string $fieldName, string $expectedType, $value): bool
- {
- $correctType = true;
- switch ($expectedType) {
- case 'int':
- if (!is_int($value)) {
- $correctType = false;
- }
- break;
- case 'string':
- if (!is_string($value)) {
- $correctType = false;
- }
- break;
- case DateTimeImmutable::class:
- if (!$value instanceof DateTimeImmutable) {
- $correctType = false;
- }
- break;
- default:
- throw new EntityFieldWrongTypeException(
- sprintf(
- 'Bad definition found for field %s in %s',
- $fieldName,
- static::class
- )
- );
- }
- return $correctType;
- }
-}
diff --git a/src/Entities/DivisionEntity.php b/src/Entities/DivisionEntity.php
index 53e640a..052f3eb 100644
--- a/src/Entities/DivisionEntity.php
+++ b/src/Entities/DivisionEntity.php
@@ -7,15 +7,14 @@
/**
* Class DivisionEntity
- * @package PublicWhip\Entities
*/
-final class DivisionEntity extends AbstractEntity
+final class DivisionEntity
{
/**
* @var int Id of the division.
*/
- private $id;
+ private $divisionId;
/**
* @var DateTimeImmutable
@@ -26,6 +25,7 @@ final class DivisionEntity extends AbstractEntity
* @var int Number of the division.
*/
private $number;
+
/**
* @var string Source of the division.
*/
@@ -82,14 +82,16 @@ final class DivisionEntity extends AbstractEntity
private $ayeMajority;
/**
+ * Get the division id.
* @return int
*/
- public function getId(): int
+ public function getDivisionId(): int
{
- return $this->id;
+ return $this->divisionId;
}
/**
+ * Get the date of the division.
* @return DateTimeImmutable
*/
public function getDate(): DateTimeImmutable
@@ -98,6 +100,7 @@ public function getDate(): DateTimeImmutable
}
/**
+ * Get the number of the division for this day.
* @return int
*/
public function getNumber(): int
@@ -106,6 +109,7 @@ public function getNumber(): int
}
/**
+ * Get the source url.
* @return string
*/
public function getSourceUrl(): string
@@ -114,6 +118,7 @@ public function getSourceUrl(): string
}
/**
+ * Get the debate url.
* @return string
*/
public function getDebateUrl(): string
@@ -122,6 +127,7 @@ public function getDebateUrl(): string
}
/**
+ * Get the motion text.
* @return string
*/
public function getMotionText(): string
@@ -130,6 +136,7 @@ public function getMotionText(): string
}
/**
+ * Get the motion title.
* @return string
*/
public function getMotionTitle(): string
@@ -138,6 +145,7 @@ public function getMotionTitle(): string
}
/**
+ * Get the original (imported) motion text.
* @return string
*/
public function getOriginalMotionText(): string
@@ -146,13 +154,16 @@ public function getOriginalMotionText(): string
}
/**
+ * Get the original (imported) motion title.
* @return string
*/
public function getOriginalMotionTitle(): string
{
return $this->originalMotionTitle;
}
+
/**
+ * Which house was this division in?
* @return string
*/
public function getHouse(): string
@@ -161,6 +172,7 @@ public function getHouse(): string
}
/**
+ * How many rebellions (in total) were then? May be null if not yet compiled.
* @return int|null
*/
public function getRebellions(): ?int
@@ -169,6 +181,7 @@ public function getRebellions(): ?int
}
/**
+ * What was the total turnout/votes cast? May be null if not yet compiled.
* @return int|null
*/
public function getTurnout(): ?int
@@ -177,6 +190,7 @@ public function getTurnout(): ?int
}
/**
+ * Get the total possible turnout (registered MPs etc) on that date. May be null if not yet compiled.
* @return int|null
*/
public function getPossibleTurnout(): ?int
@@ -185,47 +199,11 @@ public function getPossibleTurnout(): ?int
}
/**
+ * Get the majority of the ayes. May be negative if the noes won. May be null if not yet compiled.
* @return int|null
*/
public function getAyeMajority(): ?int
{
return $this->ayeMajority;
}
-
-
- /**
- * Returns an associated array of properties the entity requires.
- *
- * @return array
- */
- protected function requiredPropertiesMapping(): array
- {
- return [
- 'id' => 'int',
- 'date' => DateTimeImmutable::class,
- 'number' => 'int',
- 'sourceUrl' => 'string',
- 'debateUrl' => 'string',
- 'motionText' => 'string',
- 'house' => 'string',
- 'motionTitle' => 'string',
- 'originalMotionText' => 'string',
- 'originalMotionTitle' => 'string'
- ];
- }
-
- /**
- * Returns an optional associated array of properties this entity can deal with.
- *
- * @return array
- */
- protected function optionalPropertiesMapping(): array
- {
- return [
- 'rebellions' => 'int',
- 'turnout' => 'int',
- 'possibleTurnout' => 'int',
- 'ayeMajority' => 'int'
- ];
- }
}
diff --git a/src/Entities/README.md b/src/Entities/README.md
index fe00de3..ddc020a 100644
--- a/src/Entities/README.md
+++ b/src/Entities/README.md
@@ -1,2 +1,2 @@
-Entities (aka Model Objects) are just types with little or no logic in them - just really a standardised way
+Factories (aka Model Objects) are just types with little or no logic in them - just really a standardised way
of holding a set type of data.
\ No newline at end of file
diff --git a/src/Exceptions/AbstractException.php b/src/Exceptions/AbstractException.php
index cd226c3..ad41da4 100644
--- a/src/Exceptions/AbstractException.php
+++ b/src/Exceptions/AbstractException.php
@@ -10,7 +10,6 @@
*
* All exceptions should extend from here.
*
- * @package PublicWhip\Exceptions
*/
abstract class AbstractException extends RuntimeException
{
diff --git a/src/Exceptions/Entities/AbstractEntityException.php b/src/Exceptions/Entities/AbstractEntityException.php
deleted file mode 100644
index 1e2aedc..0000000
--- a/src/Exceptions/Entities/AbstractEntityException.php
+++ /dev/null
@@ -1,18 +0,0 @@
-logger = $logger;
+ }
+
+ /**
+ * Convert a string in yyyy-mm-dd format to a DateTimeImmutable.
+ *
+ * @param string $ymd Date in yyyy-mm-dd to be converted. Time will be set to 00:00:00
+ *
+ * @return DateTimeImmutable
+ */
+ public function dateTimeImmutableFromYyyyMmDd(string $ymd): DateTimeImmutable
+ {
+ $this->logger->info('Creating dateTimeImmutable from {ymd} in yyyy-mm-dd format', ['ymd' => $ymd]);
+ $date = DateTimeImmutable::createFromFormat('!Y-m-d', $ymd);
+ if (!$date instanceof DateTimeImmutable) {
+ $errors = DateTimeImmutable::getLastErrors();
+ $encoded = json_encode($errors);
+ $message = sprintf(
+ 'Failed to create DateTimeImmutable from yyyy-mm-dd using the input %s : errors: %s',
+ $ymd,
+ $encoded ?: '[failed]'
+ );
+ $this->logger->warning($message);
+ throw new BadDateTimeException($message);
+ }
+ return $date;
+ }
+}
diff --git a/src/Factories/DateTimeFactoryInterface.php b/src/Factories/DateTimeFactoryInterface.php
new file mode 100644
index 0000000..56eca5f
--- /dev/null
+++ b/src/Factories/DateTimeFactoryInterface.php
@@ -0,0 +1,32 @@
+logger = $logger;
+ }
+
+ /**
+ * Build an entity from an array.
+ *
+ * @param array $data The data to build from.
+ * @param string $entityName Name of the entity to build.
+ * @param array $required The required data configuration.
+ * @param array $optional The optional data configuration.
+ *
+ * @return object
+ */
+ private function buildFromArray(array $data, string $entityName, array $required, array $optional): object
+ {
+ $this->logger->debug('Building ' . $entityName);
+ $new = new $entityName();
+ /**
+ * Handles hydration without having to use reflection.
+ *
+ * @param object $object Object we are modifying.
+ * @param string $property Name of the property.
+ * @param mixed $newValue New value.
+ */
+ $hydrate = function (object $object, string $property, $newValue): void {
+ \Closure::bind(function () use ($property, $newValue): void {
+ $this->$property = $newValue;
+ }, $object, $object)->__invoke();
+ };
+ $fieldsMapped = $this->fillRequiredFields($new, $required, $data, $hydrate, $entityName);
+
+ $fieldsMapped = $this->fillOptionalFields($new, $optional, $data, $hydrate, $entityName, $fieldsMapped);
+ /**
+ * Check that all fields were loaded.
+ */
+ $difference = array_diff(array_keys($data), $fieldsMapped);
+ if (count($difference) > 0) {
+ $message = sprintf(
+ 'The entity %s was passed the following field(s) which it does not know how to handle: %s',
+ self::class,
+ implode(', ', $difference)
+ );
+ $this->logger->warning($message);
+ throw new EntityFactoryPassedUnrecognisedFieldException($message);
+ }
+ return $new;
+ }
+
+ /**
+ * Fill required fields fields.
+ *
+ * @param object $new The object we are hydrating.
+ * @param array $required The configuration for the required fields.
+ * @param array $data The provided data.
+ * @param callable $hydrate Our hydrator.
+ * @param string $entityName Name of the entity
+ *
+ * @return array Mapped fields
+ */
+ private function fillRequiredFields(
+ object $new,
+ array $required,
+ array $data,
+ callable $hydrate,
+ string $entityName
+ ): array
+ {
+ $fieldsMapped = [];
+ foreach ($required as $name => $type) {
+ if (!isset($data[$name])) {
+ $message = sprintf(
+ 'Missing required field %s when building %s',
+ $name,
+ $entityName
+ );
+ $this->logger->warning($message);
+ throw new EntityMissingRequiredFieldException($message);
+ }
+ if (!self::checkType($name, $type, $data[$name])) {
+ $message = sprintf(
+ 'Expected required entity field %s to be %s, but was %s when creating %s',
+ $name,
+ $type,
+ \is_object($data[$name]) ? \get_class($data[$name]) : gettype($data[$name]),
+ $entityName
+ );
+ $this->logger->warning($message);
+ throw new EntityFieldWrongTypeException($message);
+ }
+ $hydrate($new, $name, $data[$name]);
+ $fieldsMapped[] = $name;
+ }
+ return $fieldsMapped;
+ }
+
+ /**
+ * Fill optional fields.
+ *
+ * @param object $new The object we are hydrating.
+ * @param array $optional The configuration for the optional fields.
+ * @param array $data The provided data.
+ * @param callable $hydrate Our hydrator.
+ * @param string $entityName Name of the entity
+ * @param array $fieldsMapped List of fields already mapped.
+ *
+ * @return array Mapped fields
+ */
+ private function fillOptionalFields(
+ object $new,
+ array $optional,
+ array $data,
+ callable $hydrate,
+ string $entityName,
+ array $fieldsMapped
+ ): array
+ {
+ foreach ($optional as $name => $type) {
+ if (isset($data[$name])) {
+ $hydrate($new, $name, null);
+ if (!self::checkType($name, $type, $data[$name])) {
+ $message = sprintf(
+ 'Expected entity field %s to be %s, but was %s when creating %s',
+ $name,
+ $type,
+ \is_object($data[$name]) ? \get_class($data[$name]) : gettype($data[$name]),
+ $entityName
+ );
+ $this->logger->warning($message);
+ throw new EntityFieldWrongTypeException($message);
+ }
+ $hydrate($new, $name, $data[$name]);
+ }
+ $fieldsMapped[] = $name;
+ }
+ return $fieldsMapped;
+ }
+
+ /**
+ * Checks that the field is of the expected type.
+ *
+ * @param string $fieldName Name of the field (for error reporting).
+ * @param string $expectedType String of the expected type.
+ * @param mixed $value The value to check.
+ *
+ * @return bool
+ */
+ private static function checkType(string $fieldName, string $expectedType, $value): bool
+ {
+ $correctType = true;
+ switch ($expectedType) {
+ case 'int':
+ if (!\is_int($value)) {
+ $correctType = false;
+ }
+ break;
+ case 'string':
+ if (!\is_string($value)) {
+ $correctType = false;
+ }
+ break;
+ case DateTimeImmutable::class:
+ if (!$value instanceof DateTimeImmutable) {
+ $correctType = false;
+ }
+ break;
+ default:
+ throw new EntityFieldWrongTypeException(
+ sprintf(
+ 'Bad definition found for field %s in %s',
+ $fieldName,
+ self::class
+ )
+ );
+ }
+ return $correctType;
+ }
+
+ /**
+ * Build a division.
+ *
+ * @param array $data Data to build the entity with.
+ *
+ * @return DivisionEntity
+ */
+ public function division(array $data): DivisionEntity
+ {
+ /** @var DivisionEntity $entity */
+ $entity = $this->buildFromArray(
+ $data,
+ DivisionEntity::class,
+ [
+ 'divisionId' => 'int',
+ 'date' => DateTimeImmutable::class,
+ 'number' => 'int',
+ 'sourceUrl' => 'string',
+ 'debateUrl' => 'string',
+ 'motionText' => 'string',
+ 'house' => 'string',
+ 'motionTitle' => 'string',
+ 'originalMotionText' => 'string',
+ 'originalMotionTitle' => 'string'
+ ],
+ [
+ 'rebellions' => 'int',
+ 'turnout' => 'int',
+ 'possibleTurnout' => 'int',
+ 'ayeMajority' => 'int'
+ ]
+ );
+ return $entity;
+ }
+}
diff --git a/src/Factories/EntityFactoryInterface.php b/src/Factories/EntityFactoryInterface.php
new file mode 100644
index 0000000..6c7bb18
--- /dev/null
+++ b/src/Factories/EntityFactoryInterface.php
@@ -0,0 +1,32 @@
+ $data Data to build the entity with.
+ *
+ * @return DivisionEntity
+ */
+ public function division(array $data): DivisionEntity;
+}
diff --git a/src/Factories/README.md b/src/Factories/README.md
new file mode 100644
index 0000000..427f156
--- /dev/null
+++ b/src/Factories/README.md
@@ -0,0 +1 @@
+Factories provide 'new' things upon request.
\ No newline at end of file
diff --git a/src/Providers/CallableResolverProvider.php b/src/Providers/CallableResolverProvider.php
new file mode 100644
index 0000000..2caceba
--- /dev/null
+++ b/src/Providers/CallableResolverProvider.php
@@ -0,0 +1,41 @@
+callableResolver = $callableResolver;
+ }
+
+ /**
+ * @param mixed $toResolve What to resolve.
+ *
+ * @return callable The callable.
+ * @throws NotCallableException
+ */
+ public function resolve($toResolve): callable
+ {
+ return $this->callableResolver->resolve($toResolve);
+ }
+}
diff --git a/src/Providers/CallableResolverProviderInterface.php b/src/Providers/CallableResolverProviderInterface.php
new file mode 100644
index 0000000..167aa48
--- /dev/null
+++ b/src/Providers/CallableResolverProviderInterface.php
@@ -0,0 +1,29 @@
+invoker = $invoker;
+ }
+
+ /**
+ * Invoke a route callable.
+ *
+ * @param callable $callable The callable to invoke using the strategy.
+ * @param ServerRequestInterface $request The request object.
+ * @param ResponseInterface $response The response object.
+ * @param string[] $routeArguments The route's placeholder arguments
+ *
+ * @return ResponseInterface|string The response from the callable.
+ * @throws InvocationException
+ * @throws NotCallableException
+ * @throws NotEnoughParametersException
+ */
+ public function __invoke(
+ callable $callable,
+ ServerRequestInterface $request,
+ ResponseInterface $response,
+ array $routeArguments
+ )
+ {
+ // Inject the request and response by parameter name
+ $parameters = [
+ 'request' => $request,
+ 'response' => $response,
+ ];
+
+ // Inject the route arguments by name
+ $parameters += $routeArguments;
+
+ // Inject the attributes defined on the request
+ $parameters += $request->getAttributes();
+
+ return $this->invoker->call($callable, $parameters);
+ }
+}
diff --git a/src/Providers/DatabaseProvider.php b/src/Providers/DatabaseProvider.php
index f74edf8..d07fa39 100644
--- a/src/Providers/DatabaseProvider.php
+++ b/src/Providers/DatabaseProvider.php
@@ -12,7 +12,6 @@
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Schema\Builder as SchemaBuilder;
use Illuminate\Events\Dispatcher;
@@ -21,7 +20,11 @@
/**
* Class DatabaseProvider.
- * @package PublicWhip\Providers
+ *
+ * We just really do the minimum to get Eloquent up and running, yet it's quite complex and lots
+ * of coupling.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
final class DatabaseProvider implements DatabaseProviderInterface
{
@@ -32,7 +35,7 @@ final class DatabaseProvider implements DatabaseProviderInterface
private const DEFAULT_CONNECTION_NAME = 'default';
/**
- * @var Container $container
+ * @var Container $container An Illuminate Container.
*/
private $container;
@@ -43,7 +46,14 @@ final class DatabaseProvider implements DatabaseProviderInterface
/**
* DatabaseProvider constructor.
- * @param array $config
+ *
+ * Turn off some PHPMD warnings as Eloquent requires static settings. As a Provider,
+ * this isn't too much of a problem.
+ *
+ * @SuppressWarnings(PHPMD.StaticAccess)
+ *
+ * @param string[] $config Configuration settings.
+ *
*/
public function __construct(array $config)
{
@@ -64,18 +74,24 @@ public function __construct(array $config)
$this->addConnection($config);
$this->setEventDispatcher(new Dispatcher($this->container));
- Eloquent::setConnectionResolver($this->manager);
+ Model::setConnectionResolver($this->manager);
$dispatcher = $this->getEventDispatcher();
- if (null !== $dispatcher) {
- Eloquent::setEventDispatcher($dispatcher);
+ if (null === $dispatcher) {
+ return;
}
+
+ Model::setEventDispatcher($dispatcher);
}
/**
- * @param array $config
- * @param string $name
+ * Add a new connection.
+ *
+ * @param string[] $config Configuration settings.
+ * @param string|null $name Name of the configuration.
+ *
+ * @return void
*/
- public function addConnection(array $config, string $name = null): void
+ public function addConnection(array $config, ?string $name = null): void
{
$name = $name ?: self::DEFAULT_CONNECTION_NAME;
$connections = $this->container['config']['database.connections'];
@@ -84,7 +100,11 @@ public function addConnection(array $config, string $name = null): void
}
/**
- * @param Dispatcher $dispatcher
+ * Set the event dispatcher.
+ *
+ * @param Dispatcher $dispatcher Dispatcher.
+ *
+ * @return void
*/
public function setEventDispatcher(Dispatcher $dispatcher): void
{
@@ -92,7 +112,9 @@ public function setEventDispatcher(Dispatcher $dispatcher): void
}
/**
- * @return null|Dispatcher
+ * Get the event dispatcher.
+ *
+ * @return Dispatcher|null
*/
public function getEventDispatcher(): ?Dispatcher
{
@@ -103,37 +125,49 @@ public function getEventDispatcher(): ?Dispatcher
}
/**
- * @param string $table
- * @param Connection|null $connection
+ * Get a table to start querying.
+ *
+ * @param string $table Name of the table.
+ * @param Connection|null $connection Connection to use.
+ *
* @return QueryBuilder
*/
- public function table(string $table, Connection $connection = null): QueryBuilder
+ public function table(string $table, ?Connection $connection = null): QueryBuilder
{
$connectionName = $connection ? $connection->getName() : self::DEFAULT_CONNECTION_NAME;
return $this->getConnection($connectionName)->table($table);
}
/**
- * @param string $name
+ * Get a named connection.
+ *
+ * @param string|null $name Name of the connection (or null for default)
+ *
* @return Connection
*/
- public function getConnection(string $name = null): Connection
+ public function getConnection(?string $name = null): Connection
{
return $this->manager->connection($name);
}
/**
- * @param Connection|null $connection
+ * Get the schema builder.
+ *
+ * @param Connection|null $connection Connection to use.
+ *
* @return SchemaBuilder
*/
- public function schema(Connection $connection = null): SchemaBuilder
+ public function schema(?Connection $connection = null): SchemaBuilder
{
$connectionName = $connection ? $connection->getName() : self::DEFAULT_CONNECTION_NAME;
return $this->getConnection($connectionName)->getSchemaBuilder();
}
/**
- * @param string $model
+ * Get an eloquent model.
+ *
+ * @param string $model Name of the model.
+ *
* @return mixed
*/
public function query(string $model)
@@ -145,6 +179,8 @@ public function query(string $model)
}
/**
+ * Get the eloquent container.
+ *
* @return Container
*/
public function getContainer(): Container
@@ -153,7 +189,11 @@ public function getContainer(): Container
}
/**
- * @param Container $container
+ * Set the container.
+ *
+ * @param Container $container Set the eloquent container.
+ *
+ * @return void
*/
public function setContainer(Container $container): void
{
@@ -161,7 +201,10 @@ public function setContainer(Container $container): void
}
/**
- * @param string $fetchMode
+ * Set the fetch mode.
+ *
+ * @param string $fetchMode Fetch mode.
+ *
* @return DatabaseProviderInterface
*/
public function setFetchMode(string $fetchMode): DatabaseProviderInterface
@@ -171,6 +214,8 @@ public function setFetchMode(string $fetchMode): DatabaseProviderInterface
}
/**
+ * Get the database manager.
+ *
* @return DatabaseManager
*/
public function getDatabaseManager(): DatabaseManager
@@ -181,7 +226,10 @@ public function getDatabaseManager(): DatabaseManager
/**
* Addable to a debugger.
*
- * @param DebuggerProviderInterface $debugger
+ * @param DebuggerProviderInterface $debugger Debugger to add.
+ *
+ * @return void
+ *
* @throws DebugBarException
*/
public function addToDebugger(DebuggerProviderInterface $debugger): void
diff --git a/src/Providers/DatabaseProviderInterface.php b/src/Providers/DatabaseProviderInterface.php
index 2917ccd..a9ca958 100644
--- a/src/Providers/DatabaseProviderInterface.php
+++ b/src/Providers/DatabaseProviderInterface.php
@@ -12,81 +12,121 @@
use Illuminate\Events\Dispatcher;
/**
- * Interface DatabaseProviderInterface
- * @package PublicWhip\Providers
+ * Class DatabaseProvider.
*/
interface DatabaseProviderInterface
{
+
/**
* DatabaseProvider constructor.
- * @param array $config
+ *
+ * @param string[] $config Configuration settings.
*/
public function __construct(array $config);
/**
- * @param array $config
- * @param string $name
+ * Add a new connection.
+ *
+ * @param string[] $config Configuration settings.
+ * @param string|null $name Name of the configuration.
+ *
+ * @return void
*/
- public function addConnection(array $config, string $name = null);
+ public function addConnection(array $config, ?string $name = null): void;
/**
- * @param string $name
- * @return Connection
+ * Set the event dispatcher.
+ *
+ * @param Dispatcher $dispatcher Dispatcher.
+ *
+ * @return void
+ */
+ public function setEventDispatcher(Dispatcher $dispatcher): void;
+
+ /**
+ * Get the event dispatcher.
+ *
+ * @return Dispatcher|null
*/
- public function getConnection(string $name = null): Connection;
+ public function getEventDispatcher(): ?Dispatcher;
/**
- * @param string $table
- * @param Connection|null $connection
+ * Get a table to start querying.
+ *
+ * @param string $table Name of the table.
+ * @param Connection|null $connection Connection to use.
+ *
* @return QueryBuilder
*/
- public function table(string $table, Connection $connection = null): QueryBuilder;
+ public function table(string $table, ?Connection $connection = null): QueryBuilder;
/**
- * @param Connection|null $connection
+ * Get a named connection.
+ *
+ * @param string|null $name Name of the connection (or null for default)
+ *
+ * @return Connection
+ */
+ public function getConnection(?string $name = null): Connection;
+
+ /**
+ * Get the schema builder.
+ *
+ * @param Connection|null $connection Connection to use.
+ *
* @return SchemaBuilder
*/
- public function schema(Connection $connection = null): SchemaBuilder;
+ public function schema(?Connection $connection = null): SchemaBuilder;
/**
- * @param string $model
+ * Get an eloquent model.
+ *
+ * @param string $model Name of the model.
+ *
* @return mixed
*/
public function query(string $model);
/**
+ * Get the eloquent container.
+ *
* @return Container
*/
public function getContainer(): Container;
/**
- * @param Container $container
+ * Set the container.
+ *
+ * @param Container $container Set the eloquent container.
+ *
+ * @return void
*/
public function setContainer(Container $container): void;
/**
- * @return null|Dispatcher
- */
- public function getEventDispatcher(): ?Dispatcher;
-
- /**
- * @param Dispatcher $dispatcher
- */
- public function setEventDispatcher(Dispatcher $dispatcher): void;
-
- /**
- * @param string $fetchMode
+ * Set the fetch mode.
+ *
+ * @param string $fetchMode Fetch mode.
+ *
+ *
* @return DatabaseProviderInterface
*/
public function setFetchMode(string $fetchMode): DatabaseProviderInterface;
/**
+ * Get the database manager.
+ *
* @return DatabaseManager
*/
public function getDatabaseManager(): DatabaseManager;
/**
- * @param DebuggerProviderInterface $debugger
+ * Addable to a debugger.
+ *
+ * @param DebuggerProviderInterface $debugger Debugger to add.
+ *
+ * @return void
+ *
* @throws DebugBarException
*/
public function addToDebugger(DebuggerProviderInterface $debugger): void;
diff --git a/src/Providers/DebuggerProvider.php b/src/Providers/DebuggerProvider.php
index dad1133..41a369a 100644
--- a/src/Providers/DebuggerProvider.php
+++ b/src/Providers/DebuggerProvider.php
@@ -4,16 +4,15 @@
namespace PublicWhip\Providers;
use DebugBar\DataCollector\DataCollectorInterface;
-use DebugBar\DataCollector\ExceptionsCollector;
use DebugBar\DataCollector\MessagesAggregateInterface;
use DebugBar\DebugBarException;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
-use Exception;
+use Throwable;
/**
* Class DebuggerProvider
- * @package PublicWhip\Providers
+ *
*/
final class DebuggerProvider implements DebuggerProviderInterface
{
@@ -28,10 +27,10 @@ final class DebuggerProvider implements DebuggerProviderInterface
*/
private $renderer;
-
/**
* DebuggerProvider constructor.
- * @param bool $active
+ *
+ * @param bool $active Are we active?
*/
public function __construct(bool $active)
{
@@ -43,7 +42,26 @@ public function __construct(bool $active)
}
/**
- * @param DataCollectorInterface $collector
+ * Set the base url for output rendering.
+ *
+ * @param string $url Base Url.
+ *
+ * @return void
+ */
+ public function setBaseUrl(string $url): void
+ {
+ if (null !== $this->renderer) {
+ $this->renderer->setBaseUrl($url);
+ }
+ }
+
+ /**
+ * Add a new data collector to the debugger.
+ *
+ * @param DataCollectorInterface $collector The data collector we are adding.
+ *
+ * @return void
+ *
* @throws DebugBarException
*/
public function addDataCollector(DataCollectorInterface $collector): void
@@ -54,7 +72,11 @@ public function addDataCollector(DataCollectorInterface $collector): void
}
/**
- * @param MessagesAggregateInterface $collector
+ * Add a new messages collector.
+ *
+ * @param MessagesAggregateInterface $collector The collector we are adding.
+ *
+ * @return void
*/
public function addMessagesAggregateCollector(MessagesAggregateInterface $collector): void
{
@@ -64,10 +86,14 @@ public function addMessagesAggregateCollector(MessagesAggregateInterface $collec
}
/**
- * @param string $msg
- * @param string $level
+ * Log a message.
+ *
+ * @param string $msg The message we are logging.
+ * @param string|null $level The severity level we are logging.
+ *
+ * @return void
*/
- public function addMessage(string $msg, string $level = null): void
+ public function addMessage(string $msg, ?string $level = null): void
{
if (null !== $this->debugbar) {
$level = $level ?: 'info';
@@ -76,26 +102,22 @@ public function addMessage(string $msg, string $level = null): void
}
/**
- * @param Exception $e
+ * Add an exception.
+ *
+ * @param Throwable $throwable The throwable we are handling.
+ *
+ * @return void
*/
- public function addException(Exception $e) : void
+ public function addThrowable(Throwable $throwable): void
{
if (null !== $this->debugbar) {
- $this->debugbar['exceptions']->addException($e);
- }
- }
-
- /**
- * @param string $url
- */
- public function setBaseUrl(string $url): void
- {
- if (null !== $this->renderer) {
- $this->renderer->setBaseUrl($url);
+ $this->debugbar['exceptions']->addException($throwable);
}
}
/**
+ * Get the base URL for output rendering.
+ *
* @return string
*/
public function getBaseUrl(): string
@@ -107,6 +129,8 @@ public function getBaseUrl(): string
}
/**
+ * Get the base path for output rendering.
+ *
* @return string
*/
public function getBasePath(): string
@@ -118,6 +142,8 @@ public function getBasePath(): string
}
/**
+ * Get the head text.
+ *
* @return string
*/
public function renderHead(): string
@@ -129,6 +155,8 @@ public function renderHead(): string
}
/**
+ * Get the actual debug bar.
+ *
* @return string
*/
public function renderBar(): string
diff --git a/src/Providers/DebuggerProviderInterface.php b/src/Providers/DebuggerProviderInterface.php
index 11cf33a..40e581d 100644
--- a/src/Providers/DebuggerProviderInterface.php
+++ b/src/Providers/DebuggerProviderInterface.php
@@ -6,65 +6,93 @@
use DebugBar\DataCollector\DataCollectorInterface;
use DebugBar\DataCollector\MessagesAggregateInterface;
use DebugBar\DebugBarException;
-use Exception;
+use Throwable;
/**
- * Class DebuggerProviderInterface
- * @package PublicWhip\Providers
+ * Class DebuggerProvider
*/
interface DebuggerProviderInterface
{
/**
* DebuggerProvider constructor.
- * @param bool $active
+ *
+ * @param bool $active Are we active?
*/
public function __construct(bool $active);
/**
- * @param DataCollectorInterface $collector
- * @throws DebugBarException
+ * Set the base url for output rendering.
+ *
+ * @param string $url Base Url.
+ *
+ * @return void
*/
- public function addDataCollector(DataCollectorInterface $collector): void;
+ public function setBaseUrl(string $url): void;
/**
- * @param MessagesAggregateInterface $collector
+ * Add a new data collector to the debugger.
+ *
+ * @param DataCollectorInterface $collector The data collector we are adding.
+ *
+ * @return void
+ *
+ * @throws DebugBarException
*/
- public function addMessagesAggregateCollector(MessagesAggregateInterface $collector): void;
+ public function addDataCollector(DataCollectorInterface $collector): void;
/**
- * @param string $msg
- * @param string $level
+ * Add a new messages collector.
+ *
+ * @param MessagesAggregateInterface $collector The collector we are adding.
+ *
+ * @return void
*/
- public function addMessage(string $msg, string $level = null): void;
-
+ public function addMessagesAggregateCollector(MessagesAggregateInterface $collector): void;
/**
- * @param Exception $e
+ * Log a message.
+ *
+ * @param string $msg The message we are logging.
+ * @param string|null $level The severity level we are logging.
+ *
+ * @return void
*/
- public function addException(Exception $e) : void;
+ public function addMessage(string $msg, ?string $level = null): void;
/**
- * @param string $url
+ * Add an exception.
+ *
+ * @param Throwable $throwable The throwable we are handling.
+ *
+ * @return void
*/
- public function setBaseUrl(string $url): void;
+ public function addThrowable(Throwable $throwable): void;
/**
+ * Get the base URL for output rendering.
+ *
* @return string
*/
public function getBaseUrl(): string;
/**
+ * Get the base path for output rendering.
+ *
* @return string
*/
public function getBasePath(): string;
/**
+ * Get the head text.
+ *
* @return string
*/
public function renderHead(): string;
/**
+ * Get the actual debug bar.
+ *
* @return string
*/
public function renderBar(): string;
diff --git a/src/Providers/DebuggerTwigExtension.php b/src/Providers/DebuggerTwigExtension.php
index 2eaac6b..38e3ec4 100644
--- a/src/Providers/DebuggerTwigExtension.php
+++ b/src/Providers/DebuggerTwigExtension.php
@@ -12,26 +12,25 @@
* Class DebuggerTwigExtension.
*
* Adds the debug bar to twig.
- *
- * @package PublicWhip\Providers
*/
-class DebuggerTwigExtension extends AbstractExtension
+final class DebuggerTwigExtension extends AbstractExtension
{
/**
- * @var UriInterface $uri
+ * @var UriInterface $uri Our current url.
*/
private $uri;
/**
- * @var DebuggerProviderInterface $debugger
+ * @var DebuggerProviderInterface $debugger The debugger.
*/
private $debugger;
/**
* DebuggerTwigExtension constructor.
- * @param RequestInterface $request
- * @param DebuggerProviderInterface $debugger
+ *
+ * @param RequestInterface $request The request.
+ * @param DebuggerProviderInterface $debugger The debugger.
*/
public function __construct(RequestInterface $request, DebuggerProviderInterface $debugger)
{
@@ -41,6 +40,7 @@ public function __construct(RequestInterface $request, DebuggerProviderInterface
}
/**
+ * Get the base url.
* @return string
*/
public function baseUrlFunction(): string
@@ -52,7 +52,8 @@ public function baseUrlFunction(): string
}
/**
- * @return array
+ * Get the twig functions
+ * @return TwigFunction[]
*/
public function getFunctions(): array
{
@@ -63,6 +64,7 @@ public function getFunctions(): array
}
/**
+ * Get the debug header.
* @return string
*/
public function debugHeadFunction(): string
@@ -71,6 +73,7 @@ public function debugHeadFunction(): string
}
/**
+ * Get the debug bar main body.
* @return string
*/
public function debugBarFunction(): string
diff --git a/src/Providers/MailerProvider.php b/src/Providers/MailerProvider.php
index b71a21c..34466f9 100644
--- a/src/Providers/MailerProvider.php
+++ b/src/Providers/MailerProvider.php
@@ -5,7 +5,6 @@
use DebugBar\Bridge\SwiftMailer\SwiftLogCollector;
use DebugBar\Bridge\SwiftMailer\SwiftMailCollector;
-use DebugBar\DebugBarException;
use RuntimeException;
use Swift_Mailer;
use Swift_Message;
@@ -15,88 +14,90 @@
/**
* Class MailerProvider
- * @package PublicWhip\Providers
- * @TODO Uses 'new'/factories: can we change to use DI?
+ *
+ * @TODO Uses 'new' can we change to use DI?
*/
final class MailerProvider implements MailerProviderInterface
{
/**
- * @var Swift_Mailer $mailer
+ * @var Swift_Mailer $mailer The mailing engine.
*/
private $mailer;
/**
* MailerProvider constructor.
- * @param array $options
+ *
+ * @param string[] $options Configuration options.
*/
public function __construct(array $options)
{
$transport = $options['transport'] ?: '';
switch ($transport) {
case 'null':
- $transport = Swift_NullTransport::newInstance();
+ $transport = new Swift_NullTransport();
break;
case 'smtp':
- $transport = Swift_SmtpTransport::newInstance($options['host'], $options['port'])
+ $transport = (new Swift_SmtpTransport($options['host'], (int)$options['port']))
->setUsername($options['username'])
->setPassword($options['password']);
break;
case 'sendmail':
- $transport = Swift_SendmailTransport::newInstance($options['command']);
+ $transport = new Swift_SendmailTransport($options['command']);
break;
default:
throw new RuntimeException('Unrecognised mail transport');
}
- $this->mailer = Swift_Mailer::newInstance($transport);
+ $this->mailer = new Swift_Mailer($transport);
}
/**
- * @param string $subject
- * @param string $to
- * @param array $body
+ * Send a multipart email.
+ *
+ * @param string $subject The subject of the email.
+ * @param string $toAddress Who it is going to.
+ * @param string $toName The name of the person it is going to.
+ * @param string[] $body What the body of the email is.
+ *
* @return int The number of successful recipients. Can be 0 which indicates failure
*/
- public function sendMultipartMail(string $subject, string $to, array $body): int
+ public function sendMultipartMail(string $subject, string $toAddress, string $toName, array $body): int
{
- $message = Swift_Message::newInstance($subject)->setTo($to);
- $first = true;
+ if (0 === count($body)) {
+ return 0;
+ }
+ $message = Swift_Message::newInstance($subject)->setTo($toAddress, $toName);
+ $message->addPart($body[0]);
+ next($body);
foreach ($body as $part) {
- if ($first) {
- $message->setBody($part);
- $first = false;
- } else {
- $message->addPart($part);
- }
+ $message->addPart($part);
}
- return $this->send($message);
- }
-
- /**
- * @param Swift_Message $message
- * @return int The number of successful recipients. Can be 0 which indicates failure
- */
- private function send(Swift_Message $message): int
- {
return $this->mailer->send($message);
}
/**
- * @param string $subject
- * @param string $to
- * @param string $body
+ * Send a simple plain text email.
+ *
+ * @param string $subject The subject of the email.
+ * @param string $toAddress Who it is going to.
+ * @param string $toName The name of the person it is going to.
+ * @param string $body What the body of the email is.
+ *
* @return int The number of successful recipients. Can be 0 which indicates failure
*/
- public function sendMail(string $subject, string $to, string $body): int
+ public function sendMail(string $subject, string $toAddress, string $toName, string $body): int
{
- $message = Swift_Message::newInstance($subject)->setTo($to);
+ $message = (new Swift_Message($subject))->setTo($toAddress, $toName);
$message->setBody($body);
- return $this->send($message);
+ return $this->mailer->send($message);
}
/**
- * @param DebuggerProviderInterface $debugger
- * @throws DebugBarException
+ * Add a debugger.
+ *
+ * @param DebuggerProviderInterface $debugger The debugger to add.
+ *
+ * @return void
*/
public function addToDebugger(DebuggerProviderInterface $debugger): void
{
diff --git a/src/Providers/MailerProviderInterface.php b/src/Providers/MailerProviderInterface.php
index e97d707..6086c08 100644
--- a/src/Providers/MailerProviderInterface.php
+++ b/src/Providers/MailerProviderInterface.php
@@ -3,39 +3,51 @@
namespace PublicWhip\Providers;
-use DebugBar\DebugBarException;
-
/**
- * Class MailerProviderInterface
- * @package PublicWhip\Providers
+ * Class MailerProvider
+ *
+ * @TODO Uses 'new' can we change to use DI?
*/
interface MailerProviderInterface
{
+
/**
* MailerProvider constructor.
- * @param array $options
+ *
+ * @param string[] $options Configuration options.
*/
public function __construct(array $options);
/**
- * @param string $subject
- * @param string $to
- * @param array $body
+ * Send a multipart email.
+ *
+ * @param string $subject The subject of the email.
+ * @param string $toAddress Who it is going to.
+ * @param string $toName The name of the person it is going to.
+ * @param string[] $body What the body of the email is.
+ *
* @return int The number of successful recipients. Can be 0 which indicates failure
*/
- public function sendMultipartMail(string $subject, string $to, array $body): int;
+ public function sendMultipartMail(string $subject, string $toAddress, string $toName, array $body): int;
/**
- * @param string $subject
- * @param string $to
- * @param string $body
+ * Send a simple plain text email.
+ *
+ * @param string $subject The subject of the email.
+ * @param string $toAddress Who it is going to.
+ * @param string $toName The name of the person it is going to.
+ * @param string $body What the body of the email is.
+ *
* @return int The number of successful recipients. Can be 0 which indicates failure
*/
- public function sendMail(string $subject, string $to, string $body): int;
+ public function sendMail(string $subject, string $toAddress, string $toName, string $body): int;
/**
- * @param DebuggerProviderInterface $debugger
- * @throws DebugBarException
+ * Add a debugger.
+ *
+ * @param DebuggerProviderInterface $debugger The debugger to add.
+ *
+ * @return void
*/
public function addToDebugger(DebuggerProviderInterface $debugger): void;
}
diff --git a/src/Providers/README.md b/src/Providers/README.md
index 32dcd93..5515a96 100644
--- a/src/Providers/README.md
+++ b/src/Providers/README.md
@@ -1,3 +1,7 @@
Providers are (usually) dependency injected objects which provider a service - such as a database, debugger or
-similar. They are called Providers to help distinguish them from 'domain specific' services which handle the
-'business logic' which are kept in the 'Services' folder.
\ No newline at end of file
+similar.
+
+They are called Providers to help distinguish them from 'domain specific' services which handle the
+'business logic' which are kept in the 'Services' folder.
+
+Providers only tend to return the same thing to operator on - if you need 'new' things, look at factories.
\ No newline at end of file
diff --git a/src/Providers/TemplateProvider.php b/src/Providers/TemplateProvider.php
new file mode 100644
index 0000000..c4c3e47
--- /dev/null
+++ b/src/Providers/TemplateProvider.php
@@ -0,0 +1,43 @@
+twig = $twig;
+ }
+
+ /**
+ * Output rendered template
+ *
+ * @param ResponseInterface $response Our response to populate.
+ * @param string $template Template pathname relative to templates directory
+ * @param array $data Associative array of template variables
+ *
+ * @return ResponseInterface
+ */
+ public function render(ResponseInterface $response, string $template, array $data = []): ResponseInterface
+ {
+ return $this->twig->render($response, $template, $data);
+ }
+}
diff --git a/src/Providers/TemplateProviderInterface.php b/src/Providers/TemplateProviderInterface.php
new file mode 100644
index 0000000..929b027
--- /dev/null
+++ b/src/Providers/TemplateProviderInterface.php
@@ -0,0 +1,32 @@
+ $data Associative array of template variables
+ *
+ * @return ResponseInterface
+ */
+ public function render(ResponseInterface $response, string $template, array $data = []): ResponseInterface;
+}
diff --git a/src/Providers/WikiParserProvider.php b/src/Providers/WikiParserProvider.php
index b0d3c04..6d19809 100644
--- a/src/Providers/WikiParserProvider.php
+++ b/src/Providers/WikiParserProvider.php
@@ -9,7 +9,9 @@
/**
* Class WikiParserProvider.
*
- * @package PublicWhip\Providers
+ * Turn off all warnings as this code is a bit of a mess, even though it's been tidied up since v1.
+ *
+ * @SuppressWarnings(PHPMD)
*/
final class WikiParserProvider implements WikiParserProviderInterface
{
@@ -34,10 +36,10 @@ final class WikiParserProvider implements WikiParserProviderInterface
*/
private $logger;
-
/**
* WikiParserProvider constructor.
- * @param LoggerInterface $logger
+ *
+ * @param LoggerInterface $logger Logger.
*/
public function __construct(LoggerInterface $logger)
{
@@ -46,59 +48,22 @@ public function __construct(LoggerInterface $logger)
/**
* Get the division title.
+ *
* @param string $wiki The wiki text.
* @param string $default Default title to return.
+ *
* @return string
*/
public function parseDivisionTitle(string $wiki, string $default): string
{
+ if ('' === $wiki) {
+ return $default;
+ }
if (preg_match('/--- DIVISION TITLE ---(.*)--- MOTION EFFECT/', $wiki, $matches)) {
+ $this->logger->debug(sprintf('Parsed wiki title %s', $wiki));
return $matches[1];
}
- return $default;
- }
-
- /**
- * Get the motion text from the wiki - suitable for editing.
- * @param string $wiki
- * @param string $default
- * @return string
- */
- public function parseMotionTextForEdit(string $wiki, string $default): string
- {
- if (preg_match('/--- MOTION EFFECT ---(.*)--- COMMENT/s', $wiki, $matches)) {
- $text = $matches[1];
- $text = preg_replace(
- "/(.*)<\/p>/",
- '
\\1
',
- $text
- );
- if (null === $text) {
- throw new WikiFailedRegExp(
- sprintf(
- 'Failed with %s',
- array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]
- )
- );
- }
- $text = preg_replace(
- "/(.*)<\/p>/",
- '
\\1
',
- $text
- );
- if (null === $text) {
- throw new WikiFailedRegExp(
- sprintf(
- 'Failed with %s',
- array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]
- )
- );
- }
- if (!is_string($text)) {
- throw new WikiFailedRegExp('Expected string');
- }
- return $text;
- }
+ $this->logger->debug(sprintf('Could not parse wiki title %s', $wiki));
return $default;
}
@@ -107,12 +72,16 @@ public function parseMotionTextForEdit(string $wiki, string $default): string
*
* @see https://github.com/publicwhip/publicwhip/blob/a4899135b6957abae85da3fc93c4cc3cf9e4fbc1/website/wiki.inc#L112
*
- * @param string $wiki
- * @param string $default
+ * @param string $wiki The text to parse.
+ * @param string $default The text to return if it's not a valid wiki text.
+ *
* @return string
*/
public function parseMotionText(string $wiki, string $default): string
{
+ if ('' === $wiki) {
+ return $default;
+ }
$motion = $this->parseMotionTextForEdit($wiki, $default);
if (!preg_match('/<\/.*?>/', $motion)) {
$motionLines = explode("\n", $motion);
@@ -227,11 +196,7 @@ public function parseMotionText(string $wiki, string $default): string
$res[] = $motionLine;
- if (0 === $binUL) {
- $res[] = '
';
- } else {
- $res[] = '';
- }
+ $res[] = (0 === $binUL) ? '' : '';
}
if ($binUL) {
$res[] = '';
@@ -239,12 +204,66 @@ public function parseMotionText(string $wiki, string $default): string
$motion = implode("\n", $res);
}
$motion = $this->cleanHtml($motion);
+ $this->logger->debug(sprintf('Returning motion %s', $motion));
+
return $motion;
}
+ /**
+ * Get the motion text from the wiki - suitable for editing.
+ *
+ * @param string $wiki Wiki text to parse.
+ * @param string $default If the wiki text was not valid, the text to be returned instead.
+ *
+ * @return string
+ */
+ public function parseMotionTextForEdit(string $wiki, string $default): string
+ {
+ if ('' === $wiki) {
+ return $default;
+ }
+ if (!preg_match('/--- MOTION EFFECT ---(.*)--- COMMENT/s', $wiki, $matches)) {
+ $this->logger->debug(sprintf('Could not parse motion text %s', $wiki));
+ return $default;
+ }
+ $text = $matches[1];
+ $text = preg_replace(
+ "/(.*)<\/p>/",
+ '
\\1
',
+ $text
+ );
+ if (null === $text) {
+ throw new WikiFailedRegExp(
+ sprintf(
+ 'Failed with %s',
+ array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]
+ )
+ );
+ }
+ $text = preg_replace(
+ "/(.*)<\/p>/",
+ '
\\1
',
+ $text
+ );
+ if (null === $text) {
+ throw new WikiFailedRegExp(
+ sprintf(
+ 'Failed with %s',
+ array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]
+ )
+ );
+ }
+ if (!\is_string($text)) {
+ throw new WikiFailedRegExp('Expected string');
+ }
+ return $text;
+ }
+
/**
* Cleans the HTML.
- * @param string $html
+ *
+ * @param string $html The html to clean.
+ *
* @return string
*/
public function cleanHtml(string $html): string
@@ -256,10 +275,12 @@ public function cleanHtml(string $html): string
* Takes our safe html and converts it to normal html.
*
* @see https://github.com/publicwhip/publicwhip/blob/a4899135b6957abae85da3fc93c4cc3cf9e4fbc1/website/pretty.inc#L418
- * @param string $html
+ *
+ * @param string $html The 'safe' html to convert back to normal html.
+ *
* @return string
*/
- public function safeHtmlToNormalHtml(string $html) : string
+ public function safeHtmlToNormalHtml(string $html): string
{
$patterns = [
"/&(#?[A-Za-z0-9]+?;)/",
@@ -284,7 +305,9 @@ public function safeHtmlToNormalHtml(string $html) : string
* Strips bad html.
*
* @see https://github.com/publicwhip/publicwhip/blob/a4899135b6957abae85da3fc93c4cc3cf9e4fbc1/website/pretty.inc#L313
- * @param string $text
+ *
+ * @param string $text Text to strip unwanted html from.
+ *
* @return string
*/
public function stripBadHtml(string $text): string
@@ -336,7 +359,9 @@ public function stripBadHtml(string $text): string
* Only keeps approved attributes of HTML.
*
* @see https://github.com/publicwhip/publicwhip/blob/a4899135b6957abae85da3fc93c4cc3cf9e4fbc1/website/pretty.inc#L334
- * @param array $arr
+ *
+ * @param string[] $arr Html element and items to strip away.
+ *
* @return string
*/
public function filterHtmlAttributes(array $arr): string
@@ -388,8 +413,7 @@ public function filterHtmlAttributes(array $arr): string
if ($noSpecial) {
preg_match_all("/(?:$noSpecial)\s*=\s*\"[^\s\">]+\"/is", $attributes, $matches);
- $prepared =
- array_merge($prepared, str_replace('"', self::SAFE_QUOTE, $matches[0]));
+ $prepared = array_merge($prepared, str_replace('"', self::SAFE_QUOTE, $matches[0]));
preg_match_all("/(?:$noSpecial)\s*=\s*'[^\s'>]+'/is", $attributes, $matches);
$prepared = array_merge($prepared, $matches[0]);
preg_match_all("/(?:$noSpecial)\s*=\s*[^\s>'\"][^\s>]*/is", $attributes, $matches);
@@ -397,8 +421,7 @@ public function filterHtmlAttributes(array $arr): string
}
if ($special) {
preg_match_all("/(?:$special)\s*=\s*\"[^\"]*\"/is", $attributes, $matches);
- $prepared =
- array_merge($prepared, str_replace('"', self::SAFE_QUOTE, $matches[0]));
+ $prepared = array_merge($prepared, str_replace('"', self::SAFE_QUOTE, $matches[0]));
preg_match_all("/(?:$special)\s*=\s*'[^']*'/is", $attributes, $matches);
$prepared = array_merge($prepared, $matches[0]);
preg_match_all("/(?:$special)\s*=\s*[^\s>'\"][^\s>]*/is", $attributes, $matches);
diff --git a/src/Providers/WikiParserProviderInterface.php b/src/Providers/WikiParserProviderInterface.php
index 91e5a13..8c8d34a 100644
--- a/src/Providers/WikiParserProviderInterface.php
+++ b/src/Providers/WikiParserProviderInterface.php
@@ -1,35 +1,40 @@
databaseProvider = $databaseProvider;
+ $this->entityFactory = $entityFactory;
+ $this->dateTimeFactory = $dateTimeFactory;
$this->wikiParser = $wikiParser;
-
$this->logger = $logger;
}
@@ -65,6 +81,7 @@ public function getNewestDivisionDate(): string
/**
* Find a division by its numerical id.
+ *
* @param int $divisionId Numerical id of the division.
*
* @return DivisionEntity|null
@@ -92,58 +109,30 @@ public function findByDivisionId(int $divisionId): ?DivisionEntity
}
/**
- * Find a division by the house, date and division number.
+ * Build/fill out a division and create a division entity.
*
- * @param string $house Name of the house - probably 'commons','lords' or 'scotland'
- * @param string $date Date in YYYY-MM-DD of the division.
- * @param int $divisionNumber Number of the division.
+ * This is a bit of a mess as it needs to reverse engineer whatever PublicWhip v1
+ * has done in the database.
*
- * @return DivisionEntity|null Entity if found.
- */
- public function findByHouseDateAndNumber(string $house, string $date, int $divisionNumber): ?DivisionEntity
- {
- /**
- * Get the basic division data.
- *
- * @var object|null $basicDivision
- */
- $basicDivision = $this->databaseProvider->table('pw_division')
- ->where('division_date', '=', $date)
- ->where('division_number', '=', $divisionNumber)
- ->where('house', '=', $house)
- ->first();
- if (null === $basicDivision) {
- $this->logger->debug(
- __METHOD__ . ': Did not find in {house} date {date} {divisionNumber}',
- [
- 'house' => $house,
- 'date' => $date,
- 'divisionNumber' => $divisionNumber
- ]
- );
- return null;
- }
- return $this->buildDivisionEntityFromDivisionTable($basicDivision);
- }
-
- /**
- * Build/fill out a division and create a division entity.
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ *
+ * @param object $basicDivision A division object as returned from the database.
*
- * @param object $basicDivision
* @return DivisionEntity
*
* @throws BadDatabaseReturnException
*/
private function buildDivisionEntityFromDivisionTable(object $basicDivision): DivisionEntity
{
- if (!property_exists($basicDivision, 'division_id') ||
- !property_exists($basicDivision, 'division_date') ||
- !property_exists($basicDivision, 'division_number') ||
- !property_exists($basicDivision, 'source_url') ||
- !property_exists($basicDivision, 'motion') ||
- !property_exists($basicDivision, 'division_name') ||
- !property_exists($basicDivision, 'debate_url') ||
- !property_exists($basicDivision, 'house')) {
+ if (!property_exists($basicDivision, 'division_id')
+ || !property_exists($basicDivision, 'division_date')
+ || !property_exists($basicDivision, 'division_number')
+ || !property_exists($basicDivision, 'source_url')
+ || !property_exists($basicDivision, 'motion')
+ || !property_exists($basicDivision, 'division_name')
+ || !property_exists($basicDivision, 'debate_url')
+ || !property_exists($basicDivision, 'house')
+ ) {
throw new BadDatabaseReturnException('Not a division');
}
/**
@@ -155,8 +144,8 @@ private function buildDivisionEntityFromDivisionTable(object $basicDivision): Di
(string)$basicDivision->motion
);
$builtData = [
- 'id' => (int)$basicDivision->division_id,
- 'date' => DateTimeImmutable::createFromFormat('!Y-m-d', $basicDivision->division_date),
+ 'divisionId' => (int)$basicDivision->division_id,
+ 'date' => $this->dateTimeFactory->dateTimeImmutableFromYyyyMmDd($basicDivision->division_date),
'number' => (int)$basicDivision->division_number,
'sourceUrl' => (string)$basicDivision->source_url,
'motionText' => $this->wikiParser->cleanHtml($motionText),
@@ -179,10 +168,10 @@ private function buildDivisionEntityFromDivisionTable(object $basicDivision): Di
->where('division_id', '=', $basicDivision->division_id)
->first();
if ($voteInformation) {
- if (!property_exists($voteInformation, 'rebellions') ||
- !property_exists($voteInformation, 'turnout') ||
- !property_exists($voteInformation, 'possible_turnout') ||
- !property_exists($voteInformation, 'aye_majority')
+ if (!property_exists($voteInformation, 'rebellions')
+ || !property_exists($voteInformation, 'turnout')
+ || !property_exists($voteInformation, 'possible_turnout')
+ || !property_exists($voteInformation, 'aye_majority')
) {
throw new BadDatabaseReturnException('Not a divinfo');
}
@@ -225,6 +214,41 @@ private function buildDivisionEntityFromDivisionTable(object $basicDivision): Di
// General tidy up.
$builtData['motionTitle'] = trim(strip_tags($builtData['motionTitle']));
$builtData['motionTitle'] = str_replace(' — ', ' - ', $builtData['motionTitle']);
- return DivisionEntity::buildFromArray($builtData);
+ return $this->entityFactory->division($builtData);
+ }
+
+ /**
+ * Find a division by the house, date and division number.
+ *
+ * @param string $house Name of the house - probably 'commons','lords' or 'scotland'
+ * @param string $date Date in YYYY-MM-DD of the division.
+ * @param int $divisionNumber Number of the division.
+ *
+ * @return DivisionEntity|null Entity if found.
+ */
+ public function findByHouseDateAndNumber(string $house, string $date, int $divisionNumber): ?DivisionEntity
+ {
+ /**
+ * Get the basic division data.
+ *
+ * @var object|null $basicDivision
+ */
+ $basicDivision = $this->databaseProvider->table('pw_division')
+ ->where('division_date', '=', $date)
+ ->where('division_number', '=', $divisionNumber)
+ ->where('house', '=', $house)
+ ->first();
+ if (null === $basicDivision) {
+ $this->logger->debug(
+ __METHOD__ . ': Did not find in {house} date {date} {divisionNumber}',
+ [
+ 'house' => $house,
+ 'date' => $date,
+ 'divisionNumber' => $divisionNumber
+ ]
+ );
+ return null;
+ }
+ return $this->buildDivisionEntityFromDivisionTable($basicDivision);
}
}
diff --git a/src/Services/DivisionServiceInterface.php b/src/Services/DivisionServiceInterface.php
index 4759ec2..3203688 100644
--- a/src/Services/DivisionServiceInterface.php
+++ b/src/Services/DivisionServiceInterface.php
@@ -5,6 +5,8 @@
use Psr\Log\LoggerInterface;
use PublicWhip\Entities\DivisionEntity;
+use PublicWhip\Factories\DateTimeFactoryInterface;
+use PublicWhip\Factories\EntityFactoryInterface;
use PublicWhip\Providers\DatabaseProviderInterface;
use PublicWhip\Providers\WikiParserProviderInterface;
use ReflectionException;
@@ -14,18 +16,23 @@
*
* Reads/writes divisions.
*
- * @package PublicWhip\Services
*/
interface DivisionServiceInterface
{
+
/**
* DivisionService constructor.
- * @param DatabaseProviderInterface $databaseProvider
- * @param WikiParserProviderInterface $wikiParser
- * @param LoggerInterface $logger
+ *
+ * @param DatabaseProviderInterface $databaseProvider Database connection layer.
+ * @param EntityFactoryInterface $entityFactory Entity factory.
+ * @param DateTimeFactoryInterface $dateTimeFactory Date time factory.
+ * @param WikiParserProviderInterface $wikiParser Wiki parser.
+ * @param LoggerInterface $logger Logger.
*/
public function __construct(
DatabaseProviderInterface $databaseProvider,
+ EntityFactoryInterface $entityFactory,
+ DateTimeFactoryInterface $dateTimeFactory,
WikiParserProviderInterface $wikiParser,
LoggerInterface $logger
);
@@ -39,11 +46,12 @@ public function getNewestDivisionDate(): string;
/**
* Find a division by its numerical id.
+ *
* @param int $divisionId Numerical id of the division.
*
* @return DivisionEntity|null
*/
- public function findByDivisionId(int $divisionId) : ?DivisionEntity;
+ public function findByDivisionId(int $divisionId): ?DivisionEntity;
/**
* Find a division by the house, date and division number.
diff --git a/src/Services/README.md b/src/Services/README.md
index 31ee2e0..6782139 100644
--- a/src/Services/README.md
+++ b/src/Services/README.md
@@ -2,4 +2,4 @@ These are the services you should use to interact with the entities. These handl
and similar services.
These should be the only parts of the code that interact with the database in any manner and should just
-accept/return Entities or scalars.
\ No newline at end of file
+accept/return Factories or scalars.
\ No newline at end of file
diff --git a/src/Web.php b/src/Web.php
index fc3ed69..63d36b6 100644
--- a/src/Web.php
+++ b/src/Web.php
@@ -3,20 +3,14 @@
namespace PublicWhip;
-use DI\Bridge\Slim\App;
use DI\ContainerBuilder;
use Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
-use PublicWhip\Providers\DebuggerProviderInterface;
-use PublicWhip\Web\Controllers\DebugBarController;
-use PublicWhip\Web\Controllers\DivisionController;
-use PublicWhip\Web\Controllers\DocsController;
-use PublicWhip\Web\Controllers\IndexController;
-use PublicWhip\Web\Controllers\PingController;
-use RuntimeException;
+use PublicWhip\Exceptions\MissingConfigurationException;
+use PublicWhip\Web\Routing;
+use Slim\App;
use Slim\Exception\MethodNotAllowedException;
use Slim\Exception\NotFoundException;
use Slim\Http\Response;
@@ -25,9 +19,9 @@
* Class Web.
*
* Handles web access to PublicWhip.
- * @package PublicWhip
+ *
*/
-class Web extends App
+class Web
{
/**
@@ -36,117 +30,45 @@ class Web extends App
private $environment;
/**
- * Web constructor.
- * @param string $environment
+ * @var App The Slim Application.
*/
- public function __construct(string $environment)
- {
- $this->environment = $environment;
- parent::__construct();
- }
+ private $app;
/**
- * Main runner.
+ * Web constructor.
*
- * @param bool $silent
- * @return ResponseInterface|void
- * @throws MethodNotAllowedException
- * @throws NotFoundException
- */
- public function run($silent = false)
- {
- $this->setupRouting();
- $this->setupTrailingSlash();
- parent::run($silent);
- }
-
- /**
- * Setup routing.
+ * @param string $environment The name of the environment are setting up.
+ * @param App|null $app The optional pre-built app.
+ *
+ * @throws Exception
*/
- public function setupRouting(): void
+ public function __construct(string $environment, ?App $app = null)
{
- // uptime monitors
- $this->group(
- '/ping',
- function (App $app) {
- $app->get('/', [PingController::class, 'indexAction']);
- $app->get('/lastDivisionParsed/', [PingController::class, 'lastDivisionParsedAction']);
- }
- );
-
- // index page
- $this->get('/', [IndexController::class, 'indexAction']);
- // divisions
- $this->group(
- '/divisions',
- function (App $app) {
- $app->get('/', [DivisionController::class, 'indexAction']);
- $app->get('/{divisionId:[0-9]+}/', [DivisionController::class, 'showDivisionById']);
- $app->get(
- '/' .
- '{house:commons|lords|scotland}/' .
- '{date:18|19|20[0-9][0-9]\-[0-1][0-9]\-[0-3][0-9]}/' .
- '{divisionId:[0-9]+}/',
- [DivisionController::class, 'showDivisionByDateAndNumberAction']
- );
- }
- );
- $this->get('/docs/{file:.*}', [DocsController::class, 'render']);
- /**
- * Register the debugbar only if we are in development/debug mode,
- */
- if ($this->getContainer()->get('settings.debug')) {
- $this->get('/debugbar/[{filePath:.*}]', [DebugBarController::class, 'staticFileAction']);
+ $this->environment = $environment;
+ if (null === $app) {
+ $containerBuilder = new ContainerBuilder();
+ $this->configureContainer($containerBuilder);
+ $container = $containerBuilder->build();
+ $app = new App($container);
}
+ $this->app = $app;
}
/**
- * Handle optional trailing slashes on GET requests.
- */
- private function setupTrailingSlash(): void
- {
- $container = $this->getContainer();
- $this->add(
- function (RequestInterface $request, Response $response, callable $next) use ($container) {
- $uri = $request->getUri();
- $path = $uri->getPath();
- if (strlen($path) > 1) {
- if ('/' !== substr($path, -1) && !pathinfo($path, PATHINFO_EXTENSION)) {
- $path .= '/';
- }
- } elseif ('' === $path) {
- $path = '/';
- }
- $new = $uri->withPath($path);
- if ($uri->getPath() !== $path) {
- $logger = $container->get(LoggerInterface::class);
- $logger->debug('Redirecting from ' . $uri . ' to ' . $new);
- if ('GET' === $request->getMethod()) {
- return $response->withRedirect($new, 301);
- }
- }
- return $next($request->withUri($new), $response);
- }
- );
- }
-
- /**
- * Call relevant handler from the Container if needed. If it doesn't exist,
- * then just re-throw.
+ * Main runner.
+ **
*
- * @param Exception $e
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
+ * @return ResponseInterface The response interface.
*
- * @return ResponseInterface
- * @throws Exception if a handler is needed and not found
+ * @throws MethodNotAllowedException
+ * @throws NotFoundException
*/
- protected function handleException(Exception $e, ServerRequestInterface $request, ResponseInterface $response)
+ public function run(): ResponseInterface
{
- if ($this->getContainer()->has(DebuggerProviderInterface::class)) {
- $this->getContainer()->get(DebuggerProviderInterface::class)->addException($e);
- }
- return parent::handleException($e, $request, $response);
+ $routing = new Routing();
+ $routing->getRouting($this->app);
+ $routing->setupTrailingSlash($this->app);
+ return $this->app->run(false);
}
/**
@@ -154,7 +76,11 @@ protected function handleException(Exception $e, ServerRequestInterface $request
*
* @TODO Add caching if appropriate on production.
*
- * @param ContainerBuilder $builder
+ * @param ContainerBuilder $builder The container we we populating.
+ *
+ * @return void
+ *
+ * @throws MissingConfigurationException
*/
protected function configureContainer(ContainerBuilder $builder): void
{
@@ -164,14 +90,18 @@ protected function configureContainer(ContainerBuilder $builder): void
/**
* Now load our specifics over the top of the prebuilt ones.
*/
- $environmentSettingsFile = __DIR__ .
+ $settingsFile = __DIR__ .
DIRECTORY_SEPARATOR . '..' .
DIRECTORY_SEPARATOR . 'config' .
DIRECTORY_SEPARATOR . $this->environment . '.php';
- if (file_exists($environmentSettingsFile) && is_readable($environmentSettingsFile)) {
- $builder->addDefinitions($environmentSettingsFile);
- } else {
- throw new RuntimeException('Could not find environment file: ' . $environmentSettingsFile);
+ if (!file_exists($settingsFile) && is_readable($settingsFile)) {
+ throw new MissingConfigurationException(
+ sprintf(
+ 'Could not find environment file: %s',
+ $settingsFile
+ )
+ );
}
+ $builder->addDefinitions($settingsFile);
}
}
diff --git a/src/Web/Controllers/DebugBarController.php b/src/Web/Controllers/DebugBarController.php
index 812e1a5..5b4c213 100644
--- a/src/Web/Controllers/DebugBarController.php
+++ b/src/Web/Controllers/DebugBarController.php
@@ -16,34 +16,38 @@
*
* Based off https://github.com/php-middleware/phpdebugbar .
*
- * @package PublicWhip\Web\Controllers
*/
class DebugBarController
{
+
/**
- * @var DebuggerProviderInterface $debuggerProvider
+ * @var DebuggerProviderInterface $debuggerProvider The debugger.
*/
private $debuggerProvider;
+
/**
- * @var LoggerInterface $logger
+ * @var LoggerInterface $logger The logger.
*/
private $logger;
+
/**
- * @var CacheProvider $cacheProvider
+ * @var CacheProvider $cacheProvider Http caching system.
*/
private $cacheProvider;
/**
* DebugBarController constructor.
- * @param DebuggerProviderInterface $debuggerProvider
- * @param LoggerInterface $logger
- * @param CacheProvider $cacheProvider
+ *
+ * @param DebuggerProviderInterface $debuggerProvider The debugger itself.
+ * @param LoggerInterface $logger The logging system.
+ * @param CacheProvider $cacheProvider Our Http caching system.
*/
public function __construct(
DebuggerProviderInterface $debuggerProvider,
LoggerInterface $logger,
CacheProvider $cacheProvider
- ) {
+ )
+ {
$this->debuggerProvider = $debuggerProvider;
$this->logger = $logger;
$this->cacheProvider = $cacheProvider;
@@ -56,9 +60,10 @@ public function __construct(
*
* The debug bar should only be enabled on Development environments anyway.
*
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @param string $filePath
+ * @param ServerRequestInterface $request The server request to populate a not found exception.
+ * @param ResponseInterface $response The response to populate.
+ * @param string $filePath The path requested.
+ *
* @return ResponseInterface
* @throws NotFoundException
*/
@@ -66,7 +71,8 @@ public function staticFileAction(
ServerRequestInterface $request,
ResponseInterface $response,
string $filePath
- ): ResponseInterface {
+ ): ResponseInterface
+ {
$fullPathToFile = $this->debuggerProvider->getBasePath() . DIRECTORY_SEPARATOR . $filePath;
$this->logger->debug('Fetching {file}', ['file' => $fullPathToFile]);
@@ -98,7 +104,7 @@ public function staticFileAction(
/**
* Get the content type, with more validation if we get something we don't recognise.
*/
- $contentType = $this->getContentTypeByFileName($realpath);
+ $contentType = self::getContentTypeByFileName($realpath);
if ('' === $contentType) {
$this->logger->warning(
'Invalid attempt to access debugbar file {file}: unrecognised content type',
@@ -120,12 +126,14 @@ public function staticFileAction(
return $this->cacheProvider->withExpires($response, time() + 60 * 60 * 6);
}
-
/**
- * @param string $filename
+ * Get the content type of a file based on its extension.
+ *
+ * @param string $filename Name of the file to get the file type of.
+ *
* @return string
*/
- private function getContentTypeByFileName(string $filename): string
+ private static function getContentTypeByFileName(string $filename): string
{
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$map = [
diff --git a/src/Web/Controllers/DivisionController.php b/src/Web/Controllers/DivisionController.php
index af3bc99..76e343e 100644
--- a/src/Web/Controllers/DivisionController.php
+++ b/src/Web/Controllers/DivisionController.php
@@ -5,22 +5,21 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use PublicWhip\Providers\TemplateProviderInterface;
use PublicWhip\Services\DivisionServiceInterface;
use Slim\Exception\NotFoundException;
-use Slim\Views\Twig;
/**
* Class DivisionController.
*
- * @package PublicWhip\Web\Controllers
*/
class DivisionController
{
/**
- * @var Twig $twig Templating engine.
+ * @var TemplateProviderInterface $templateProvider Templating engine.
*/
- private $twig;
+ private $templateProvider;
/**
* @var ServerRequestInterface $request
@@ -34,33 +33,40 @@ class DivisionController
/**
* DivisionController constructor.
- * @param Twig $twig
- * @param ServerRequestInterface $request
- * @param DivisionServiceInterface $divisionService
+ *
+ * @param TemplateProviderInterface $templateProvider The templating system provider.
+ * @param ServerRequestInterface $request The actual server request information.
+ * @param DivisionServiceInterface $divisionService The division service.
*/
public function __construct(
- Twig $twig,
+ TemplateProviderInterface $templateProvider,
ServerRequestInterface $request,
DivisionServiceInterface $divisionService
- ) {
- $this->twig = $twig;
+ )
+ {
+ $this->templateProvider = $templateProvider;
$this->request = $request;
$this->divisionService = $divisionService;
}
/**
- * @param ResponseInterface $response
+ * Just show a basic page for now.
+ *
+ * @param ResponseInterface $response The inbound request.
+ *
* @return ResponseInterface
*/
public function indexAction(ResponseInterface $response): ResponseInterface
{
- return $this->twig->render($response, 'DivisionController/indexAction.twig', []);
+ return $this->templateProvider->render($response, 'DivisionController/indexAction.twig', []);
}
-
/**
- * @param string $divisionId
- * @param ResponseInterface $response
+ * Show a division by it's id.
+ *
+ * @param string $divisionId Has to support string as this is passed from the frontend.
+ * @param ResponseInterface $response The response to populate.
+ *
* @return ResponseInterface
* @throws NotFoundException
*/
@@ -70,7 +76,7 @@ public function showDivisionById(string $divisionId, ResponseInterface $response
if (!$division) {
throw new NotFoundException($this->request, $response);
}
- return $this->twig->render(
+ return $this->templateProvider->render(
$response,
'DivisionController/Division.twig',
[
@@ -80,10 +86,13 @@ public function showDivisionById(string $divisionId, ResponseInterface $response
}
/**
- * @param string $house
- * @param string $date
- * @param string $divisionId
- * @param ResponseInterface $response
+ * Show a division by it's house, date and number.
+ *
+ * @param string $house Name of the house.
+ * @param string $date Date (in YYYY-MM-DD format)
+ * @param string $divisionNumber The division number.
+ * @param ResponseInterface $response The response to populate.
+ *
* @return ResponseInterface
* @throws NotFoundException
* @throws \ReflectionException
@@ -91,14 +100,15 @@ public function showDivisionById(string $divisionId, ResponseInterface $response
public function showDivisionByDateAndNumberAction(
string $house,
string $date,
- string $divisionId,
+ string $divisionNumber,
ResponseInterface $response
- ): ResponseInterface {
- $division = $this->divisionService->findByHouseDateAndNumber($house, $date, (int)$divisionId);
+ ): ResponseInterface
+ {
+ $division = $this->divisionService->findByHouseDateAndNumber($house, $date, (int)$divisionNumber);
if (!$division) {
throw new NotFoundException($this->request, $response);
}
- return $this->twig->render(
+ return $this->templateProvider->render(
$response,
'DivisionController/Division.twig',
[
diff --git a/src/Web/Controllers/DocsController.php b/src/Web/Controllers/DocsController.php
index 5684f9f..2492635 100644
--- a/src/Web/Controllers/DocsController.php
+++ b/src/Web/Controllers/DocsController.php
@@ -7,44 +7,51 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
+use PublicWhip\Providers\TemplateProviderInterface;
use Slim\Exception\NotFoundException;
-use Slim\Views\Twig;
/**
* Class DocsController
- * @package PublicWhip\Web\Controllers
+ *
*/
class DocsController
{
/**
- * @var Parsedown $parseDown
+ * @var Parsedown $parseDown Our markdown parser.
*/
private $parseDown;
+
/**
- * @var string $fileRoot
+ * @var string $fileRoot The root of our files.
*/
private $fileRoot;
+
/**
- * @var LoggerInterface $logger
+ * @var LoggerInterface $logger The logger.
*/
private $logger;
/**
- * @var Twig %twig
+ * @var TemplateProviderInterface $templateProvider The templating engine.
*/
- private $twig;
+ private $templateProvider;
/**
* DocsController constructor.
- * @param Parsedown $parseDown
- * @param Twig $twig
- * @param LoggerInterface $logger
+ *
+ * @param Parsedown $parseDown The Markdown parser.
+ * @param TemplateProviderInterface $templateProvider The templating engine provider.
+ * @param LoggerInterface $logger The logger.
*/
- public function __construct(Parsedown $parseDown, Twig $twig, LoggerInterface $logger)
+ public function __construct(
+ Parsedown $parseDown,
+ TemplateProviderInterface $templateProvider,
+ LoggerInterface $logger
+ )
{
$this->parseDown = $parseDown;
- $this->twig = $twig;
+ $this->templateProvider = $templateProvider;
$this->logger = $logger;
$this->fileRoot = __DIR__ . DIRECTORY_SEPARATOR . '..' .
DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
@@ -53,13 +60,18 @@ public function __construct(Parsedown $parseDown, Twig $twig, LoggerInterface $l
/**
* Render a page.
*
- * @param ServerRequestInterface $request
- * @param ResponseInterface $response
- * @param string $file
+ * @param ServerRequestInterface $request The inbound request.
+ * @param ResponseInterface $response The response to populate.
+ * @param string $file Which file was requested.
+ *
* @return ResponseInterface
* @throws NotFoundException
*/
- public function render(ServerRequestInterface $request, ResponseInterface $response, string $file)
+ public function render(
+ ServerRequestInterface $request,
+ ResponseInterface $response,
+ string $file
+ ): ResponseInterface
{
if ('' === $file) {
$file = 'README.md';
@@ -67,22 +79,23 @@ public function render(ServerRequestInterface $request, ResponseInterface $respo
$allowedFiles = [
'README.md',
'docs/Milestones.md',
+ 'docs/ChangeLog.md',
'LICENSE.txt',
'docs/CODE_OF_CONDUCT.md',
'docs/CONTRIBUTING.md',
'docs/Contact.md',
'docs/QuickStart.md'
];
- if (!in_array($file, $allowedFiles)) {
+ if (!\in_array($file, $allowedFiles, true)) {
throw new NotFoundException($request, $response);
}
$fullPath = $this->fileRoot . $file;
$this->logger->info('Reading {fullPath}', ['fullPath' => $fullPath]);
$text = $this->parseDown->text(file_get_contents($fullPath));
$this->logger->info('Read {fullPath} - parsed', ['fullPath' => $fullPath, 'text' => $text]);
- return $this->twig->render($response, 'DocsController/main.twig', [
+ return $this->templateProvider->render($response, 'DocsController/main.twig', [
'file' => $file,
- 'content' => $text
+ 'markdownText' => $text
]);
}
}
diff --git a/src/Web/Controllers/IndexController.php b/src/Web/Controllers/IndexController.php
index c0bd456..e989d42 100644
--- a/src/Web/Controllers/IndexController.php
+++ b/src/Web/Controllers/IndexController.php
@@ -4,22 +4,26 @@
namespace PublicWhip\Web\Controllers;
use Psr\Http\Message\ResponseInterface;
-use Slim\Views\Twig;
+use PublicWhip\Providers\TemplateProviderInterface;
/**
* Class IndexController
- * @package PublicWhip\Web\Controllers
+ *
*/
class IndexController
{
/**
- * @param Twig $twig
- * @param ResponseInterface $response
+ * @param TemplateProviderInterface $templateProvider The templating engine.
+ * @param ResponseInterface $response The response to send back.
+ *
* @return ResponseInterface
*/
- public function indexAction(Twig $twig, ResponseInterface $response)
+ public function indexAction(
+ TemplateProviderInterface $templateProvider,
+ ResponseInterface $response
+ ): ResponseInterface
{
- return $twig->render($response, 'IndexController/indexAction.twig', []);
+ return $templateProvider->render($response, 'IndexController/indexAction.twig', []);
}
}
diff --git a/src/Web/Controllers/PingController.php b/src/Web/Controllers/PingController.php
index 299b718..1522e05 100644
--- a/src/Web/Controllers/PingController.php
+++ b/src/Web/Controllers/PingController.php
@@ -4,21 +4,22 @@
namespace PublicWhip\Web\Controllers;
use Psr\Http\Message\ResponseInterface;
-use PublicWhip\Model\Division;
use PublicWhip\Services\DivisionServiceInterface;
/**
* Class PingController.
*
* Uptime health checks.
- * @package PublicWhip\Web\Controllers
+ *
*/
class PingController
{
/**
* Simple uptime check.
- * @param ResponseInterface $response
+ *
+ * @param ResponseInterface $response The response to populate.
+ *
* @return ResponseInterface
*/
public function indexAction(ResponseInterface $response): ResponseInterface
@@ -28,18 +29,19 @@ public function indexAction(ResponseInterface $response): ResponseInterface
return $response;
}
-
/**
* Returns the date of the last division processed.
*
- * @param DivisionServiceInterface $divisionService
- * @param ResponseInterface $response
+ * @param DivisionServiceInterface $divisionService The devision service.
+ * @param ResponseInterface $response The response to populate.
+ *
* @return ResponseInterface
*/
public function lastDivisionParsedAction(
DivisionServiceInterface $divisionService,
ResponseInterface $response
- ): ResponseInterface {
+ ): ResponseInterface
+ {
$body = $response->getBody();
$body->write($divisionService->getNewestDivisionDate());
return $response;
diff --git a/src/Web/ErrorHandlers/ErrorHandler.php b/src/Web/ErrorHandlers/ErrorHandler.php
index daef527..c0748eb 100644
--- a/src/Web/ErrorHandlers/ErrorHandler.php
+++ b/src/Web/ErrorHandlers/ErrorHandler.php
@@ -3,25 +3,33 @@
namespace PublicWhip\Web\ErrorHandlers;
-use Exception;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
use PublicWhip\Providers\DebuggerProviderInterface;
-use Slim\Handlers\Error;
+use Slim\Handlers\AbstractError;
+use Slim\Http\Body;
+use Throwable;
+use UnexpectedValueException;
/**
- * Class ErrorHandler/
- * @package PublicWhip\Web\ErrorHandlers
+ * Class ErrorHandler.
+ *
+ * Mainly based straight of Slim's Error class for now.
+ *
*/
-class ErrorHandler extends Error
+final class ErrorHandler extends AbstractError
{
+
/**
- * @var DebuggerProviderInterface $debuggerProvider
+ * @var DebuggerProviderInterface $debuggerProvider The debugger.
*/
private $debuggerProvider;
/**
* Constructor.
- * @param DebuggerProviderInterface $debuggerProvider
- * @param bool $displayErrorDetails
+ *
+ * @param DebuggerProviderInterface $debuggerProvider The debugger.
+ * @param bool $displayErrorDetails Should we show error details. Should be false on production.
*/
public function __construct(DebuggerProviderInterface $debuggerProvider, bool $displayErrorDetails)
{
@@ -29,29 +37,71 @@ public function __construct(DebuggerProviderInterface $debuggerProvider, bool $d
parent::__construct($displayErrorDetails);
}
+ /**
+ * Invoke error handler
+ *
+ * @param ServerRequestInterface $request The most recent Request object
+ * @param ResponseInterface $response The most recent Response object
+ * @param Throwable $throwable The caught Throwable object
+ *
+ * @return ResponseInterface
+ * @throws UnexpectedValueException
+ */
+ public function __invoke(
+ ServerRequestInterface $request,
+ ResponseInterface $response,
+ Throwable $throwable
+ ): ResponseInterface
+ {
+ $contentType = $this->determineContentType($request);
+ switch ($contentType) {
+ case 'application/json':
+ $output = $this->renderJsonErrorMessage($throwable);
+ break;
+
+ case 'text/html':
+ $output = $this->renderHtmlErrorMessage($throwable);
+ break;
+
+ default:
+ throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType);
+ }
+
+ $this->writeToErrorLog($throwable);
+
+ /** @var resource $fopen */
+ $fopen = fopen('php://temp', 'rb+');
+ $body = new Body($fopen);
+ $body->write($output);
+
+ return $response->withStatus(500)
+ ->withHeader('Content-type', $contentType)
+ ->withBody($body);
+ }
+
/**
* Render HTML error page
*
- * @param Exception $exception
+ * @param Throwable $throwable The thrown item.
*
* @return string
*/
- protected function renderHtmlErrorMessage(Exception $exception): string
+ private function renderHtmlErrorMessage(Throwable $throwable): string
{
$title = 'PublicWhip Application Exception';
+ $html = 'A website error has occurred. Sorry for the temporary inconvenience.
';
+
if ($this->displayErrorDetails) {
$html = 'The application could not run because of the following exception:
';
$html .= 'Details
';
- $html .= $this->renderHtmlException($exception);
+ $html .= $this->renderHtmlExceptionOrError($throwable);
- $previous = $exception->getPrevious();
- while ($previous instanceof Exception) {
+ $previous = $throwable->getPrevious();
+ while ($previous instanceof Throwable) {
$html .= 'Previous exception
';
- $html .= $this->renderHtmlException($previous);
+ $html .= $this->renderHtmlExceptionOrError($previous);
$previous = $previous->getPrevious();
}
- } else {
- $html = 'A website error has occurred. Sorry for the temporary inconvenience.
';
}
$debugHead = $this->debuggerProvider->renderHead();
$debugBar = $this->debuggerProvider->renderBar();
@@ -70,4 +120,81 @@ protected function renderHtmlErrorMessage(Exception $exception): string
return $output;
}
+
+ /**
+ * Render exception or error as HTML.
+ *
+ * @param Throwable $throwable The thrown item.
+ *
+ * @return string
+ */
+ protected function renderHtmlExceptionOrError(Throwable $throwable): string
+ {
+ $html = sprintf('Type: %s
', \get_class($throwable));
+
+ if ($throwable->getCode()) {
+ $html .= sprintf('Code: %s
', $throwable->getCode());
+ }
+
+ if ($throwable->getMessage()) {
+ $html .= sprintf(
+ 'Message: %s
',
+ htmlentities($throwable->getMessage(), ENT_QUOTES)
+ );
+ }
+
+ if ($throwable->getFile()) {
+ $html .= sprintf('File: %s
', $throwable->getFile());
+ }
+
+ if ($throwable->getLine()) {
+ $html .= sprintf('Line: %s
', $throwable->getLine());
+ }
+
+ if ($throwable->getTraceAsString()) {
+ $html .= 'Trace
';
+ $html .= sprintf(
+ '%s
',
+ htmlentities($throwable->getTraceAsString(), ENT_QUOTES)
+ );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Render JSON error
+ *
+ * @param Throwable $throwable The thrown item.
+ *
+ * @return string
+ */
+ private function renderJsonErrorMessage(Throwable $throwable): string
+ {
+ $error = [
+ 'message' => 'PublicWhip Application Error',
+ ];
+
+ if ($this->displayErrorDetails) {
+ $error['exception'] = [];
+
+ do {
+ $error['exception'][] = [
+ 'type' => \get_class($throwable),
+ 'code' => $throwable->getCode(),
+ 'message' => $throwable->getMessage(),
+ 'file' => $throwable->getFile(),
+ 'line' => $throwable->getLine(),
+ 'trace' => explode("\n", $throwable->getTraceAsString()),
+ ];
+ $throwable = $throwable->getPrevious();
+ } while ($throwable);
+ }
+
+ $encoded = json_encode($error, JSON_PRETTY_PRINT);
+ if (!\is_string($encoded)) {
+ $encoded = '[]';
+ }
+ return $encoded;
+ }
}
diff --git a/src/Web/ErrorHandlers/NotFoundHandler.php b/src/Web/ErrorHandlers/NotFoundHandler.php
index 5289670..ba9e460 100644
--- a/src/Web/ErrorHandlers/NotFoundHandler.php
+++ b/src/Web/ErrorHandlers/NotFoundHandler.php
@@ -3,37 +3,100 @@
namespace PublicWhip\Web\ErrorHandlers;
+use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use PublicWhip\Providers\DebuggerProviderInterface;
-use Slim\Handlers\NotFound;
+use Slim\Handlers\AbstractHandler;
+use Slim\Http\Body;
+use UnexpectedValueException;
/**
* Class NotFoundHandler
- * @package PublicWhip\Web\ErrorHandlers
+ *
*/
-class NotFoundHandler extends NotFound
+final class NotFoundHandler extends AbstractHandler
{
/**
- * @var DebuggerProviderInterface
+ * @var DebuggerProviderInterface The debugger.
*/
private $debuggerProvider;
/**
* Constructor.
- * @param DebuggerProviderInterface $debuggerProvider
+ *
+ * @param DebuggerProviderInterface $debuggerProvider The debugger.
*/
public function __construct(DebuggerProviderInterface $debuggerProvider)
{
$this->debuggerProvider = $debuggerProvider;
}
+ /**
+ * Invoke not found handler
+ *
+ * @param ServerRequestInterface $request The most recent Request object
+ * @param ResponseInterface $response The most recent Response object
+ *
+ * @return ResponseInterface
+ * @throws UnexpectedValueException
+ */
+ public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
+ {
+ // set some defaults
+ $contentType = 'text/plain';
+ $output = $this->renderPlainNotFoundOutput();
+
+ if ('OPTIONS' !== $request->getMethod()) {
+ $contentType = $this->determineContentType($request);
+ switch ($contentType) {
+ case 'application/json':
+ $output = $this->renderJsonNotFoundOutput();
+ break;
+
+ case 'text/html':
+ $output = $this->renderHtmlNotFoundOutput($request);
+ break;
+
+ default:
+ throw new UnexpectedValueException('Cannot render unknown content type ' . $contentType);
+ }
+ }
+
+ /** @var resource $fopen */
+ $fopen = fopen('php://temp', 'rb+');
+ $body = new Body($fopen);
+ $body->write($output);
+
+ return $response->withStatus(404)
+ ->withHeader('Content-type', $contentType)
+ ->withBody($body);
+ }
+
+
+ /**
+ * Render plain not found message
+ *
+ * @return string
+ */
+ protected function renderPlainNotFoundOutput(): string
+ {
+ return 'Not found';
+ }
+
+ /**
+ * Return a response for application/json content not found
+ *
+ * @return string
+ */
+ protected function renderJsonNotFoundOutput(): string
+ {
+ return '{"message":"Not found"}';
+ }
/**
* Return a response for text/html content not found
*
- * phpcs:disable -- Slim currently has the wrong typehint on this method. Disabled CodeSniffer for now
- * @codingStandardsIgnoreStart
* @param ServerRequestInterface $request The most recent Request object
*
* @return string
@@ -44,7 +107,7 @@ protected function renderHtmlNotFoundOutput(ServerRequestInterface $request): st
$text = <<
- Page Not Found
+ Public Whip Page Not Found