RESTful services with jQuery, PHP and the Slim Framework, 2011 in JQuery, PHP, REST

NOTE: This is the PHP version of this article and its companion app. A Java version is available here.

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. They all seem like good frameworks. In the end, I chose Slim for this project for two main reasons:

It’s very lightweight and focused on REST and nothing else.
It supports all the HTTP methods (GET, POST, PUT, DELETE), which was a key requirement for my application.

This article (and its companion app) provides an example of building a complete RESTful API using the different HTTP methods:

GET to retrieve and search data
POST to add data
PUT to update data
DELETE to delete data

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.

The REST API consists of the following methods:
Method URL Action
GET /api/wines Retrieve all wines
GET /api/wines/search/Chateau Search for wines with ‘Chateau’ in their name
GET /api/wines/10 Retrieve wine with id == 10
POST /api/wines Add a new wine
PUT /api/wines/10 Update wine with id == 10
DELETE /api/wines/10 Delete wine with id == 10

Implementing the API with Slim

Slim makes it easy to implement this API in PHP:
$app->get(‘/wines/:id’, ‘getWine’);
$app->get(‘/wines/search/:query’, ‘findByName’);
$app->post(‘/wines’, ‘addWine’);
$app->put(‘/wines/:id’, ‘updateWine’);
$app->delete(‘/wines/:id’, ‘deleteWine’);

$app->run();

function getWines() {
$sql = “select * FROM wine ORDER BY name”;
try {
$db = getConnection();
$stmt = $db->query($sql);
$wines = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
echo ‘{“wine”: ‘ . json_encode($wines) . ‘}’;
} catch(PDOException $e) {
echo ‘{“error”:{“text”:’. $e->getMessage() .’}}’;
}
}

function getWine($id) {
$sql = “SELECT * FROM wine WHERE id=:id”;
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$stmt->bindParam(“id”, $id);
$stmt->execute();
$wine = $stmt->fetchObject();
$db = null;
echo json_encode($wine);
} catch(PDOException $e) {
echo ‘{“error”:{“text”:’. $e->getMessage() .’}}’;
}
}

function addWine() {
$request = Slim::getInstance()->request();
$wine = json_decode($request->getBody());
$sql = “INSERT INTO wine (name, grapes, country, region, year, description) VALUES (:name, :grapes, :country, :region, :year, :description)”;
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$stmt->bindParam(“name”, $wine->name);
$stmt->bindParam(“grapes”, $wine->grapes);
$stmt->bindParam(“country”, $wine->country);
$stmt->bindParam(“region”, $wine->region);
$stmt->bindParam(“year”, $wine->year);
$stmt->bindParam(“description”, $wine->description);
$stmt->execute();
$wine->id = $db->lastInsertId();
$db = null;
echo json_encode($wine);
} catch(PDOException $e) {
echo ‘{“error”:{“text”:’. $e->getMessage() .’}}’;
}
}

function updateWine($id) {
$request = Slim::getInstance()->request();
$body = $request->getBody();
$wine = json_decode($body);
$sql = “UPDATE wine SET name=:name, grapes=:grapes, country=:country, region=:region, year=:year, description=:description WHERE id=:id”;
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$stmt->bindParam(“name”, $wine->name);
$stmt->bindParam(“grapes”, $wine->grapes);
$stmt->bindParam(“country”, $wine->country);
$stmt->bindParam(“region”, $wine->region);
$stmt->bindParam(“year”, $wine->year);
$stmt->bindParam(“description”, $wine->description);
$stmt->bindParam(“id”, $id);
$stmt->execute();
$db = null;
echo json_encode($wine);
} catch(PDOException $e) {
echo ‘{“error”:{“text”:’. $e->getMessage() .’}}’;
}
}

function deleteWine($id) {
$sql = “DELETE FROM wine WHERE id=:id”;
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$stmt->bindParam(“id”, $id);
$stmt->execute();
$db = null;
} catch(PDOException $e) {
echo ‘{“error”:{“text”:’. $e->getMessage() .’}}’;
}
}

function findByName($query) {
$sql = “SELECT * FROM wine WHERE UPPER(name) LIKE :query ORDER BY name”;
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$query = “%”.$query.”%”;
$stmt->bindParam(“query”, $query);
$stmt->execute();
$wines = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
echo ‘{“wine”: ‘ . json_encode($wines) . ‘}’;
} catch(PDOException $e) {
echo ‘{“error”:{“text”:’. $e->getMessage() .’}}’;
}
}

function getConnection() {
$dbhost=”127.0.0.1″;
$dbuser=”root”;
$dbpass=””;
$dbname=”cellar”;
$dbh = new PDO(“mysql:host=$dbhost;dbname=$dbname”, $dbuser, $dbpass);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $dbh;
}

?>

 

 


function findAll() {
    $.ajax({
        type: 'GET',
        url: rootURL,
        dataType: "json", // data type of response
        success: renderList
    });
}
function findByName(searchKey) {
    $.ajax({
        type: 'GET',
        url: rootURL + '/search/' + searchKey,
        dataType: "json",
        success: renderList
    });
}
function findById(id) {
    $.ajax({
        type: 'GET',
        url: rootURL + '/' + id,
        dataType: "json",
        success: function(data){
            $('#btnDelete').show();
            renderDetails(data);
        }
    });
}
function addWine() {
    console.log('addWine');
    $.ajax({
        type: 'POST',
        contentType: 'application/json',
        url: rootURL,
        dataType: "json",
        data: formToJSON(),
        success: function(data, textStatus, jqXHR){
            alert('Wine created successfully');
            $('#btnDelete').show();
            $('#wineId').val(data.id);
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert('addWine error: ' + textStatus);
        }
    });
}
function updateWine() {
    $.ajax({
        type: 'PUT',
        contentType: 'application/json',
        url: rootURL + '/' + $('#wineId').val(),
        dataType: "json",
        data: formToJSON(),
        success: function(data, textStatus, jqXHR){
            alert('Wine updated successfully');
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert('updateWine error: ' + textStatus);
        }
    });
}
function deleteWine() {
    console.log('deleteWine');
    $.ajax({
        type: 'DELETE',
        url: rootURL + '/' + $('#wineId').val(),
        success: function(data, textStatus, jqXHR){
            alert('Wine deleted successfully');
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert('deleteWine error');
        }
    });
}
// Helper function to serialize all the form fields into a JSON string
function formToJSON() {
    return JSON.stringify({
        "id": $('#id').val(),
        "name": $('#name').val(),
        "grapes": $('#grapes').val(),
        "country": $('#country').val(),
        "region": $('#region').val(),
        "year": $('#year').val(),
        "description": $('#description').val()
        });
}
————