diff --git a/10-dynamic-pages.md b/10-dynamic-pages.md new file mode 100644 index 0000000..a416cec --- /dev/null +++ b/10-dynamic-pages.md @@ -0,0 +1,225 @@ +[<< previous](9-templating.md) + +### Dynamic Pages + +So far we only have a static page with not much functionality. Just having a hello world example is not very useful, so let's go beyond that and add some real functionality to our application. + +Our first feature will be dynamic pages generated from [markdown](http://en.wikipedia.org/wiki/Markdown) files. + +Create a `Page` controller with the following content: + +```php +pageFolder = $pageFolder; + } + + public function getContentBySlug($slug) + { + return 'I am a placeholder'; + } +} +``` + +As you can see we are requiring the page folder path as a constructor argument. This makes the class flexible and if we decide to move files or write unit tests for the class, we can easily change the location with the constructor argument. + +You could also put the page related things into it's own package and reuse it in different applications. Because we are not tightly coupling things, things are very flexible. + +Because PHP does not have the ability to type hint for scalar values (things like strings and integers), we have to manually check that `$pageFolder` is a string. If we don't do that, there might be a bug in the future that is hard to find if a wrong type is injected. By throwing an exception, this can be caught and debugged immediately. + +This will do for now. Let's create a template file for our pages with the name `Page.mustache` in the `templates` folder. For now just add `{{ content }}` in there. + +Add the following to your `Dependencies.php` file so that the application know which implementation to inject for our new interface. We also define the the `pageFolder` there. + +```php +$injector->define('Example\Page\FilePageReader', [ + ':pageFolder' => __DIR__ . '/../pages', +]); + +$injector->alias('Example\Page\PageReader', 'Example\Page\FilePageReader'); +$injector->share('Example\Page\FilePageReader'); +``` + + +Now go back to the `Page` controller and change the `show` method to the following: + +```php +public function show($params) +{ + $slug = $params['slug']; + $data['content'] = $this->pageReader->getContentBySlug($slug); + $html = $this->templateEngine->render('Page', $data); + $this->response->setContent($html); +} +``` + +To make this work, we will need to inject a `Response`, `TemplateEngine` and a `PageReader`. I will leave this to you as an exercise. Remember to `use` all the proper namespaces. Use the `Homepage` controller as a reference. + +Did you get everything to work? + +If not, this is how the beginning of your controller should look now: + +```php +response = $response; + $this->templateEngine = $templateEngine; + $this->pageReader = $pageReader; + } +... +``` + +So far so good, now let's make our `FilePageReader` actually do some work. + +Again, let's check first that the proper type was passed into the method: + +```php +public function getContentBySlug($slug) +{ + if (!is_string($slug)) { + throw new InvalidArgumentException('slug must be a string'); + } +} +``` + +We also need to be able to communicate that a page was not found. For this we can create a custom exception that we can catch later. In your `src/Page` folder, create a `InvalidPageException.php` file with this content: + +```php +pageFolder/$slug.md"; + +if(!file_exists($path)) { + throw new InvalidPageException($slug); +} + +return file_get_contents($path); +``` + +Now if you navigate to a page that does not exist, you should see an `InvalidPageException`. If a file exists, you should see the content. + +Of course showing the user an exception for an invalid URL does not make sense. So let's catch the exception and show a 404 error instead. + +Go to your `Page` controller and refactor the `show` method so that it looks like this: + +```php +public function show($params) +{ + $slug = $params['slug']; + + try { + $data['content'] = $this->pageReader->getContentBySlug($slug); + } catch (InvalidPageException $e) { + $this->response->setStatusCode(404); + return $this->response->setContent('404 - Page not found'); + } + + $html = $this->templateEngine->render('Page', $data); + $this->response->setContent($html); +} +``` + +Add this at the top of your file: +```php +use Example\Page\InvalidPageException; +``` + +It is important that you don't forget this step, otherwise it will try to catch the wrong exception (it's looking in the wrong namespace) and thus will never catch it. + +Try a few different URLs to check that everything is working as it should. If something is wrong, go back and debug it until it works. + +And as always, don't forget to commit your changes. +[<< previous](9-templating.md) \ No newline at end of file diff --git a/9-templating.md b/9-templating.md index 50ccb93..564e9c1 100644 --- a/9-templating.md +++ b/9-templating.md @@ -1,4 +1,4 @@ -[<< previous](8-dependency-injector.md) +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) ### Templating @@ -108,8 +108,8 @@ We also have to rewrite the `show` method. Please note that while we are just pa $data = [ 'name' => $this->request->getParameter('name', 'stranger'), ]; - $content = $this->templateEngine->render('Hello {{name}}', $data); - $this->response->setContent($content); + $html = $this->templateEngine->render('Hello {{name}}', $data); + $this->response->setContent($html); } ``` @@ -136,4 +136,4 @@ Now you can go back to your `Homepage` controller and change the render line to Navigate to the hello page in your browser to make sure everything works. And as always, don't forget to commit your changes. -[<< previous](8-dependency-injector.md) \ No newline at end of file +[<< previous](8-dependency-injector.md) | [next >>](10-dynamic-pages.md) \ No newline at end of file