aRvi - High Standard OOP MVC Framework for PHP

aRvi is a small MVC framework for people with high standards with OOP and a passion for clean code. :)

Philosophy

aRvi's goal is to implement the MVC idea in a strict way combined with very good object-oriented programming patterns. The main idea behind aRvi is that a page has one resource address and a single job to do. Transfering Robert Martin's Single Responsibility Principle from OOP, one page may notresult in two different responsibilites or actions in other words. In detail this means the common used technique of "creating a form and processing it after submitting" in a single "script" is not valid for aRvi. This may sound like much overhead but it's the way aRvi is meant to use. The spirit of aRvi is to seperate the programming into tidy OOP pieces.

In aRvi's concept a single page consists of the following ingridients:

  • One mandatory controller class
  • One optional view class
  • Optional model class(es)
  • Optional Page Registry entry

The dispatching process works automatically and the programmer of a new page has nothing to consider. The work starts by creating a controller and typically a view.

Compatibility and Interoperability

aRvi is fully namespaced and therefore will not going to get in each other's way with other frameworks or components. aRvi is not meant as a complete framework and so you will probably need to use other libraries or frameworks for database connections, sending emails, job processing, etc. aRvi is designed to work with all other components together as you should not be limited in designing and programming your controllers and views in any way. You can use your favorite template engine (e.g. smarty) as well as printing out raw PHP strings.

aRvi can be used in existing projects as well as starting from the scratch with it.

Audience

At first everyone interested in creating tidy and strict OOP should be satisfied by using aRvi. aRvi is not hard to learn and use but maybe hard to hold out.

Limitations

aRvi is not ready for the CLI out-of-the-box yet. Using it should be no problem as the programming of aRvi was always made while thinking about CLI usage but there is no CLI launcher ready at the moment.

aRvi has no router yet. Building well-formed URIs requires URL rewriting with webserver's configuration files (e.g. mod_rewrite on Apache) or accessing pages directly with "_page" param on the landing page script.

Startup

To open a specific page you have to specify a given parameter _page via you request method (GET, POST, etc.)

http://www.example.com/index.php?_page=settings

This will load and run the controller class SettingsController.
Note: Omitting the parameter _page will lead the dispatcher to load a default one called "home"

http://www.example.com/index.php?_page=settings&_ns=settings

This will load and run the controller class SettingsController in the directory folder settings.
Note: Setting the namespace can be done in Page Registry much cleaner and more comfortable.

http://www.example.com/index.php?_page=settings&_ns=settings&_view=settings_form

This will load and run the controller class SettingsController in the directory folder settings and the view SettingsFormHtmlView also in the directory folder settings.
Note: Setting the view can be done in Page Registry much cleaner and more comfortable. Omitting the parameter _view will let the dispatcher use an instance of \aRvi\View\View instead.

http://www.example.com/index.php?_page=settings&_ns=settings&_view=other_settings_form&_type=json

This will load and run the controller class SettingsController in the directory folder settings and the view SettingsFormJsonView also in the directory folder settings.
Note: Setting the type can be done in Page Registry much cleaner and more comfortable.

Fallbacks

As you have read above, passing the parameters _page and _type via request method seems to be mandatory. In fact and due to comfort reasons you can always pass them via GET. In case of a POST without _page or _type aRvi will try to extract them from GET.

Controller

The heart of each page is the controller. Within the controller the programmer will start the job when creating a new page. According to the MVC model the controller's job is to process the needed data, to operate with models and collecting the data which the view need to create its output.

Simple controller example

class SettingsController extends \aRvi\Controller {
    
    protected function generate() {

        // Do your stuff here ...
        // Interoperate with your view
        // $this->view-> ....
        // That's it

        return parent::generate();
    }

}

View

The view is an optional component of aRvi but is used high frequently as most pages have an output for the user (exceptions are HTTP redirections for example). Interpreted from the MVC model the view is a "stupid" component processing the data provided by the controller without doing business logic. aRvi's idea is that you can exchange (or use parallel) views of different types (HTML, JSON(P), HTML-Fragments, ...) with the same controller. It works in theory and also in practice depending on your programming.

The view itself has no direct output if you want to follow aRvi's idea of a perfect OOP world. Surely you can use "echos" and "exits" within the view (who want's to stop you) but the intended way is to return your output back to the controller which will also return it back to the dispatcher as an response instance that will be processed and then printed to the client. This sounds hard to adapt but is as absolutely simple to use.

