跳至主要内容

Create a restful application with AngularJS and CakePHP(II): Bake backend REST API


Bake backend REST API

I will reuse the database scheme of the CakePHP Blog tutorial.

Prepare posts table

Execute the following scripts to create a posts table in your MySQL database, it also initialized the sample data.
/* First, create our posts table: */
CREATE TABLE posts (
 id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 title VARCHAR(50),
 body TEXT,
 created DATETIME DEFAULT NULL,
 modified DATETIME DEFAULT NULL
);

/* Then insert some posts for testing: */
INSERT INTO posts (title,body,created)
VALUES (’The title’, ’This is the post body.’, NOW());
INSERT INTO posts (title,body,created)
VALUES (’A title once again’, ’And the post body follows.’, NOW());
INSERT INTO posts (title,body,created)
VALUES (’Title strikes back’, ’This is really exciting! Not.’, NOW());
You can use MySQL client command line or MySQL Workbench(GUI) to complete this work.

Bake the skeleton of model and controller

Use bake command line to generate model, controller for posts table.
 Vendor\pear-pear.cakephp.org\CakePHP\bin\cake.bat bake model Post
This command will generate a Model named Post, it is under Modelfolder.
class Post extends AppModel {

}
A model class must extend AppModel which is a base class in the same folder, it is a place holder to customize common model hooks for all models in your application.
Use the following command to generate the posts controller.
 Vendor\pear-pear.cakephp.org\CakePHP\bin\cake.bat bake controller posts
After it is executed and a file named PostsController.php will be generated in the Controller folder.
class PostsController extends AppController {
 public $scaffold;
}
A controller class must extends AppController, it is similar with theAppModel. It is a place holder to customize the common controller lifecycle. All controllers in this application should extendAppController
The naming of table, model and controller follows the CakePHP naming convention.
You can also use cake bake all to generate model, controller and view together. Of course, I do not need the view in this project, as described formerly, we will use AngularJS to build the frontend UI.
If you want to generate the codes interactively, ignore the name argument in the bake command line.
For example,
 Vendor\pear-pear.cakephp.org\CakePHP\bin\cake.bat bake controller 
The bake command will guide you to generate the PostsControllerstep by step, you can also generate the CRUD dummy codes instead of the default public $scaffold;.
But the generated CRUD methods are ready for web pages, we will replace them with REST resource operations.

Make the controller restful

Open PostsController and add the basic CRUD methods for modelPost.
public function index() {
        $posts = $this->Post->find('all');
        $this->set(array(
            'posts' => $posts,
            '_serialize' => array('posts')
        ));
    }

    public function view($id) {
        $post = $this->Post->findById($id);
        $this->set(array(
            'post' => $post,
            '_serialize' => array('post')
        ));
    }

    public function add() {
        //$this->Post->id = $id;
        if ($this->Post->save($this->request->data)) {
            $message = array(
                'text' => __('Saved'),
                'type' => 'success'
            );
        } else {
            $message = array(
                'text' => __('Error'),
                'type' => 'error'
            );
        }
        $this->set(array(
            'message' => $message,
            '_serialize' => array('message')
        ));
    }

    public function edit($id) {
        $this->Post->id = $id;
        if ($this->Post->save($this->request->data)) {
            $message = array(
                'text' => __('Saved'),
                'type' => 'success'
            );
        } else {
            $message = array(
                'text' => __('Error'),
                'type' => 'error'
            );
        }
        $this->set(array(
            'message' => $message,
            '_serialize' => array('message')
        ));
    }

    public function delete($id) {
        if ($this->Post->delete($id)) {
            $message = array(
                'text' => __('Deleted'),
                'type' => 'success'
            );
        } else {
            $message = array(
                'text' => __('Error'),
                'type' => 'error'
            );
        }
        $this->set(array(
            'message' => $message,
            '_serialize' => array('message')
        ));
    }
