REST in the microframework Inphinit

Asked

Viewed 101 times

3

To experimental functionality REST Inphinit microframework requires a class with 7 functions

  • index
  • create
  • store
  • show
  • edit
  • update
  • destroy

In a REST API basically two are required entry points for the verb GET, one that will return all the data (filtered or not, paged or not) and one that will return a specific resource, ie, show and index

What is the usefulness of the functions create and edit? It is possible to disable them?

  • 2

    It’s quite likely that create and edit follow a logic similar to that of Laravel: create the view layer, for displaying forms.

  • @Wallacemaxters seems strange to me, I have always connected the REST the modern architecture, where the backend only traffic the data, IE, the registration and alteration form screens are not part of the responsibility of the back

  • I agree with you. But the controller is responsible for rendering the view. In a scenario where the front end is in the framework, it makes sense to do so. Now if you just use the framework to serve REST, really, the methods would make no sense. It remains the creator to give an option to leave these optional methods.

  • 1

    @Wallacemaxters they only add/exist if the Controller has the methods create() and edit(), if in the class there is no then it would be similar to use Route::resource('photos', 'Usuarios')->except([ 'create', 'edit' ]); of Laravel, the difference is that you don’t need to execute an except, just don’t create the methods in the class and FW takes care of this.

1 answer

2


I am the developer of this framework, let’s isolate some explanations

Inphinit requires a class with 7 functions

Actually not required, all are optional, basically the class Rest checks which class methods are valid in:

private static $valids = array(
    'index'   => array( 'GET',  '/' ),
    'create'  => array( 'GET',  '/create' ),
    'store'   => array( 'POST', '/' ),
    'show'    => array( 'GET',  '/{:[^/]+:}' ),
    'edit'    => array( 'GET',  '/{:[^/]+:}/edit' ),
    'update'  => array( array('PUT', 'PATCH'), '/{:[^/]+:}' ),
    'destroy' => array( 'DELETE', '/{:[^/]+:}' ),
);

And then comparing it to what you set:

$route = empty(self::$valids[$method]) ? false : self::$valids[$method];

The class Rest is just a facilitator, it creates the routes as you configure the class of your controller

It is possible to disable them?

Yes, they are optional

So much is a facilitator that without it you could create your API manually (if so desired), for example session-driven to simulate data recording and reading:

<?php

use Inphinit\Routing\Route;
use Inphinit\Http\Request;
use Inphinit\Http\Response;

session_start();

// Cria os dados iniciais se não existirem `Inphinit\Experimental\Rest`
if (empty($_SESSION['exemplo'])) {
    $_SESSION['exemplo'] = array(
        array( 'id' => 1, 'name' => 'Guilherme Costamilam' ),
        array( 'id' => 2, 'name' => 'Wallace Maxters' ),
        array( 'id' => 3, 'name' => 'Carlos Bacco' )
    );
}

function getItemById($id)
{
    foreach ($_SESSION['exemplo'] as $key => $value) {
        if ($value['id'] == $id) return $key;
    }

    return false;
}

// Lista todas "entidades"
Route::set('GET', '/api/', function () {
    Response::type('application/json');

    return json_encode($_SESSION['exemplo']);
});

// Adiciona uma nova "entidade"
Route::set('POST', '/api/', function () {
    $raw = stream_get_contents( Request::raw() );
    $json = null;

    if ($raw) {
        $json = json_decode($raw);
    }

    //Pega a ultima ID
    $lastId = max(array_column($_SESSION['exemplo'], 'id'));

    //Nova ID
    $newId = ++$lastId;

    if (empty($json->name)) {
        $status = 400;
        $response = array( 'response' => 'Dados inválidos' );
    } else {
        $_SESSION['exemplo'][] = array(
            'id' => $newId,
            'name' => $json->name
        );

        $status = 201;
        $response = array( 'response' => true );
    }

    Response::status($status);
    Response::type('application/json');

    return json_encode($response);
});

// Lê uma entidade especifica
Route::set('GET', '/api/{:\d+:}', function ($id) {
    Response::type('application/json');

    $key = getItemById($id);

    if (empty($_SESSION['exemplo'][$key])) {
        Response::status(404);
        $response = array( 'response' => 'Não encontrado' );
    } else {
        $response = $_SESSION['exemplo'][$key];
    }

    return json_encode($response);
});

// Deleta uma entidade especifica
Route::set('DELETE', '/api/{:\d+:}', function ($id) {
    Response::type('application/json');

    $key = getItemById($id);

    if (empty($_SESSION['exemplo'][$key])) {
        Response::status(404);
        $response = array( 'response' => 'Não encontrado' );
    } else {
        unset($_SESSION['exemplo'][$key]);

        $response = array( 'response' => true );
    }

    return json_encode($response);
});