Bad style (but working) example 

class SettingsHtmlView extends \aRvi\View\View {

    protected function render() {        
        $content = "<html>";
        $content .= "<body>";
        $content .= "Hello World!";
        $content .= "</body>";
        $content .= "</html>";

        echo $content;
        exit;
    }

}

This example will work but is not recommended.

Recommended example

class SettingsHtmlView extends \aRvi\View\View {
   
    protected function render() {        
        $content = "<html>";
        $content .= "<body>";
        $content .= "Hello World!";
        $content .= "</body>";
        $content .= "</html>";

        return $content;
    }

}

Not a big difference for you but for the process of the dispatching process which is not interrupted and will generate a full qualified web response with optional additional headers.

View Payload

To easily pass data from controller to view an object called "payload" is available with each view. Payload is an instance of type ViewPayload which implements Iterator and ArrayAccess interfaces.

class SettingsController extends \aRvi\Controller {
    
    protected function generate() {

        $this->view->payload->firstName = "Ronald";

        return parent::generate();
    }

}

class SettingsHtmlView extends \aRvi\View\View {

    protected function render() {       
 
        $content = "Hello " . $this->payload->firstName;

        return $content;
    }

}

Responses

aRvi can return any response type that you like to. The default one is HTML, some others types are built-in for easy and automatic handling.

View type

aRvi can handle different built-in so called view types to output the view's data in the proper way (and again, that's the reason why it's important to return data instead of printing on your own). The passed view type will let the dispatcher load and initialize the corresponding view and the associated web respsonse.

Built-in View types

HTML

The most used and default view type is the HTML one. The output of a HTML view will be printed out with a "Content-type: text/html" header.

JSON

Returned content of a JSON view will be automatically encoded to JSON and send with a "Content-type: application/json" header.

JSONP

Returned content of a JSONP view will be automatically encoded to JSON and wrapped in a JavaScript function name named "jsonpCallback". The name is customizable for sure. It will be send with a "Content-type: application/javascript" header.

Creating a custom view type

Creating a custom view type is not hard. The following example shows how you could easily create a PDF web response. The web response must be located in "App" namespace and autoloading must be able to find it.

Web response
namespace App\WebResponse;

class PdfWebResponse extends \aRvi\WebResponse\WebResponse {

    public function __construct() {
        $this->arrHeaders[] = "Content-type: application/pdf";
    }

}

Page Registry

See the Page Registry as the index of the whole website. Each single page is listed within this index and most of the meta data is stored in it too. The entry of a page stores data like the URI, the title and the meta description if wanted. Even if a secure connection over HTTPS is required or optional may be set in Page Registry. Also if a user has to be logged in to access the page. In many frameworks you have to set or access these information in the controller or view by hand. With defining them in the page registry you can set a page to a "login required" status without changing your programming at all. The Page Registry can be stored in a file, database or memory as long as you create a compatible provider.

Page Registry example (XML style)

<entry>
    <id>settings</id>

    <title><![CDATA[User settings form]]></title>
    <uri>/settings</uri>

    <methods>GET</methods>

    <login>force_login</login>
    <https>force_on</https>        
</entry>

This example shows a page with id "settings" that will have an HTML title-Tag "User settings form". The page shall be accessed by the URI "/settings" and only via GET. User login is required together with a secure connection over HTTPS. There are several other options to set.

Configuring a Page Registry entry

Basically most of the following properties are optional. When creating a Page Registry it's highly recommended to create an entry "_default_" with all properties that shall be inherited to your page entries. All properties that are not explicitly set in your page entry will be inherited from the entry called "_default_". With this method you can easily require your whole website to use HTTPS with setting "https" to "force_on" in the default entry. After that you don't have to set a "https" property in each page entry because it will be inherited from the default one.

id 

The unique name of a page. You can access the page in your programming with this name. Tell the dispatcher to load the page with request paramete "_page". Example: http://example.org/?_page=settings

parent_id

The ID of the parent of this entry. Parent is relevant for logical connection but not for intheriting entry properties. Best examples are automatically created breadcrumbs or sitemaps which is simple with Page Registry.

title

HTML title tag can be generated with this property.

description

The meta description can be stored in this property.

label 