As you see, we must use _serialize as the key of the associated array of the returned result.
Besides these, RequestHandler component is required to process the REST resource request.
public $components = array('RequestHandler');
Open Config/routes.php, add the following lines beforerequire CAKE . 'Config' . DS . 'routes.php';.
Router::mapResources("posts");
Router::parseExtensions();
Router::mapResources indicates which posts will be mapped as REST resources, and Router::parseExtensions will parse the resources according to the file extension, XML and JSON are native supported.
Now navigate to http://localhost/posts.json, you will get the following JSON string in browser.
{
    "posts": [
        {
            "Post": {
                "id": "1",
                "title": "The title",
                "body": "This is the post body.",
                "created": "2013-10-22 16:10:53",
                "modified": null
            }
        },
        {
            "Post": {
                "id": "2",
                "title": "A title once again",
                "body": "And the post body follows.",
                "created": "2013-10-22 16:10:53",
                "modified": null
            }
        },
        {
            "Post": {
                "id": "3",
                "title": "Title strikes back",
                "body": "This is really exciting! Not.",
                "created": "2013-10-22 16:10:53",
                "modified": null
            }
        }
    ]
}
When access http://localhost/posts.json, it will call the index method of PostsController, and render the data as JOSN format. CakePHP follows the following rules to map REST resources to controllers. Here I use PostsController as an example to describe it.
URLHTTP MethodPostsController methodDescription
/posts.jsonGETindexGet the list of Posts
/posts.jsonPOSTaddCreate a new Post
/posts/:id.jsonGETviewGet the details of a Post
/posts/:id.jsonPUTeditUpdate a Post by id
/posts/:id.jsonDELETEdeleteDelete a Post by id
If you want to change the resource mapping, you can define your rules via Router::resourceMap in Config/routes.php.
Router::resourceMap(array(
 array(’action’ => ’index’, ’method’ => ’GET’, ’id’ => false),
 array(’action’ => ’view’, ’method’ => ’GET’, ’id’ => true),
 array(’action’ => ’add’, ’method’ => ’POST’, ’id’ => false),
 array(’action’ => ’edit’, ’method’ => ’PUT’, ’id’ => true),
 array(’action’ => ’delete’, ’method’ => ’DELETE’, ’id’ => true),
 array(’action’ => ’update’, ’method’ => ’POST’, ’id’ => true)
));
Alternatively, you can use Router::connect to define the route rule manually.

Summary

CakePHP REST API producing is simple and stupid, the only thing make me a little incompatible is the produced JSON data, the JSON data structure is a little tedious.

评论

此博客中的热门博文

Build a Reactive application with Angular 5 and Spring Boot 2.0

I have created a post to describe Reactive programming supports in Spring 5 and its subprojects, all codes of this article are updated the latest Spring 5 RELEASE, check spring-reactive-sample under my Github account.
In this post, I will create a simple blog system, including:
A user can sign in and sign out.An authenticated user can create a post.An authenticated user can update a post.Only the user who has ADMIN role can delete a post.All users(including anonymous users) can view post list and post details.An authenticated user can add his comments to a certain post. The backend will be built with the latest Spring 5 reactive stack, including:
Spring Boot 2.0, at the moment the latest version is 2.0.0.M7Spring Data MongoDB supports reactive operations for MongoDBSpring Session adds reactive support for WebSessionSpring Security 5 aligns with Spring 5 reactive stack The frontend is an Angular based SPA and it will be generated by Angular CLI.
The source code is hosted on Github, …

Activating CDI in JSF 2.3

Activating CDI in JSF 2.3 When I upgraed my Java EE 7 sample to the newest Java EE 8, the first thing confused me is the CDI beans are not recoganized in Facelects template in a JSF 2.3 based web applicaiton, which is working in the development version, but in the final release version, they are always resolved as null. I filed an issue on Mojarra and discussed it with the developers from communities and the JSF experts.
According to the content of README, In a JSF 2.3 application, to activate CDI support, declaring a 2.3 versioned faces-config.xml and adding javax.faces.ENABLE_CDI_RESOLVER_CHAIN in web.xml is not enough, you have to declare @FacesConfig annotated class to enable CDI.
Here is the steps I created a workable JSF 2.3 applicatoin in Java EE 8.
Create a Java web application, this can be done easily by NetBeans IDE, or generated by Maven archetype, for exmaple.
$ mvn archetype:generate -DgroupId=com.example -DartifactId=demo -DarchetypeArtifactId=maven-archetype-w…

JSF 2.3:Websocket support

Websocket support One of the most attractive features is JSF 2.3 added native websocket support, it means you can write real-time applications with JSF and no need extra effort.
To enable websocket support, you have to add javax.faces.ENABLE_WEBSOCKET_ENDPOINT in web.xml.
<context-param> <param-name>javax.faces.ENABLE_WEBSOCKET_ENDPOINT</param-name> <param-value>true</param-value> </context-param> Hello Websocket Let's start with a simple example.
@ViewScoped@Named("helloBean") publicclassHelloBeanimplementsSerializable { privatestaticfinalLoggerLOG=Logger.getLogger(HelloBean.class.getName()); @Inject@PushPushContext helloChannel; String message; publicvoidsendMessage() { LOG.log(Level.INFO, "send push message"); this.sendPushMessage("hello"); } privatevoidsendPushMessage(Objectmessage) { helloChannel.send(""+ message +" at &…