This would be an example of a very basic API, of course all done "manually", now creating a controller and using the class Inphinit\Experimental\Rest would look something like this:

  • ./system/main.php

    <?php
    
    use Inphinit\Experimental\Routing\Rest;
    
    Rest::create('Photo');
    
  • ./system/application/Controller/Pessoas.php

    <?php
    namespace Controller;
    
    use Inphinit\Response;
    
    class Pessoas
    {
        //Ao instanciar a classe (ocorre internamente dentro de )
        public function __construct()
        {
            session_start();
    
            // Cria os dados iniciais se não existirem
            if (empty($_SESSION['exemplo'])) {
                $_SESSION['exemplo'] = array(
                    array( 'id' => 1, 'name' => 'Guilherme Costamilam' ),
                    array( 'id' => 2, 'name' => 'Wallace Maxters' ),
                    array( 'id' => 3, 'name' => 'Carlos Bacco' )
                );
            }
        }
    
        // acesse via GET http://site.com/
        public function index()
        {
            return json_encode($_SESSION['exemplo']);
        }
    
        // acesse via POST http://site.com/
        public function store()
        {
            $raw = Request::raw();
            $json = null;
    
            if ($raw) {
                $json = json_decode($raw);
            }
    
            if (empty($json->name)) {
                $status = 400;
                $response = array( 'response' => 'Dados inválidos' );
            } else {
                $status = 201;
                $response = array( 'response' => true );
            }
    
            Response::status($status);
            Response::type('application/json');
    
            return json_encode($response);
        }
    
        // acesse via GET http://site.com/show/<ID>
        public function show($id)
        {
            $key = getItemById($id);
    
            if (empty($_SESSION['exemplo'][$key])) {
                Response::status(404);
                $response = array( 'response' => 'Não encontrado' );
            } else {
                $response = $_SESSION['exemplo'][$id];
            }
    
            return json_encode($response);
        }
    
        // acesse via DELETE http://site.com/<ID>
        public function destroy($id)
        {
            $key = getItemById($id);
    
            if (empty($_SESSION['exemplo'][$key])) {
                Response::status(404);
                $response = array( 'response' => 'Não encontrado' );
            } else {
                unset($_SESSION['exemplo'][$key]);
    
                $response = array( 'response' => true );
            }
    
            return json_encode($response);
        }
    
        private function getItemById($id)
        {
            foreach ($_SESSION['exemplo'] as $key => $value) {
                if ($value['id'] == $id) return $key;
            }
    
            return false;
        }
    }
    

This whole explanation was to understand what can "facilitate", now let’s explain the specific methods create and edit, I personally believe that these methods are only needed in environments like web pages or that will render something like HTML forms.

For example in these methods would have forms that would make requests fetch() or XmlHttpRequest that would access the methods store and update.

Now another scenario, imagine you are creating a Javafx application, and want to use FXML (read https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html), then the create:

public function create($id)
{
    Response::type('application/xml');

    return '<?xml version="1.0" encoding="UTF-8"?>
        <?import java.net.*?>
        <?import javafx.geometry.*?>
        <?import javafx.scene.control.*?>
        <?import javafx.scene.layout.*?>
        <?import javafx.scene.text.*?>

        <GridPane fx:controller="fxmlexample.FXMLExampleController" 
            xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10"
            styleClass="root">
            <padding><Insets top="25" right="25" bottom="25" left="25"/></padding>

            <Label text="Digite o nome do novo usuário:"
                GridPane.columnIndex="0" GridPane.rowIndex="1"/>

            <TextField 
                GridPane.columnIndex="1" GridPane.rowIndex="1"/>

            <HBox spacing="10" alignment="bottom_right" 
                GridPane.columnIndex="1" GridPane.rowIndex="4">
                <Button text="Sign In"     
                onAction="#handleSubmitButtonAction"/>
            </HBox>

        </GridPane>';
}

And in the edit would return something like:

