diff --git a/14-middleware.md b/14-middleware.md
index e698327..087e26b 100644
--- a/14-middleware.md
+++ b/14-middleware.md
@@ -1,4 +1,4 @@
-[<< previous](12-refactoring.md) | [next >>](14-invoker.md)
+[<< previous](12-refactoring.md) | [next >>](15-adding-content.md)
### Middleware
@@ -295,4 +295,4 @@ Lets try if you can make the kernel work with our created Pipeline implementatio
pipeline a little bit, so that it can accept a class-string of a middleware and resolves that with the help of a
dependency container, if you want you can do that as well.
-[<< previous](12-refactoring.md) | [next >>](14-invoker.md)
+[<< previous](12-refactoring.md) | [next >>](15-adding-content.md)
diff --git a/15-adding-content.md b/15-adding-content.md
new file mode 100644
index 0000000..d1a7348
--- /dev/null
+++ b/15-adding-content.md
@@ -0,0 +1,248 @@
+[<< previous](14-middleware.md) | [next >>](14-invoker.md)
+
+### Adding Content
+
+By now we did not really display anything but some examples to in our application and it is now time to make our app
+display some content. For example we could our app be able to display the Markdown files used in this tutorial as
+nicely rendered HTML Pages that can be viewed in the browser instead of the editor you are using.
+
+So lets start by copying the markdown files to our app directory. I have created a new folder 'data/pages' and placed all
+the markdown files in there.
+
+Next we need a markdown parser, a pretty simple one is [Parsedown](https://parsedown.org/), if you want more features
+you could also use the [Commonmark parser](https://commonmark.thephpleague.com/). I will choose Parsedown here, but you
+can use whatever you like.
+
+After installing Parsedown lets write a Markdownparser interface and an implementation using parsedown.
+
+We only need one function that receives a string of Markdown and returns the HTML represantion (as a string as well).
+
+
+
+```php
+parser->parse($markdown);
+ }
+}
+```
+
+We could now use the ParsedownRender class directly in our actions by typehinting the classname as an argument to the
+constructor or a method, but as we always want to rely on an interface instead of an implementation we need to define
+the the ParsedownRenderer as the correct implementation for the MarkdownRenderer interface in the dependencies file:
+
+```php
+...
+ \Lubian\NoFramework\Template\MarkdownParser::class => fn(\Lubian\NoFramework\Template\ParsedownParser $p) => $p,
+...
+```
+
+You can test that in our "Other.php" action and try out if the Parser works and is able to render Markdown to HTML:
+
+```php
+public function someFunctionName(ResponseInterface $response, MarkdownParser $parser): ResponseInterface
+{
+ $html = $parser->parse('This *works* **too!**');
+ $response->getBody()->write($html);
+ return $response->withStatus(200);
+}
+```
+
+But we want to display complete Pages written in Markdown, it would also be neat to be able to display a list of all
+available pages. For that we need a few things:
+
+Firstly we need two new Templates, one for the list of the Pages, and the second one for displaying a single pages
+content. Create a new folder in `templates/page` with to files:
+
+`templates/page/list.html`
+```html
+
+
+
+
+ Pages
+
+
+
+
+
+
+
+
+```
+
+This template iterates over a provided array of pages, each element consists of the two properties: an id and a title,
+those are simply displayed using an unordered list.
+
+`templates/page/show.html`
+```html
+
+
+
+
+ {{title}}
+
+
+
+
+ {{{content}}}
+
+
+
+```
+
+The second templates displays a single rendered markdown page. As data it expects the title and the content as array.
+I used an extra bracket for the content ```{{{content}}}``` so that the Mustache-Renderer does not escape the provided
+html and thereby destroys the the parsed markdown.
+
+You might have spotted that I added [Pico.css](https://picocss.com/) which is just a very small css framework to make the
+pages a little bit nicer to look at. It mostly provides some typography styles that work great with rendered Markdown,
+but you can leave that out or use any other css framework you like.
+
+After you have taken care of the templating side we can now create an new Action class with two methods to display use
+our markdown files and the templates to create the pages. As we have two templates I propose to use Two methods in our
+Action:
+`src/Action/Page.php`
+```php
+function show(string $name): \Psr\Http\Message\ResponseInterface;
+function list(): \Psr\Http\Message\ResponseInterface;
+```
+
+Lets define two routes. `/page` should display the overview of all pages, and if the add the name of chapter to the
+route, `/page/adding-content` for example, the show action should be called with the name as a variable:
+
+`config/routes.php`
+```php
+$r->addRoute('GET', '/page', [Page::class, 'list']);
+$r->addRoute('GET', '/page/{page}', [Page::class, 'show']);
+```
+
+Here is my Implementation. If have added a little regex replacement in the show method that replaces the links to the
+next and previous chapter so that it works with our routing configuration.
+
+`src/Action/Page.php`
+```php
+getPages(),
+ fn (string $filename) => str_contains($filename, $page)
+ )
+ )[0];
+ $markdown = file_get_contents($page);
+
+ // fix the next and previous buttons to work with our routing
+ $markdown = preg_replace('/\(\d\d-/m', '(', $markdown);
+ $markdown = str_replace('.md)', ')', $markdown);
+
+ $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page);
+ $data = [
+ 'title' => substr($page, 3),
+ 'content' => $this->parser->parse($markdown),
+ ];
+ $html = $this->renderer->render('page/show', $data);
+ $this->response->getBody()->write($html);
+ return $this->response;
+ }
+
+ public function list(): ResponseInterface
+ {
+ $pages = array_map(function (string $page) {
+ $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page);
+ return [
+ 'id' => substr($page, 0, 2),
+ 'title' => substr($page, 3),
+ ];
+ }, $this->getPages());
+ $html = $this->renderer->render('page/list', ['pages' => $pages]);
+ $this->response->getBody()->write($html);
+ return $this->response;
+ }
+
+ /**
+ * @return string[]
+ */
+ private function getPages(): array
+ {
+ $files = glob($this->pagesPath . '*.md');
+ if ($files === false) {
+ throw new InternalServerError('cannot read pages');
+ }
+ return $files;
+ }
+}
+```
+
+You can now navigate your Browser to [localhost:1234/page][http://localhost:1234/page] and try out if everything works.
+
+Of course this code is far from looking good. We heavily rely on the pages being files in the filesystem, and the action
+should never be aware of the filesystem in the first place, also we have a lot of string replacements and other repetetive
+code in the file. And phpstan is gonna scream at us a lot, but if we rewrite the code to satisfy all the checks we would
+add even more lines to that simple class, so lets move on to the next chapter where we move all the logic to seperate
+classes following our holy SOLID principles :)
+
+
+[<< previous](14-middleware.md) | [next >>](14-invoker.md)
diff --git a/app/composer.json b/app/composer.json
index 04edde0..4809539 100644
--- a/app/composer.json
+++ b/app/composer.json
@@ -13,7 +13,8 @@
"middlewares/whoops": "^2.0",
"erusev/parsedown": "^1.7",
"symfony/cache": "^6.0",
- "doctrine/orm": "^2.11"
+ "doctrine/orm": "^2.11",
+ "league/commonmark": "^2.2"
},
"autoload": {
"psr-4": {
diff --git a/app/composer.lock b/app/composer.lock
index 904cfb4..648f2d5 100644
--- a/app/composer.lock
+++ b/app/composer.lock
@@ -4,8 +4,83 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a3762dd11bab0c9e948d3a73b7f252b9",
+ "content-hash": "1e8469bfebe6479a139b946b8aba49de",
"packages": [
+ {
+ "name": "dflydev/dot-access-data",
+ "version": "v3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dflydev/dflydev-dot-access-data.git",
+ "reference": "0992cc19268b259a39e86f296da5f0677841f42c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
+ "reference": "0992cc19268b259a39e86f296da5f0677841f42c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.42",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
+ "scrutinizer/ocular": "1.6.0",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^3.14"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dflydev\\DotAccessData\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Dragonfly Development Inc.",
+ "email": "info@dflydev.com",
+ "homepage": "http://dflydev.com"
+ },
+ {
+ "name": "Beau Simensen",
+ "email": "beau@dflydev.com",
+ "homepage": "http://beausimensen.com"
+ },
+ {
+ "name": "Carlos Frutos",
+ "email": "carlos@kiwing.it",
+ "homepage": "https://github.com/cfrutos"
+ },
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com"
+ }
+ ],
+ "description": "Given a deep data structure, access data by dot notation.",
+ "homepage": "https://github.com/dflydev/dflydev-dot-access-data",
+ "keywords": [
+ "access",
+ "data",
+ "dot",
+ "notation"
+ ],
+ "support": {
+ "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
+ "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
+ },
+ "time": "2021-08-13T13:06:58+00:00"
+ },
{
"name": "doctrine/cache",
"version": "2.1.1",
@@ -1155,6 +1230,192 @@
],
"time": "2022-03-29T20:12:16+00:00"
},
+ {
+ "name": "league/commonmark",
+ "version": "2.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/commonmark.git",
+ "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71",
+ "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "league/config": "^1.1.1",
+ "php": "^7.4 || ^8.0",
+ "psr/event-dispatcher": "^1.0",
+ "symfony/deprecation-contracts": "^2.1 || ^3.0",
+ "symfony/polyfill-php80": "^1.15"
+ },
+ "require-dev": {
+ "cebe/markdown": "^1.0",
+ "commonmark/cmark": "0.30.0",
+ "commonmark/commonmark.js": "0.30.0",
+ "composer/package-versions-deprecated": "^1.8",
+ "erusev/parsedown": "^1.0",
+ "ext-json": "*",
+ "github/gfm": "0.29.0",
+ "michelf/php-markdown": "^1.4",
+ "phpstan/phpstan": "^0.12.88 || ^1.0.0",
+ "phpunit/phpunit": "^9.5.5",
+ "scrutinizer/ocular": "^1.8.1",
+ "symfony/finder": "^5.3",
+ "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
+ "unleashedtech/php-coding-standard": "^3.1",
+ "vimeo/psalm": "^4.7.3"
+ },
+ "suggest": {
+ "symfony/yaml": "v2.3+ required if using the Front Matter extension"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\CommonMark\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
+ "homepage": "https://commonmark.thephpleague.com",
+ "keywords": [
+ "commonmark",
+ "flavored",
+ "gfm",
+ "github",
+ "github-flavored",
+ "markdown",
+ "md",
+ "parser"
+ ],
+ "support": {
+ "docs": "https://commonmark.thephpleague.com/",
+ "forum": "https://github.com/thephpleague/commonmark/discussions",
+ "issues": "https://github.com/thephpleague/commonmark/issues",
+ "rss": "https://github.com/thephpleague/commonmark/releases.atom",
+ "source": "https://github.com/thephpleague/commonmark"
+ },
+ "funding": [
+ {
+ "url": "https://www.colinodell.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.paypal.me/colinpodell/10.00",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/colinodell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/league/commonmark",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-02-26T21:24:45+00:00"
+ },
+ {
+ "name": "league/config",
+ "version": "v1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/config.git",
+ "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
+ "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
+ "shasum": ""
+ },
+ "require": {
+ "dflydev/dot-access-data": "^3.0.1",
+ "nette/schema": "^1.2",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.90",
+ "phpunit/phpunit": "^9.5.5",
+ "scrutinizer/ocular": "^1.8.1",
+ "unleashedtech/php-coding-standard": "^3.1",
+ "vimeo/psalm": "^4.7.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Config\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Define configuration arrays with strict schemas and access values with dot notation",
+ "homepage": "https://config.thephpleague.com",
+ "keywords": [
+ "array",
+ "config",
+ "configuration",
+ "dot",
+ "dot-access",
+ "nested",
+ "schema"
+ ],
+ "support": {
+ "docs": "https://config.thephpleague.com/",
+ "issues": "https://github.com/thephpleague/config/issues",
+ "rss": "https://github.com/thephpleague/config/releases.atom",
+ "source": "https://github.com/thephpleague/config"
+ },
+ "funding": [
+ {
+ "url": "https://www.colinodell.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.paypal.me/colinpodell/10.00",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/colinodell",
+ "type": "github"
+ }
+ ],
+ "time": "2021-08-14T12:15:32+00:00"
+ },
{
"name": "middlewares/trailing-slash",
"version": "v2.0.1",
@@ -1378,6 +1639,153 @@
},
"time": "2022-01-21T06:08:36+00:00"
},
+ {
+ "name": "nette/schema",
+ "version": "v1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/schema.git",
+ "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
+ "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
+ "php": ">=7.1 <8.2"
+ },
+ "require-dev": {
+ "nette/tester": "^2.3 || ^2.4",
+ "phpstan/phpstan-nette": "^0.12",
+ "tracy/tracy": "^2.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "📐 Nette Schema: validating data structures against a given Schema.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "config",
+ "nette"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/schema/issues",
+ "source": "https://github.com/nette/schema/tree/v1.2.2"
+ },
+ "time": "2021-10-15T11:40:02+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v3.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99",
+ "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2 <8.2"
+ },
+ "conflict": {
+ "nette/di": "<3.0.6"
+ },
+ "require-dev": {
+ "nette/tester": "~2.0",
+ "phpstan/phpstan": "^1.0",
+ "tracy/tracy": "^2.3"
+ },
+ "suggest": {
+ "ext-gd": "to use Image",
+ "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+ "ext-json": "to use Nette\\Utils\\Json",
+ "ext-mbstring": "to use Strings::lower() etc...",
+ "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
+ "ext-xml": "to use Strings::length() etc. when mbstring is not available"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "array",
+ "core",
+ "datetime",
+ "images",
+ "json",
+ "nette",
+ "paginator",
+ "password",
+ "slugify",
+ "string",
+ "unicode",
+ "utf-8",
+ "utility",
+ "validation"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/utils/issues",
+ "source": "https://github.com/nette/utils/tree/v3.2.7"
+ },
+ "time": "2022-01-24T11:29:14+00:00"
+ },
{
"name": "nikic/fast-route",
"version": "v1.3.0",
@@ -1763,6 +2171,56 @@
},
"time": "2021-11-05T16:50:12+00:00"
},
+ {
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/event-dispatcher/issues",
+ "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+ },
+ "time": "2019-01-08T18:20:26+00:00"
+ },
{
"name": "psr/http-factory",
"version": "1.0.1",
diff --git a/app/config/dependencies.php b/app/config/dependencies.php
index 4a97f31..e2a3925 100644
--- a/app/config/dependencies.php
+++ b/app/config/dependencies.php
@@ -14,13 +14,14 @@ use Lubian\NoFramework\Http\Pipeline;
use Lubian\NoFramework\Http\RoutedRequestHandler;
use Lubian\NoFramework\Http\RouteMiddleware;
use Lubian\NoFramework\Repository\CachedMarkdownPageRepo;
-use Lubian\NoFramework\Repository\DoctrineMarkdownPageRepo;
use Lubian\NoFramework\Repository\MarkdownPageFilesystem;
use Lubian\NoFramework\Repository\MarkdownPageRepo;
use Lubian\NoFramework\Service\Time\Now;
use Lubian\NoFramework\Service\Time\SystemClockNow;
use Lubian\NoFramework\Settings;
+use Lubian\NoFramework\Template\MarkdownParser;
use Lubian\NoFramework\Template\MustacheRenderer;
+use Lubian\NoFramework\Template\ParsedownParser;
use Lubian\NoFramework\Template\Renderer;
use Mustache_Engine as ME;
use Mustache_Loader_FilesystemLoader as MLF;
@@ -43,6 +44,7 @@ return [
RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf,
CacheInterface::class => fn (FilesystemAdapter $a) => $a,
MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r,
+ MarkdownParser::class => fn (ParsedownParser $p) => $p,
// Factories
ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(),
diff --git a/app/config/middlewares.php b/app/config/middlewares.php
index 459547e..71dd461 100644
--- a/app/config/middlewares.php
+++ b/app/config/middlewares.php
@@ -1,13 +1,11 @@
getBody();
-
- $body->write('This works too!');
-
- return $response
- ->withStatus(200)
- ->withBody($body);
+ $html = $parser->parse('This *works* **too!**');
+ $response->getBody()->write($html);
+ return $response->withStatus(200);
}
}
diff --git a/app/src/Action/Page.php b/app/src/Action/Page.php
index 9fb86c2..6a3aad0 100644
--- a/app/src/Action/Page.php
+++ b/app/src/Action/Page.php
@@ -2,50 +2,79 @@
namespace Lubian\NoFramework\Action;
-use Lubian\NoFramework\Model\MarkdownPage;
-use Lubian\NoFramework\Repository\MarkdownPageFilesystem;
-use Lubian\NoFramework\Repository\MarkdownPageRepo;
+use Lubian\NoFramework\Exception\InternalServerError;
+use Lubian\NoFramework\Template\MarkdownParser;
use Lubian\NoFramework\Template\Renderer;
-use Parsedown;
use Psr\Http\Message\ResponseInterface;
+use function array_filter;
+use function array_map;
+use function array_values;
+use function file_get_contents;
+use function glob;
use function preg_replace;
+use function str_contains;
use function str_replace;
+use function substr;
class Page
{
public function __construct(
private ResponseInterface $response,
- private MarkdownPageRepo $repo,
- private Parsedown $parsedown,
+ private MarkdownParser $parser,
private Renderer $renderer,
- ){}
+ private string $pagesPath = __DIR__ . '/../../data/pages/'
+ ) {
+ }
+
public function show(
string $page,
): ResponseInterface {
- $page = $this->repo->byTitle($page);
- $content = $this->linkFilter($page->content);
- $content = $this->parsedown->parse($content);
- $html = $this->renderer->render('page', ['content' => $content, 'title' => $page->title]);
+ $page = array_values(
+ array_filter(
+ $this->getPages(),
+ fn (string $filename) => str_contains($filename, $page)
+ )
+ )[0];
+ $markdown = file_get_contents($page);
+
+ // fix the next and previous buttons to work with our routing
+ $markdown = preg_replace('/\(\d\d-/m', '(', $markdown);
+ $markdown = str_replace('.md)', ')', $markdown);
+
+ $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page);
+ $data = [
+ 'title' => substr($page, 3),
+ 'content' => $this->parser->parse($markdown),
+ ];
+ $html = $this->renderer->render('page/show', $data);
$this->response->getBody()->write($html);
return $this->response;
}
public function list(): ResponseInterface
{
- $pages = array_map(
- fn (MarkdownPage $p) => ['title' => $p->title, 'id' => $p->id],
- $this->repo->all()
- );
- $html = $this->renderer->render('pagelist', ['pages' => $pages]);
+ $pages = array_map(function (string $page) {
+ $page = str_replace([$this->pagesPath, '.md'], ['', ''], $page);
+ return [
+ 'id' => substr($page, 0, 2),
+ 'title' => substr($page, 3),
+ ];
+ }, $this->getPages());
+ $html = $this->renderer->render('page/list', ['pages' => $pages]);
$this->response->getBody()->write($html);
return $this->response;
-
}
- private function linkFilter(string $content): string
+ /**
+ * @return string[]
+ */
+ private function getPages(): array
{
- $content = preg_replace('/\(\d\d-/m', '(', $content);
- return str_replace('.md)', ')', $content);
+ $files = glob($this->pagesPath . '*.md');
+ if ($files === false) {
+ throw new InternalServerError('cannot read pages');
+ }
+ return $files;
}
}
diff --git a/app/src/Factory/DoctrineEm.php b/app/src/Factory/DoctrineEm.php
index be24e7f..b0be39b 100644
--- a/app/src/Factory/DoctrineEm.php
+++ b/app/src/Factory/DoctrineEm.php
@@ -1,7 +1,4 @@
-settings->doctrine['devMode']);
@@ -29,4 +29,4 @@ final class DoctrineEm
$config,
);
}
-}
\ No newline at end of file
+}
diff --git a/app/src/Factory/SettingsContainerProvider.php b/app/src/Factory/SettingsContainerProvider.php
index baf278b..5b89266 100644
--- a/app/src/Factory/SettingsContainerProvider.php
+++ b/app/src/Factory/SettingsContainerProvider.php
@@ -20,7 +20,7 @@ final class SettingsContainerProvider implements ContainerProvider
$dependencies = require $settings->dependenciesFile;
$dependencies[Settings::class] = $settings;
$builder->addDefinitions($dependencies);
- // $builder->enableCompilation('/tmp');
+ // $builder->enableCompilation('/tmp');
return $builder->build();
}
}
diff --git a/app/src/Middleware/CacheMiddleware.php b/app/src/Middleware/CacheMiddleware.php
index 2ff6f6e..1bd58c5 100644
--- a/app/src/Middleware/CacheMiddleware.php
+++ b/app/src/Middleware/CacheMiddleware.php
@@ -19,13 +19,12 @@ final class CacheMiddleware implements MiddlewareInterface
private CacheInterface $cache,
private Response\Serializer $serializer,
private Settings $settings,
- )
- {
+ ) {
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
- if ($request->getMethod() === 'GET' && !$this->settings->isDev()) {
+ if ($request->getMethod() === 'GET' && ! $this->settings->isDev()) {
$key = (string) $request->getUri();
$key = base64_encode($key);
$callback = fn () => $handler->handle($request);
diff --git a/app/src/Model/MarkdownPage.php b/app/src/Model/MarkdownPage.php
index 73f001d..bae383c 100644
--- a/app/src/Model/MarkdownPage.php
+++ b/app/src/Model/MarkdownPage.php
@@ -3,17 +3,15 @@
namespace Lubian\NoFramework\Model;
use Doctrine\DBAL\Types\Types;
-use Doctrine\ORM\Mapping\Column;
-use Doctrine\ORM\Mapping\Entity;
-use Doctrine\ORM\Mapping\GeneratedValue;
-use Doctrine\ORM\Mapping\Id;
#[Entity]
class MarkdownPage
{
public function __construct(
- #[Id, Column, GeneratedValue]
- public int|null $id = null,
+ #[Id,
+ Column,
+ GeneratedValue]
+ public int |null $id = null,
#[Column]
public string $title,
#[Column(type: Types::TEXT)]
diff --git a/app/src/Repository/DoctrineMarkdownPageRepo.php b/app/src/Repository/DoctrineMarkdownPageRepo.php
index f107c9e..8d1d457 100644
--- a/app/src/Repository/DoctrineMarkdownPageRepo.php
+++ b/app/src/Repository/DoctrineMarkdownPageRepo.php
@@ -1,7 +1,4 @@
-
- */
+ /** @var EntityRepository */
private EntityRepository $repo;
+
public function __construct(
private EntityManagerInterface $entityManager
- ){
+ ) {
$this->repo = $this->entityManager->getRepository(MarkdownPage::class);
}
@@ -27,15 +26,15 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo
*/
public function all(): array
{
- usleep(rand(500, 1500) * 1000);
+ usleep(random_int(500, 1500) * 1000);
return $this->repo->findAll();
}
public function byId(int $id): MarkdownPage
{
- usleep(rand(500, 1500) * 1000);
+ usleep(random_int(500, 1500) * 1000);
$page = $this->repo->findOneBy(['id' => $id]);
- if (!$page instanceof MarkdownPage){
+ if (! $page instanceof MarkdownPage) {
throw new NotFound;
}
return $page;
@@ -43,9 +42,9 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo
public function byTitle(string $title): MarkdownPage
{
- usleep(rand(500, 1500) * 1000);
+ usleep(random_int(500, 1500) * 1000);
$page = $this->repo->findOneBy(['title' => $title]);
- if (!$page instanceof MarkdownPage){
+ if (! $page instanceof MarkdownPage) {
throw new NotFound;
}
return $page;
@@ -57,4 +56,4 @@ final class DoctrineMarkdownPageRepo implements MarkdownPageRepo
$this->entityManager->flush();
return $page;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/Repository/MarkdownPageFilesystem.php b/app/src/Repository/MarkdownPageFilesystem.php
index 1d70998..25dfd97 100644
--- a/app/src/Repository/MarkdownPageFilesystem.php
+++ b/app/src/Repository/MarkdownPageFilesystem.php
@@ -13,6 +13,7 @@ use function count;
use function file_get_contents;
use function glob;
use function is_array;
+use function random_int;
use function str_replace;
use function substr;
use function usleep;
@@ -31,7 +32,7 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo
$fileNames = glob($this->dataPath . '*.md');
assert(is_array($fileNames));
return array_map(function (string $name): MarkdownPage {
- usleep(rand(200, 500) * 1000);
+ usleep(random_int(200, 500) * 1000);
$content = file_get_contents($name);
$name = str_replace($this->dataPath, '', $name);
$name = str_replace('.md', '', $name);
diff --git a/app/src/Template/GithubMarkdownRenderer.php b/app/src/Template/GithubMarkdownRenderer.php
new file mode 100644
index 0000000..121504b
--- /dev/null
+++ b/app/src/Template/GithubMarkdownRenderer.php
@@ -0,0 +1,28 @@
+addExtension($commonMarkCoreExtension);
+ $environment->addExtension($githubFlavoredMarkdownExtension);
+ $this->engine = new MarkdownConverter($environment);
+ }
+
+ public function render(string $markdown): string
+ {
+ return (string) $this->engine->convert($markdown);
+ }
+}
diff --git a/app/src/Template/MarkdownParser.php b/app/src/Template/MarkdownParser.php
new file mode 100644
index 0000000..d404005
--- /dev/null
+++ b/app/src/Template/MarkdownParser.php
@@ -0,0 +1,8 @@
+parser->parse($markdown);
+ }
+}
diff --git a/app/templates/page/list.html b/app/templates/page/list.html
new file mode 100644
index 0000000..bf42348
--- /dev/null
+++ b/app/templates/page/list.html
@@ -0,0 +1,19 @@
+
+
+
+
+ Pages
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/templates/page/show.html b/app/templates/page/show.html
new file mode 100644
index 0000000..ebe707a
--- /dev/null
+++ b/app/templates/page/show.html
@@ -0,0 +1,13 @@
+
+
+
+
+ {{title}}
+
+
+
+
+ {{{content}}}
+
+
+
\ No newline at end of file
diff --git a/implementation/02-composer/composer.lock b/implementation/02-composer/composer.lock
index 31ecc9c..6d84747 100644
--- a/implementation/02-composer/composer.lock
+++ b/implementation/02-composer/composer.lock
@@ -4,191 +4,9 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "83df32885d5f14b3e3e8dd6d88fafd23",
- "packages": [
- {
- "name": "filp/whoops",
- "version": "2.14.5",
- "source": {
- "type": "git",
- "url": "https://github.com/filp/whoops.git",
- "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc",
- "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9 || ^7.0 || ^8.0",
- "psr/log": "^1.0.1 || ^2.0 || ^3.0"
- },
- "require-dev": {
- "mockery/mockery": "^0.9 || ^1.0",
- "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
- "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
- },
- "suggest": {
- "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
- "whoops/soap": "Formats errors as SOAP responses"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.7-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Whoops\\": "src/Whoops/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Filipe Dobreira",
- "homepage": "https://github.com/filp",
- "role": "Developer"
- }
- ],
- "description": "php error handling for cool kids",
- "homepage": "https://filp.github.io/whoops/",
- "keywords": [
- "error",
- "exception",
- "handling",
- "library",
- "throwable",
- "whoops"
- ],
- "support": {
- "issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.14.5"
- },
- "funding": [
- {
- "url": "https://github.com/denis-sokolov",
- "type": "github"
- }
- ],
- "time": "2022-01-07T12:00:00+00:00"
- },
- {
- "name": "psr/log",
- "version": "3.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
- "shasum": ""
- },
- "require": {
- "php": ">=8.0.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Log\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
- "keywords": [
- "log",
- "psr",
- "psr-3"
- ],
- "support": {
- "source": "https://github.com/php-fig/log/tree/3.0.0"
- },
- "time": "2021-07-14T16:46:02+00:00"
- }
- ],
- "packages-dev": [
- {
- "name": "phpstan/phpstan",
- "version": "1.4.10",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "898c479c39caa727bedf4311dd294a8f4e250e72"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/898c479c39caa727bedf4311dd294a8f4e250e72",
- "reference": "898c479c39caa727bedf4311dd294a8f4e250e72",
- "shasum": ""
- },
- "require": {
- "php": "^7.1|^8.0"
- },
- "conflict": {
- "phpstan/phpstan-shim": "*"
- },
- "bin": [
- "phpstan",
- "phpstan.phar"
- ],
- "type": "library",
- "autoload": {
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "PHPStan - PHP Static Analysis Tool",
- "support": {
- "issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/1.4.10"
- },
- "funding": [
- {
- "url": "https://github.com/ondrejmirtes",
- "type": "github"
- },
- {
- "url": "https://github.com/phpstan",
- "type": "github"
- },
- {
- "url": "https://www.patreon.com/phpstan",
- "type": "patreon"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
- "type": "tidelift"
- }
- ],
- "time": "2022-03-14T10:25:45+00:00"
- }
- ],
+ "content-hash": "bad16ec07065a74b5b41da54ff6ca57e",
+ "packages": [],
+ "packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],