The label is a short human readable (better customer readable) name for an entry. It can be used in breadcrumb or when linking to page. Label can be accessed in programming with UriFetcher which is a best way to create links in your programming. Using this option strictly you can easily change a page's label and all links within your website using UriFetcher will change the name too.

uri

The property "uri" will hold the URI to your page so it can be used by UriFetcher. Like the property "label" you can easily create links to your pages with the page's ID and not hardlinking them in your programming. Changing the property "uri" once in page registry will make all occurances using UriFetcher leading to the new URI.

methods

A list of  allowed HTTP methods to access the page. Commonly you will use "GET" and "POST" as options or a combination like "GET POST" to allow more than one. Accessing a page with a method that is not listed will result in an exception.

login

Set if the page can be access when user is logged in only or when user is not logged in or if both states are possible. Valid options are "force_login", "deny_login" or leaving empty if both is valid. If  login state is required for accessing the page, there will be an automatic redirection to your defined login page (see below).

https

Set if the page can be access with HTTP, HTTPS or both via connections. If the page is opened via a prohibited protocol, there will be an automatic redirection to the same page with the required protocol. This will be made according to a redirection setting (see below).

redirections

Redirections will be used when the desired state of "https" or "login" is not fulfilled.

login
force_login

In case of accessing a page with "login" property "force_login" as a user which is not logged in, he will be redirected to the URI specified in this property. The original URI can be passed as a GET parameter to redirect the user again after logging in to the original requested page.

deny_login

The opposite case of accessing a page that is only allowed for "guest users" will lead into a redirection to the URI set in this field.

https
force_on

Accessing a page that needs HTTPS over a simple HTTP request will end in a redirection to the URI specified in this property. Usually this should be the same page with HTTPS protocol.

force_off

The opposite case of accessing a page that needs simple HTTP over a simple HTTPS request will end in a redirection to the URI specified in this property. Usually this should be the same page with HTTP protocol.

permissions

Defining permissions that are required to open the page. This may be a single permission like "admin" or a list like "moderator admin".

type

Set the view type of the page.

namespace

Set a namespace which will be used for loading controller and view. Important: This is not a PHP namespace. Namespace is the name of the directory used in the file path when loading the controller/view.

controller

Set the controller name to load like "settings". This will load the controller file "SettingsController.php". This property is optional - omitting will make the dispatcher load the controller with the same name as the ID.

view

Set the view name to load like "settings". This will load the view file "SettingsHtmlView.php".

Using multiple Page Registries together

The Page Registry provider of aRvi is capable of loading multiple page registries at once and merging them intelligently to one entry. This is highly useful when dealing with multilingual websites. You can set up a global page registry and defining all the global settings like "method", "https", "login", etc. Then you create multiple Page Registries for each language. When starting aRvi's dispatching process you assign both Page Registries to the dispatcher. This will load the global entry and merge it with the language specific one. All properties that are not set in the language-specific Page Registry will be inherited from the global file (and also inherited from the default entry as well).

Example

Exerpt of global Page Registry file "page_registry.xml"

<entry>
    <id>_default_</id>
    <methods>GET</methods>
    <login></login>                        
    <https>force_off</https>
</entry>

<entry>
    <id>settings</id>
    <uri>/settings</uri>
    <login>force_login</login>
    <https>force_on</https>
</entry>
Exerpt of  language-specific Page Registry file "page_registry_de.xml"
<entry>
    <id>settings</id>

    <title>Einstellungen</title>
</entry> 

Loading these with a Page Registry provider will end in the following (virtual) result:

<entry>
    <id>settings</id>
    <uri>/settings</uri>

    <title>Einstellungen</title>

    <methods>GET</methods>

    <login>force_login</login>
    <https>force_on</https>     
</entry>

Accessing the Page Registry within programming

Loading a the Page Registry is made in two steps. First creating a provider and then assign it to a Page Registry instance.

$provider = new \aRvi\PageRegistry\Provider\PageRegistryXmlProvider("./page_registry.xml");
$registry = new \aRvi\PageRegistry\Provider\MultiProviderPageRegistry();
$registry->addProvider($provider);

Accessing a page entry is easily made with

    $registry->getPageEntry("settings");
When using aRvi it's comfortable to access the current entry because it's already loaded in your controller's dispatcher.
class SettingsController extends \aRvi\Controller {

    protected function generate() {

        $this->dispatcher->pageRegistry->getPageEntry("settings");

    }
} 

