{"id":2624,"date":"2015-05-19T22:06:55","date_gmt":"2015-05-19T20:06:55","guid":{"rendered":"http:\/\/www.extradrm.com\/?p=2624"},"modified":"2020-10-09T22:13:34","modified_gmt":"2020-10-09T20:13:34","slug":"2624","status":"publish","type":"post","link":"https:\/\/www.extradrm.com\/?p=2624","title":{"rendered":"RESTful services with jQuery, PHP and the Slim Framework"},"content":{"rendered":"<p>I have been looking for a lightweight framework to build a RESTful API in PHP. There are a number of good options out there: <a href=\"http:\/\/www.slimframework.com\/\">Slim<\/a>, <a href=\"https:\/\/github.com\/jmathai\/epiphany\">Epiphany<\/a>, <a href=\"http:\/\/peej.github.com\/tonic\/\">Tonic<\/a>, <a href=\"http:\/\/www.recessframework.org\/\">Recess<\/a>, and <a href=\"http:\/\/getfrapi.com\/\">Frapi<\/a> to name a few. They all seem like good frameworks. In the end, I chose Slim for this project for two main reasons:<\/p>\n<ol>\n<li>It\u2019s very lightweight and focused on REST and nothing else.<\/li>\n<li>It supports all the HTTP methods (GET, POST, PUT, DELETE), which was a key requirement for my application.<\/li>\n<\/ol>\n<p>This article (and its companion app) provides an example of building a complete RESTful API using the different HTTP methods:<\/p>\n<ul>\n<li><strong>GET<\/strong> to retrieve and search data<\/li>\n<li><strong>POST<\/strong> to add data<\/li>\n<li><strong>PUT<\/strong> to update data<\/li>\n<li><strong>DELETE<\/strong> to delete data<\/li>\n<\/ul>\n<p>The application used as an example for this article is a Wine Cellar app. You can search for wines, add a wine to your cellar, update and delete wines.<\/p>\n<p><a href=\"http:\/\/www.extradrm.com\/wp-content\/uploads\/2015\/05\/cellar2.jpg\"><img loading=\"lazy\" class=\"aligncenter size-full wp-image-2625\" src=\"http:\/\/www.extradrm.com\/wp-content\/uploads\/2015\/05\/cellar2.jpg\" alt=\"cellar2\" width=\"640\" height=\"496\" srcset=\"https:\/\/www.extradrm.com\/wp-content\/uploads\/2015\/05\/cellar2.jpg 640w, https:\/\/www.extradrm.com\/wp-content\/uploads\/2015\/05\/cellar2-300x232.jpg 300w\" sizes=\"(max-width: 640px) 100vw, 640px\" \/><\/a><\/p>\n<p>You can run the application <a href=\"http:\/\/coenraets.org\/cellar\">here<\/a>. The create\/update\/delete features are disabled in this online version. Use the link at the bottom of this post to download a fully enabled version.<\/p>\n<p>The REST API consists of the following methods:<\/p>\n<table>\n<tbody>\n<tr>\n<th>Method<\/th>\n<th>URL<\/th>\n<th>Action<\/th>\n<\/tr>\n<tr>\n<td>GET<\/td>\n<td>\/api\/wines<\/td>\n<td>Retrieve all wines<\/td>\n<\/tr>\n<tr>\n<td>GET<\/td>\n<td>\/api\/wines\/search\/Chateau<\/td>\n<td>Search for wines with \u2018Chateau\u2019 in their name<\/td>\n<\/tr>\n<tr>\n<td>GET<\/td>\n<td>\/api\/wines\/10<\/td>\n<td>Retrieve wine with id == 10<\/td>\n<\/tr>\n<tr>\n<td>POST<\/td>\n<td>\/api\/wines<\/td>\n<td>Add a new wine<\/td>\n<\/tr>\n<tr>\n<td>PUT<\/td>\n<td>\/api\/wines\/10<\/td>\n<td>Update wine with id == 10<\/td>\n<\/tr>\n<tr>\n<td>DELETE<\/td>\n<td>\/api\/wines\/10<\/td>\n<td>Delete wine with id == 10<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<h4>Implementing the API with Slim<\/h4>\n<p>Slim makes it easy to implement this API in PHP:<\/p>\n<pre>lt;?php\r\n\r\nrequire 'Slim\/Slim.php';\r\n\r\n$app = new Slim();\r\n\r\n$app-&gt;get('\/wines', 'getWines');\r\n$app-&gt;get('\/wines\/:id',  'getWine');\r\n$app-&gt;get('\/wines\/search\/:query', 'findByName');\r\n$app-&gt;post('\/wines', 'addWine');\r\n$app-&gt;put('\/wines\/:id', 'updateWine');\r\n$app-&gt;delete('\/wines\/:id',   'deleteWine');\r\n\r\n$app-&gt;run();\r\n\r\nfunction getWines() {\r\n    $sql = \"select * FROM wine ORDER BY name\";\r\n    try {\r\n        $db = getConnection();\r\n        $stmt = $db-&gt;query($sql);\r\n        $wines = $stmt-&gt;fetchAll(PDO::FETCH_OBJ);\r\n        $db = null;\r\n        echo '{\"wine\": ' . json_encode($wines) . '}';\r\n    } catch(PDOException $e) {\r\n        echo '{\"error\":{\"text\":'. $e-&gt;getMessage() .'}}';\r\n    }\r\n}\r\n\r\nfunction getWine($id) {\r\n    $sql = \"SELECT * FROM wine WHERE id=:id\";\r\n    try {\r\n        $db = getConnection();\r\n        $stmt = $db-&gt;prepare($sql);\r\n        $stmt-&gt;bindParam(\"id\", $id);\r\n        $stmt-&gt;execute();\r\n        $wine = $stmt-&gt;fetchObject();\r\n        $db = null;\r\n        echo json_encode($wine);\r\n    } catch(PDOException $e) {\r\n        echo '{\"error\":{\"text\":'. $e-&gt;getMessage() .'}}';\r\n    }\r\n}\r\n\r\nfunction addWine() {\r\n    $request = Slim::getInstance()-&gt;request();\r\n    $wine = json_decode($request-&gt;getBody());\r\n    $sql = \"INSERT INTO wine (name, grapes, country, region, year, description) VALUES (:name, :grapes, :country, :region, :year, :description)\";\r\n    try {\r\n        $db = getConnection();\r\n        $stmt = $db-&gt;prepare($sql);\r\n        $stmt-&gt;bindParam(\"name\", $wine-&gt;name);\r\n        $stmt-&gt;bindParam(\"grapes\", $wine-&gt;grapes);\r\n        $stmt-&gt;bindParam(\"country\", $wine-&gt;country);\r\n        $stmt-&gt;bindParam(\"region\", $wine-&gt;region);\r\n        $stmt-&gt;bindParam(\"year\", $wine-&gt;year);\r\n        $stmt-&gt;bindParam(\"description\", $wine-&gt;description);\r\n        $stmt-&gt;execute();\r\n        $wine-&gt;id = $db-&gt;lastInsertId();\r\n        $db = null;\r\n        echo json_encode($wine);\r\n    } catch(PDOException $e) {\r\n        echo '{\"error\":{\"text\":'. $e-&gt;getMessage() .'}}';\r\n    }\r\n}\r\n\r\nfunction updateWine($id) {\r\n    $request = Slim::getInstance()-&gt;request();\r\n    $body = $request-&gt;getBody();\r\n    $wine = json_decode($body);\r\n    $sql = \"UPDATE wine SET name=:name, grapes=:grapes, country=:country, region=:region, year=:year, description=:description WHERE id=:id\";\r\n    try {\r\n        $db = getConnection();\r\n        $stmt = $db-&gt;prepare($sql);\r\n        $stmt-&gt;bindParam(\"name\", $wine-&gt;name);\r\n        $stmt-&gt;bindParam(\"grapes\", $wine-&gt;grapes);\r\n        $stmt-&gt;bindParam(\"country\", $wine-&gt;country);\r\n        $stmt-&gt;bindParam(\"region\", $wine-&gt;region);\r\n        $stmt-&gt;bindParam(\"year\", $wine-&gt;year);\r\n        $stmt-&gt;bindParam(\"description\", $wine-&gt;description);\r\n        $stmt-&gt;bindParam(\"id\", $id);\r\n        $stmt-&gt;execute();\r\n        $db = null;\r\n        echo json_encode($wine);\r\n    } catch(PDOException $e) {\r\n        echo '{\"error\":{\"text\":'. $e-&gt;getMessage() .'}}';\r\n    }\r\n}\r\n\r\nfunction deleteWine($id) {\r\n    $sql = \"DELETE FROM wine WHERE id=:id\";\r\n    try {\r\n        $db = getConnection();\r\n        $stmt = $db-&gt;prepare($sql);\r\n        $stmt-&gt;bindParam(\"id\", $id);\r\n        $stmt-&gt;execute();\r\n        $db = null;\r\n    } catch(PDOException $e) {\r\n        echo '{\"error\":{\"text\":'. $e-&gt;getMessage() .'}}';\r\n    }\r\n}\r\n\r\nfunction findByName($query) {\r\n    $sql = \"SELECT * FROM wine WHERE UPPER(name) LIKE :query ORDER BY name\";\r\n    try {\r\n        $db = getConnection();\r\n        $stmt = $db-&gt;prepare($sql);\r\n        $query = \"%\".$query.\"%\";\r\n        $stmt-&gt;bindParam(\"query\", $query);\r\n        $stmt-&gt;execute();\r\n        $wines = $stmt-&gt;fetchAll(PDO::FETCH_OBJ);\r\n        $db = null;\r\n        echo '{\"wine\": ' . json_encode($wines) . '}';\r\n    } catch(PDOException $e) {\r\n        echo '{\"error\":{\"text\":'. $e-&gt;getMessage() .'}}';\r\n    }\r\n}\r\n\r\nfunction getConnection() {\r\n    $dbhost=\"127.0.0.1\";\r\n    $dbuser=\"root\";\r\n    $dbpass=\"\";\r\n    $dbname=\"cellar\";\r\n    $dbh = new PDO(\"mysql:host=$dbhost;dbname=$dbname\", $dbuser, $dbpass);\r\n    $dbh-&gt;setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);\r\n    return $dbh;\r\n}\r\n\r\n?&gt;<\/pre>\n<p><strong>Code Highlights<\/strong><\/p>\n<ol>\n<li>Lines 7 to 12: Slim helps you route resource URIs to callback functions in response to specific HTTP request methods (e.g. GET, POST, PUT, DELETE).<\/li>\n<li>Lines 45 to 46 and 67 to 68: the request object makes it easy to access the request\u2019s data: In this case the JSON representation of a wine object.<\/li>\n<li>The approach you use to actually retrieve the data is totally up to you. In this example, I use some simple PDO code, but you can of course use your own data access solution.<\/li>\n<\/ol>\n<h4>Testing the API using cURL<\/h4>\n<p>If you want to test your API before using it in a client application, you can invoke your REST services straight from a browser address bar. For example, you could try:<\/p>\n<ul>\n<li><a href=\"http:\/\/localhost\/cellar\/api\/wines\">http:\/\/localhost\/cellar\/api\/wines<\/a><\/li>\n<li><a href=\"http:\/\/localhost\/cellar\/api\/wines\/search\/Chateau\">http:\/\/localhost\/cellar\/api\/wines\/search\/Chateau<\/a><\/li>\n<li><a href=\"http:\/\/localhost\/cellar\/api\/wines\/5\">http:\/\/localhost\/cellar\/api\/wines\/5<\/a><\/li>\n<\/ul>\n<p>You will only be able to test your GET services that way, and even then, it doesn\u2019t give you full control to test all the content types your API can return.<\/p>\n<p>A more versatile solution to test RESTful services is to use <a href=\"http:\/\/curl.haxx.se\/\">cURL<\/a>, a command line utility for transferring data with URL syntax.<\/p>\n<p>For example, using cURL, you can test the Wine Cellar API with the following commands:<\/p>\n<ul>\n<li>Get all wines:\n<div>\n<div id=\"highlighter_511394\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<div>\n<div><code>curl -i -X GET http:<\/code><code>\/\/localhost\/cellar\/api\/wines<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/li>\n<li>Get all wines with \u2018chateau\u2019 in their name:\n<div>\n<div id=\"highlighter_925005\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<div>\n<div><code>curl -i -X GET http:<\/code><code>\/\/localhost\/cellar\/api\/wines\/search\/chateau<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/li>\n<li>Get wine #5:\n<div>\n<div id=\"highlighter_896113\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<div>\n<div><code>curl -i -X GET http:<\/code><code>\/\/localhost\/cellar\/api\/wines\/5<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/li>\n<li>Delete wine #5:\n<div>\n<div id=\"highlighter_963810\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<div>\n<div><code>curl -i -X DELETE http:<\/code><code>\/\/localhost\/cellar\/api\/wines\/5<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/li>\n<li>Add a new wine:<\/li>\n<li>\n<ul>\n<li>\n<div>\n<div id=\"highlighter_565544\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<div>\n<div><code>curl -i -X POST -H <\/code><code>'Content-Type: application\/json'<\/code> <code>-d <\/code><code>'{\"name\": \"New Wine\", \"year\": \"2009\"}'<\/code> <code>http:<\/code><code>\/\/localhost\/cellar\/api\/wines<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/li>\n<li>Modify wine #27:\n<div>\n<div id=\"highlighter_437795\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>\n<div>\n<div><code>curl -i -X PUT -H <\/code><code>'Content-Type: application\/json'<\/code> <code>-d <\/code><code>'{\"id\": \"27\", \"name\": \"New Wine\", \"year\": \"2010\"}'<\/code> <code>http:<\/code><code>\/\/localhost\/cellar\/api\/wines\/27<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<div id=\"main-sidebar-container\"><\/div>\n<\/li>\n<\/ul>\n<h4>The jQuery Client<\/h4>\n<p>Accessing your API through cURL is cool, but there is nothing like a real application to put your API to the test. So the source code (available for download at the end of this post) includes a simple jQuery client to manage your wine cellar.<\/p>\n<p>Here is the jQuery code involved in calling the services:<\/p>\n<pre>function findAll() {\r\n    $.ajax({\r\n        type: 'GET',\r\n        url: rootURL,\r\n        dataType: \"json\", \/\/ data type of response\r\n        success: renderList\r\n    });\r\n}\r\n\r\nfunction findByName(searchKey) {\r\n    $.ajax({\r\n        type: 'GET',\r\n        url: rootURL + '\/search\/' + searchKey,\r\n        dataType: \"json\",\r\n        success: renderList\r\n    });\r\n}\r\n\r\nfunction findById(id) {\r\n    $.ajax({\r\n        type: 'GET',\r\n        url: rootURL + '\/' + id,\r\n        dataType: \"json\",\r\n        success: function(data){\r\n            $('#btnDelete').show();\r\n            renderDetails(data);\r\n        }\r\n    });\r\n}\r\n\r\nfunction addWine() {\r\n    console.log('addWine');\r\n    $.ajax({\r\n        type: 'POST',\r\n        contentType: 'application\/json',\r\n        url: rootURL,\r\n        dataType: \"json\",\r\n        data: formToJSON(),\r\n        success: function(data, textStatus, jqXHR){\r\n            alert('Wine created successfully');\r\n            $('#btnDelete').show();\r\n            $('#wineId').val(data.id);\r\n        },\r\n        error: function(jqXHR, textStatus, errorThrown){\r\n            alert('addWine error: ' + textStatus);\r\n        }\r\n    });\r\n}\r\n\r\nfunction updateWine() {\r\n    $.ajax({\r\n        type: 'PUT',\r\n        contentType: 'application\/json',\r\n        url: rootURL + '\/' + $('#wineId').val(),\r\n        dataType: \"json\",\r\n        data: formToJSON(),\r\n        success: function(data, textStatus, jqXHR){\r\n            alert('Wine updated successfully');\r\n        },\r\n        error: function(jqXHR, textStatus, errorThrown){\r\n            alert('updateWine error: ' + textStatus);\r\n        }\r\n    });\r\n}\r\n\r\nfunction deleteWine() {\r\n    console.log('deleteWine');\r\n    $.ajax({\r\n        type: 'DELETE',\r\n        url: rootURL + '\/' + $('#wineId').val(),\r\n        success: function(data, textStatus, jqXHR){\r\n            alert('Wine deleted successfully');\r\n        },\r\n        error: function(jqXHR, textStatus, errorThrown){\r\n            alert('deleteWine error');\r\n        }\r\n    });\r\n}\r\n\r\n\/\/ Helper function to serialize all the form fields into a JSON string\r\nfunction formToJSON() {\r\n    return JSON.stringify({\r\n        \"id\": $('#id').val(),\r\n        \"name\": $('#name').val(),\r\n        \"grapes\": $('#grapes').val(),\r\n        \"country\": $('#country').val(),\r\n        \"region\": $('#region').val(),\r\n        \"year\": $('#year').val(),\r\n        \"description\": $('#description').val()\r\n        });\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<h4>Download the Source Code<\/h4>\n<p>The source code for this application is hosted on GitHub <a href=\"https:\/\/github.com\/ccoenraets\/wine-cellar-php\">here<\/a>. And <a href=\"https:\/\/github.com\/ccoenraets\/wine-cellar-php\/zipball\/master\">here<\/a> is a quick link to the source code download. It includes both the PHP and jQuery code for the application.<\/p>\n<p>I\u2019m interested in your feedback. Let me know what you think and what your experience has been building RESTful-based applications using PHP and jQuery<\/p>\n<div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>I have been looking for a lightweight framework to build a RESTful API in PHP. There are a number of good options out there: Slim, Epiphany, Tonic, Recess, and Frapi to name a few.&#46;&#46;&#46;<\/p>\n","protected":false},"author":1,"featured_media":2850,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[267,318,10],"tags":[],"youtube_video":null,"_links":{"self":[{"href":"https:\/\/www.extradrm.com\/index.php?rest_route=\/wp\/v2\/posts\/2624"}],"collection":[{"href":"https:\/\/www.extradrm.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.extradrm.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.extradrm.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.extradrm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2624"}],"version-history":[{"count":0,"href":"https:\/\/www.extradrm.com\/index.php?rest_route=\/wp\/v2\/posts\/2624\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.extradrm.com\/index.php?rest_route=\/wp\/v2\/media\/2850"}],"wp:attachment":[{"href":"https:\/\/www.extradrm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2624"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.extradrm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2624"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.extradrm.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2624"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}