public function edit($id)
{
    Response::type('application/xml');

    $name = null;

    foreach ($_SESSION['foobarbaz'] as $key => $value) {
        if ($value['id'] == $id) {
            $name = $value['name'];
            break;
        }
    }

    if (!$name) {
        Response::status(404);
        $response = '<?xml version="1.0" encoding="UTF-8"?>
            <?import javafx.scene.control.Label?>
            <Label text="Não encontrado!"/>';
    } else {
        $response = '<?xml version="1.0" encoding="UTF-8"?>
            <?import java.net.*?>
            <?import javafx.geometry.*?>
            <?import javafx.scene.control.*?>
            <?import javafx.scene.layout.*?>
            <?import javafx.scene.text.*?>

            <GridPane fx:controller="fxmlexample.FXMLExampleController" 
                xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10"
                styleClass="root">
                <padding><Insets top="25" right="25" bottom="25" left="25"/></padding>

                <Label text="Edite o nome do usuário:"
                    GridPane.columnIndex="0" GridPane.rowIndex="1"/>

                <TextField text="' . $name . '"
                    GridPane.columnIndex="1" GridPane.rowIndex="1"/>

                <HBox spacing="10" alignment="bottom_right" 
                    GridPane.columnIndex="1" GridPane.rowIndex="4">
                    <Button text="Sign In"     
                    onAction="#handleSubmitButtonAction"/>
                </HBox>

            </GridPane>';
    }

    return $response;
}

If you have any fault with the Fxmls let me know, it’s not my area

These are hypothetical examples only, many things I based on existing frameworks to make it easier for people to adapt and learn quickly the use of the framework, trying to make it a little easier

I must make something clear about the namespace Inphinit\Experimental, everything in it is geared towards suggestions (not that others do not accept), I accept any suggestion and I will analyze it calmly, but to summarize if you have suggestions on how to work with REST in the framework can contact me wherever you want, facebook, Telegram and Linkedin.

To summarize, if you only want a JSON or XML API you just create the methods like this:

class Usuarios
{
    // acesse via GET http://site/photo/
    public function index()
    {
        ...
    }

    // acesse via GET http://site/photo/create
    public function create()
    {
        ...
    }

    // acesse via POST http://site/photo/
    public function store()
    {
        ...
    }

    // acesse via GET http://site/photo/show/<digite o nome ou ID>
    public function show($id)
    {
        ...
    }

    // acesse via PUT http://site/photo/<digite o nome ou ID>
    public function update($id)
    {
        ...
    }

    // acesse via DELETE http://site/photo/<digite o nome ou ID>
    public function destroy($id)
    {
        ...
    }
}

This way the routes GET /create and GET /<id ou algo assim>/edit nay will be available


Extra: Group routes

You can isolate the API by a "sub-route" with Inphinit\Experimental\Group something like:

<?php
use Inphinit\Experimental\Routing\Rest;
use Inphinit\Experimental\Routing\Group;

Group::create()->path('/api/')->then(function () {
    Rest::create('Usuarios');
});

Can isolate by domains:

//Acessivel via foobar.com ou www.foobar.com
Group::create()->domain('{:www|:}.foobar.com')->then(function () {
    Route::set('GET', '/', 'Foo:index');
    Route::set('GET', '/abc', 'Foo:bar');
    Route::set('GET', '/xyz/', 'Foo:baz');
});

//Acessivel via api.foobar.com
Group::create()->domain('api.foobar.com')->then(function () {
    Rest::create('Usuarios');
});

What would be another way to isolate what are forms and the like and isolate what are Apis

  • Cool, my only suggestion would be to use a name like Resource instead of REST. A question, if I then try to access a route I haven’t defined, what does it return? 404?

  • @Guilhermecostamilam yes, 404, but I’m thinking of changing the scheme to registered routes but with different methods for 405 Method Not Allowed, this for any route, whether on a normal site, or an API

  • @Guilhermecostamilam by the logic of how I actually implemented the API system would be about one "RESOURCE" at a time, however the idea of writing REST initially seemed more intuitive for users to understand the indicated use, of course the FW there allows you to kind of use as you wish, I’m not a fan of limiting people, but I’ll think it over and suggest something to see if it pleases. I have some improvements to version 0.1.3 (or 0.2.0) that I still need to publish, nothing that affects the operation :)

  • @Guilhermecostamilam advanced what was possible, version 0.1.5, now routes defined, but in methods other than accessed will issue code 405, the class Inphinit\Experimental\Dom improved, it can be a good way to return arrays as JSON or as XML varying according to header Accepts from HTTP (take a look at https://github.com/inphinit/inphinit/wiki/NegoryContentious_HTTP and https://github.com/inphinit/inphinit/wiki/DOM%2C-Xml%2C-HTML%2C-Json-e-Arrays#array-for-dom-xml-or-html)

  • 1

    I really was in doubt about 404 vs 405 (ask a question). Thanks for the help and congratulations on the Inphinit

  • @Guilhermecostamilam think the idea is cool, you can answer something really cool, to advance 405 is when the route "exists" but the expected HTTP method is another, in the initial logic of FW I had created that the routes with verbs (GET, POST, etc) were different, for example GET /foo and POST /foo would be different things, but with the 405 answer you can make it easy to understand that there is, by chance who will use the API wrong configure the client program, add me in Telegram, I want to send you some links

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.