Using UriFetcher with Page Registry

UriFetcher is a tool to fetch the URI and label of a page entry. aRvi offers you an initilazied instance of UriFetcher for instant access.

class SettingsController extends \aRvi\Controller {

    protected function generate() {

        echo $this->uriFetcher->fetch("settings"); // will print "/settings"

        echo $this->uriFetcher->fetchURILabelByPageId("settings"); // will print "User settings form"

        return new \aRvi\WebResponse\RedirectionWebResponse($this->uriFetcher->fetch("settings")); // will result in a 301 HTTP redirection to "/settings"

    }
} 

This will work in a view as well

class SettingsHtmlView extends \aRvi\View\View {
   
    protected function render() {

        echo $this->uriFetcher->fetch("settings"); // will print "/settings"
        echo $this->uriFetcher->fetchURILabelByPageId("settings"); // will print "User settings form"

    }

}

User input and filtering

Basics

Foreword: aRvi has not the claim to offer complete and secure methods of filtering malicious user input. Never use unfiltered user input in your application. It's up to you to use PHP's built-in filter features and maybe filtering libraries in addition to secure your application against user attacks.

aRvi is very straight (and strict) with request data. aRvi will provide you filtered user input to use in your programming. You should never use raw superglobals like $_GET or $_POST because the are not touched by any filtering of aRvi. When it comes to the strict point mentioned in the introduction sentence it's meant that aRvi will only provide the user input according to the request method. So if a page is requested via POST you will not have access to any GET parameters.

Example POST request to /settings?option1=yes with POST payload "option2: yes, option3: no"

class SettingsController extends \aRvi\Controller {

    protected function generate() {

        echo $this->request->option1; // will print ""
        echo $this->request->option2; // will print "yes"
        echo $this->request->option3; // will print "no"

    }

}
Using the provided ClientRequest instance $request over the direct access of $_GET or $_POST makes it easy to use or test your programming in a shell environment (e.g. PHPUnit) or changing request methods from GET to POST or vice versa.

InputProcessors

aRvi comes along with some basic InputProcessors. aRvi will apply them automatically to each user input. So when using the ClientRequest instance you might receive data that may differ from the original input. aRvi will not modify the PHP's superglobals so you can take unmodified (but also unfiltered) data direclty from there.

  • HTML - Filter out HTML tags
  • JavaScript - Filter out common JavaScript expressions
  • Trim - Trim white spaces
  • Unicode MB4 - Removes MB4 unicode chars (emojis)

Creating your own InputProcessor

To use own filters it's easy to add own InputProcessors that will be applied to the request instance's data.

Extending ClientRequest for better filter techniques

To combine the advantages of aRvi's ClientRequest instance with PHP's built-in filter techniques you can extend the ClientRequest class and add methods that use PHP's filter_input/filter_var and add much more security to your application. This feature will be included in further aRvi releases most likely.

Client

For accessing client data (more session data than browser information) aRvi is equipped with a Client class that can be accessed from controllers and views easily. Dealing with PHP's $_SESSION for user data is needless and not recommend (once again think about easier handling with unit tests ore shell environments in general). 

Accessing client data in programming

Both controller and view have direct access to the Client instance. 

class SettingsController extends \aRvi\Controller {        

    protected function generate() {

        echo $this->client->public_ip_address; // will print "192.168.0.1"
        var_dump($this->client->isLoggedIn()); // will print "true"

    }

}

Default settings

public_ip_address

The IP address taken from $_SERVER's field "REMOTE_ADDR".

real_ip_address

The IP address taken from $_SERVER's field "HTTP_X_FORWARDED_FOR". HTTP_X_FORWARDED_FOR is commonly used by proxy servers and set to the original client's IP address. Even if the naming says it's the "real" one, keep in mind, that it's only a client's request header line that can be faked easily.

isLoggedIn()

Returning the login state (true/false) of the client. Must be overwritten for your own purposes. Used by dispatcher when checking Page Registry settings.

hasPermission($permissionName)

Returns the permission state (true/false) of the client for a given permission. Must be overwritten for your own purposes. Used by dispatcher when checking Page Registry settings.

Extending the class for your own desires

To fulfill your own requirements of login state, permission checks or simple more user data like IDs, names, etc. you can extend the ClientRequest class and inject it into the dispatcher at startup.