Hello everyone! So this article will be on how you can create your own Docker registry hub and push your own Docker images to it. So before we start, here are the prerequisites. They're obvious, but let's make sure.
The Application Documentation (MVC)
Posted on September 17, 2021753 views15 min read
Ok, so this is pretty simple. It's sort of like CodeIgniter, but I took it a step further (or backwards depending on how you view it).
# Controller
So to create a controller file, it's actually pretty simple. In my 5th blog post, I condensed it. But you can simply think of it kind of like CodeIgniter in a way. We'll use the word test for all of our test demos.
In order to properly access or even use our test controller, we must first create the sub-folder within the main controller folder in the root of The Application. So the folder structure should be something like /{WHERE_EVER_THE_APPLICATION_IS_STORED}/controller/test. Within that folder, we can create our test controller and it also has to be a PHP file. It should look similar to this /{WHERE_EVER_THE_APPLICATION_IS_STORED}/controller/test/test.php.
All controller files have to have the class ending in Controller. This will help distinguish between classes. Also, it's also wise to include a constant check on Rheta. Rheta is one of the core files that actually detects direct file access within The Application. This actually prevents people from snooping or accessing controllers and files the wrong way. So for instance, if you don't include a check on Rheta and someone accesses the file like localhost.com/controller/test/test.php, that's actually the wrong way of accessing that controller in the browser. It should actually be localhost.com/test. When someone accesses the controller directly, it may actually spit out fatal errors because of missing classes that would have been included in the instance of said session.
So what Rheta does is check to make sure that the CHECK constant exists within the current instance. If it doesn't, then Rheta will create a "session" (using this word loosely) with database connection and throw the user a 404 error page. This helps prevent unwanted access and also gives the user the illusion that they have reached a file that does not exist. But it actually does exist, just not accessed correctly.
To use Rheta in your controller or where ever you may want it, you just simply do
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
Remember to properly locate the Autoload file or you'll have a problem with the Rheta class not being found. So if this is within a controller file, it should be 2 folder levels below to find the Autoload file. If it's within say for example a model file, it should be 1 level below because the model folder can consist of multiple model files without breaking it.
One last thing to note. You can actually also access other controller files within your current controller. This is the reason why controller files are stored within a specific controller folder. It allows you to create multiple controllers relating to the main controller. This will be explained a little further down.
So, let's start creating our test controller. We'll start by creating the namespace The\Application and including Rheta.
<?php
namespace The\Application;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
Next, we'll create our class and inherit from the core Controller class. You're required to inherit from the core Controller class if you want to use all the various core classes such as Validate, Encrypt, Markdown, Model, Call, and Render. After that, we'll create our default index() method with a void signature because Controller implements from a an interface called Blueprint.
So it'll look something like
<?php
namespace The\Application;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
class TestController extends Controller {
public function __construct() {
parent::__construct();
}
public function index(): void {
}
}
From here, we can do whatever we want inside the default index() method. You can access a model, you can call for another controller, you can render the view.
To access another controller you would use the Call class. This is how the full access looks like $this->call->controller();.
There are actually 3 arguments in this controller() method we're using.
-
The first argument is the filename itself or folder name and filename. So in our use case say we create another controller within our test folder and we called it demo.php. Well, to access that demo.php file, we simply specify the 1st argument as test/demo. You don't need to specify the .php file extension, that is already included in the argument section. Now say we have a different controller we want to access that doesn't relate to test, we just simply do the same thing. Say we have a main controller of foo and a sub controller of bar, but we want to access it from our test controller. Well it'll actually look like this $this->call->controller('foo/bar');. And that's it. You just stick that into your default index() method in the test controller and we're good to go.
-
The second argument is actually the method we want to access. Again, all controllers should have a default index() method when creating your controllers.
-
The third argument is actually optional, but can simply be used to pass around variables (just one).
So let's create that foo/bar file and include it in our test controller.
<?php
namespace The\Application;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
class FooBarController extends Controller {
public function __construct() {
parent::__construct();
}
public function index(): void {
// DO NOT USE THIS, THIS IS JUST A DEMO AND WILL THROW YOU AN ERROR PAGE IF YOU DO USE IT
print 'Our very luscious random line of code for FooBar.';
}
}
In our test controller, we'd just do something like
<?php
namespace The\Application;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
class TestController extends Controller {
public function __construct() {
parent::__construct();
}
public function index(): void {
$this->call->controller('foo/bar', 'index');
}
}
From the browser, we just simply do localhost.com/test and we'll see the output of Our very luscious random line of code for FooBar.
# Model
To create model files, you simply create a file that has to end in .model.php and you can store that in the model folder of The Application. You don't create folders for models. To create our model class, use the The\Application\Model namespace. Our class will actually be our filename with the first letter being capitalized and ending in Model. We'll use the filename test so our test model file should be created as such /{WHERE_EVER_THE_APPLICATION_IS_STORED}/model/test.model.php. You also do not need to include any kind of loading because model files are automatically loaded into The Application.
We'll also create a constructor that's passing in the database connection and the Validate object. So it'll look something like this.
<?php
namespace The\Application\Model;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../core/Autoload.php';
new Rheta();
die;
}
use \The\Application\Validate;
use \Exception;
class TestModel {
public function __construct(object $db, Validate $validate) {
try {
$this->db = $db;
$this->validate = $validate;
} catch (Exception $e) {
die('Database connection could not be established.');
}
}
}
After that, you can create whatever method you'd like. For example, we can grab a user's first name and last name from the database like so.
<?php
namespace The\Application\Model;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../core/Autoload.php';
new Rheta();
die;
}
use \The\Application\Validate;
use \Exception;
class TestModel {
public function __construct(object $db, Validate $validate) {
try {
$this->db = $db;
$this->validate = $validate;
} catch (Exception $e) {
die('Database connection could not be established.');
}
}
public function getFirstLast(int $id): array {
$sql = 'SELECT first_name, last_name FROM users WHERE id = :id';
$prepare = $this->db->prepare($sql);
$parameters = [
':id' => $id,
];
$prepare->execute($parameters);
if($prepare->rowCount()) {
$row = $prepare->fetch();
return [
'first_name' => $row['first_name'],
'last_name' => $row['last_name'],
];
} else {
return [];
}
}
}
Then to access this method and model in your controller, you can simply just do $this->model->testModel->getFirstLast(int $var);. The accessed class of testModel() is actually your original class from the test.model.php file. The first letter is just lower cased because of camel casing naming conventions.
So let's try using that in our test controller.
<?php
namespace The\Application;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
class TestController extends Controller {
public function __construct() {
parent::__construct();
}
public function index(): void {
$data = $this->model->testModel->getFirstLast(1);
print_r($data);
}
}
And the output should give us an array output with first_name and last_name being key indexes and their respective values from the ID of 1. If the data that's sent back is an empty array, that's because the query returned back a boolean of false. So that means either the user doesn't exist with that ID number or we wrote our query wrong.
# Views
So to create views is actually very simple. Just like controller files, views are also stored in their respective folder. This is because you can have multiple views that related to just the 1 main view. For instance, you can have a header and a footer as separate view files, but have the body as the main view file. You just have to specify that in your controller file when rendering your view.
So to store your view file, we can do something like /{WHERE_EVER_THE_APPLICATION_IS_STORED}/view/test/header.php and /{WHERE_EVER_THE_APPLICATION_IS_STORED}/view/test/footer.php and /{WHERE_EVER_THE_APPLICATION_IS_STORED}/view/test/test.php as our body.
To access a view file from your controller, you simply just do this $this->render->view(). The view() method only has 2 arguments compared to the Call class method.
-
The first argument is your filename. So for instance test/header or test/footer or just test which would be our body or main view file.
-
The second argument has to be an associative array and can be anything we pass into it. It can actually be the data set we get from the database. Whatever you pass into the array also does not have to correlate with the variable you are referencing. What that means is the index you want to create does not actually have to have the same name as the variable you are referencing. You can still use the same name for both if you'd like. Every index key you write in this associative array are then turned into variables you can use within your view files.
So an example of usage would be something like
<?php
namespace The\Application;
if(!defined('CHECK')) {
define('CHECK', true);
require_once '../../core/Autoload.php';
new \The\Application\Rheta();
die;
}
class TestController extends Controller {
public function __construct() {
parent::__construct();
}
public function index(): void {
$data = $this->model->testModel->getFirstLast(1);
$this->render->view('test', [
'myDataSet' => $data,
]);
}
}
In our /{WHERE_EVER_THE_APPLICATION_IS_STORED}/view/test/test.php file, we can simply then output that variable like
<?php
print_r($myDataSet);
Be very careful of spelling and the name you try to use as variable though. Whatever the index key you use to pass in from your controller, you have to use that name in your views. That's the only thing you should really worry about. So you can't do print_r($data) if your index key you passed is myDataset. Your variable inside of your views has to be $myDataSet.
# Others
There are other classes you can use in your views if you want. So to escape an output you get from the database, you can simply do something like $this->validate->escape($myDataSet['first_name']);.