![]() |
NanoFramework 1.4.0
A tiny PHP Framework
|
Set up a simple blog.
If it is not the case, install composer by following the instructions on https://getcomposer.org/download/.
In order to install NanoFramework, you must first create an empty directory in your server source directory (usually called www
or htdocs
).
Once created, use the command line to navigate to that directory and type the following command:
The first command will tell composer to retrieve the code for the Framework along with its dependency and store them in the vendor
directory.
The second command will generate all the default files that you can later customize. You must accept the rewriting of composer.json.
The third command will reload the new configuration and allow the Framework to find your code.
Once it's done, running your website should show you the home page.
The heart of a blog is the article. Let's make the first Controller to be an article controller.
Create a file called app/Controllers/Article.php
in which you put the following code:
The first line defines the namespace. It must correspond to the location in the project. App\Controllers
means the file is located in app/Controllers
.
The next line creates a new class called Article
(the name corresponds to the file) and it extends BaseController
. A class only knows other classes in the same namespace, but as there is an app/Controllers/BaseController.php
file, it knows what to look for.
Then, we have a method called index
. This is the default method. Therefore, by visiting /article
on the server (for example on http://blog.local/article
), the code in that method will be executed. Of course, /article/index
will work too!
By modifying the method this way:
An error occurs. It says that a file is missing. That's because when a Controller calls $this->render()
, it expect the corresponding View file to be created. For instance, the View file corresponding to Article::index()
is app/Views/article/index.html
. Let's create that file and give it the following content:
The page displays correctly, but you may notice the title of the page: "change SITE_NAME in .env". Let's take care of that!
At the root of your project is a file called .env.example
. It is not usable as is, but it contains all the data specific to the environment.
Your project will run on a handful of environments. For example, now, you are in the "development" environment, as the website you are making is still a work in progress. You can have a testing environment, un staging environment, and so on, to end up with the production environment. Note that NanoFramework must not be used outside development environment!
All of these have most of the code in common, but some specific data depends of the said environment. For example, you don't want to have the same passwords in development and in production. All those differences can be placed in the .env
file.
On top of that, the
.env
file isn't versioned, it is therefore a good practice to place sensitive data in it, such as tokens or passwords.
Let's duplicate .env.example
and call it .env
. In that second file, let's change the following constant:
The SITE_NAME
constant will be displayed in the page title. Our problem is now solved.
With NanoFramework, to create a new page, we simply have to create a new method in a Controller. Let's add a "Create article" page by adding the method Article::create()
.
In Article.php
, add the following code in the class:
Of course, it will result in a warning, because the corresponding View file is missing. Let's create app/Views/article/create.html
with the content:
We can then see the result on the page /article/create
.
OK, it's ugly, but we'll take care of CSS later on. First, let's make that page useful: for now, if you fill the form and submit, nothing happens.
In order to recieve the informations sent by the form, let's add some code in the Controller:
Wow, that's a lot to handle! Let's describe those lines one by one:
$article = $this->getPost()
will retrieve all the data in the request body if sent with the HTTP method POST and store them in the variable $article
as an associative array. If we are in POST (i.e. if the form has been submitted), $article
will content ‘['title’ => '...', 'content' => '...']`, with the respective values.
In the if
part, we test if the request has been submitted in POST. If it's the case, it means the form has been sent and we can save data in the database. We'll come to that part later.
As for the two lines just before the rendering, they are defining data ready to be sent to the View. ‘$this->data['title’] = $article['title'] ?? ''means that we create a new View variable called
titlethat will contain the data in
$article['title']or
''` if it doesn't exist (it is the case when the page is displayed the first time and the form hasn't been submitted yet). Those two variables will be inserted in the view as follows:
This simple trick allows us to have persistence.
When using a Framework, the database is never accessed directly. The tool used to access it is called a Model.
To create a model, let's create the file app/Models/ArticleModel.php
and fill it this way:
As usual, we must define a namespace corresponding to the directory.
As for the only line in the class, it is one that is mandatory: we must provide a table name to be able to interact with the database.
That's it! The Model is the easiest of all classes to set up, because the rest is completely automated. Of course, you can still customize it, we'll see that later.
Now that we have a Model, we must call it from the Controller. Remember, the Controller is the entry point of the MVC, so we can't access the View or the Model directly, only the Controller can.
As we'll need to have the Model ready to be used everywhere in our Controller, let's add it in the constructor. Insert this code at the beginning of your Article class:
This will result in a Fatal Error, because the class App\Controllers\ArticleModel
isn't found. Of course it isn't! The class we created isn't in the namespace App\Controllers
but in App\Models
!
Don't move the class, it is exactly where it is supposed to be. Instead, we'll tell the Controller to look for that class in another namespace. We just did that kind of thing in the Model itself, so let's do it again: let's add a use
command before the class:
The error is gone and we can now access that Model.
In the create
method, in the if
, replace the comment by:
This will send the data received by the form to the Model (that will save it in the database) and then, redirect the page to our homepage.
You should now notice a new db
directory that appeared in your project location. In it, you will find a file called articles.json
. This directory is your database. NanoFramework uses FakeDb as an easy to use database. Don't edit that file directly!
WARNING: FakeDb is not at all and will never be a reliable database! Its goal is to provide easy to use and easy to understand material for teaching purpose. Never use FakeDb (or NanoFramework, for the matter) on a real website in production or be ready to be easily hacked, or lose all your data, even without a malvolent visitor.
Let's address the elePHPant in the room: our homepage is not the best. It would be so much nicer if we could have the Article:index()
as our main page! We could then get rid of two redundant files that were provided by the Framework, but that we don't really need.
To do that, let's add a specific route, a URI that will point to the Controller of our choice. Let's edit the file called routes
at the root of our project. For now, it looks like this:
We want to replace it by:
That means: if we recieve a get
request for /
, we instead treat it as /article
.
The syntax for this file can be found in its own page
.
The next step would be to display the list of articles on the homepage. For that, let's edit the Controller by setting out index
method to this:
This will load all the articles in the variable, then store this variable as a View-side variable (remember, that's what $this->data
contains), before displaying the page.
Lastly, to display the articles in the view, we must use a special syntax to iterate on all data. Place this code in app/Views/article/index.html
:
The {articles}
and {/articles}
tags are the equivalent of a foreach
statement, because ‘$this->data['articles’]` in the Controller contained an array. In this loop, the keys of the inner arrays can be used as variables. Therefore, this code will create code that may look like this:
The page we created earlier isn't displayed on its own, it's included in a template page located at app/Views/template.html
. This template contains a {template_title}
and a {template_content}
tags.
Let's focus on the {template_content}
tag first. This tag will be replaced by the content of each page the user want to view. That means that everything around will always be part of the page, only the middle portion will be different. Therefore, if we add code in the outer part, it is possible to display code on all pages.
Let's do that by adding a header, a menu and a footer. The <body>
tag will contain this code:
Now, everytime a page is displayed, the menu and the footer will appear on the page.
Those two concepts don't have anything in common, but as we are now going to add an edit page for our articles, we ought to do both.
First, let's speak about parameters. There is only one homepage and only one create page, but the edit page will be different in content for each of the existing articles. The URI /article/edit
isn't enough to decide which article to edit, we must add a unique id to the URI to know what we are editing. Therefore, it would be nice if the URI was something like /article/edit/1
for the first article, /article/edit/2
for the second article, and so forth.
This is the usual behavior implemented in NanoFramework. The third segment of the URI will be transformed into the first parameter of the method.
Let's create that method in the Controller:
How are we going to implement that method? After all, editing the article should look a lot like creating the article, with two exceptions: the fields are pre-filled and the save is an update. Based on the DRY principle, we should reuse at least some of the code.
Let's reuse the View: it is exactly the same!
Instead of having the form in app/Views/article/create.html
, let's move it to a new file called app/Views/article/_form.html
. The _
symbol shows that it is not a full page, but a fragment of page, to be inserted in a full page later on.
_form.html
will look lik this:
and create.html
will simply contain
at least for now!
If you try to display the creation page, you'll see that the form disappeared. Of course it did! We removed it from the View! We now need to put it back using our fragment file.
Edit the create
method to make it look like this:
Two lines are added between the $this->data
part and the $this->render()
. This is not a random place as it would not work elsewhere!
The first line creates a fragment. The method fragment()
works almost exactly like render()
, except it doesn't try to include the page in the template, and it returns the content instead of displaying it. But the variables stored in $this->data
will still be inserted in the page.
The second line taks that output and stored it in $this->rawData
. "Raw" data are exactly like the others (they will be inserted in the page), but no escaping will be used. That means that HTML code will be interpreted. If you mistakenly wrote ‘$this->data['form’] = $form`, you will see the HTML code on the page instead of the elements they represent.
For now, nothing appears on the page anyway! That's because we have to add the new variable (which is the whole form) to the View, using the {form}
tag, as it's the name we gave to the rawData
variable.
Let's make the article/create.html
form look like this:
The form is back!
The last thing we have to do is to do the same for the edit page.
While we are working with views, let's duplicate article/create.html
to article/edit.html
and just change the title to "Edit a new article".
Then, we fill the Controller method:
A few differences from the create()
method don't leave us the choice: we can't reuse the code, it's just too different. Let's see those differences:
Instead of creating the $article
variable from the POST input, we fetch the date in the database, by providing the id. We still take the data from POST in the part where we know the form was sent, because we need to save the new data in the database, not the initial data, the method would be useless.
Next difference is the use of $this->model->update()
instead of $this->model->insert()
. We are going to update the data with the given id. Thus, we need to send two parameters: the id and the new data.
Let's now create a link to the edit page from the homepage. In article/index.html
, let's edit the {articles}{/articles}
part like this:
We can now click on the link and display the edit page with the form!
Oh no! On the edit page, the button still says "Create". It should say "Edit"!
We can do that by changing the button content in article/_form.html
and replacing the button by
Then, under the other $this->data
declarations in the controller, we simply add:
In the create()
method:
In the edit()
method:
Views are very flexibles!
This tutorial ends, quite abruptly I reckon, but feel free to notify to ask for further explanations that I will gladly provide and/or add to that document!