A few days ago I wrote about using PHP with Docker and Couchbase, but I never really got into best practices of going all in with PHP and NoSQL databases. For example, how do you read and write data with Couchbase Server while using PHP? What happens when you need to create some advanced queries or create high-performance indexes?
We’re going to see some examples for using Couchbase Server with PHP, an extension to the previous tutorial around containerizing the database and web application.
While the previous tutorial is incredibly useful for microservice development, it is not a prerequisite to the material we discuss going forward.
Assuming you’ve got some PHP environment configured and ready for serving applications, we need to obtain the Couchbase PHP SDK for communication with our database. If you’re on Debian or Ubuntu, execute the following:
1 2 3 4 5 6 7 |
wget http://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-2-amd64.deb sudo dpkg -i couchbase-release-1.0-2-amd64.deb rm couchbase-release-1.0-2-amd64.deb sudo apt-get update sudo apt-get install libcouchbase-dev build-essential php5-dev zlib1g-dev sudo pecl install pcs-1.3.3 sudo pecl install couchbase |
The above Terminal commands were taken from the official Couchbase PHP documentation. First the SDK is downloaded and installed, followed by various Debian packages that are required. Finally the Couchbase extension is downloaded and its pcs
dependency.
After the dependencies are installed, add the following to your php.ini file:
1 |
extension=couchbase.so |
If you’re using an environment other than Debian or Ubuntu for hosting your PHP application, check out the documentation for other SDK installation steps.
Performing CRUD Operations With Couchbase Server and PHP
Couchbase in its simplest form is a key-value database. With this in mind, we can do operations such as read, write, replace, and delete (CRUD) all based on the key for a particular document.
Take the following PHP code for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?php header("Content-Type: application/json"); $cluster = new CouchbaseCluster("couchbase://localhost"); $bucket = $cluster->openBucket("default", ""); try { $result = $bucket->get("nraboy"); } catch (CouchbaseException $e) { $bucket->insert("nraboy", array( "firstname" => "Nic", "lastname" => "Raboy", "social_media" => array( "twitter" => "https://www.twitter.com/nraboy", "website" => "https://www.thepolyglotdeveloper.com" ) )); $result = $bucket->get("nraboy"); } echo json_encode($result->value); ?> |
The above code assumes that any printed data will be in JSON format. First we connect to a Couchbase cluster, in this case a single node located on our local machine. By local machine I mean that in this scenario Couchbase and PHP are both operating on the same server.
Once a connection is established, we can open a particular Bucket which we’ll use for storing data within our application and reading data. The Bucket in this example is named default
and has no password.
This is where things get interesting:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
try { $result = $bucket->get("nraboy"); } catch (CouchbaseException $e) { $bucket->insert("nraboy", array( "firstname" => "Nic", "lastname" => "Raboy", "social_media" => array( "twitter" => "https://www.twitter.com/nraboy", "website" => "https://www.thepolyglotdeveloper.com" ) )); $result = $bucket->get("nraboy"); } |
First we try to get a particular document in our Bucket by the key name. If an error is thrown for reasons like no such key existing, we’ll catch it and attempt to create a new document with said key.
The data you insert can be however complex you’d like to make it. Within Couchbase it is stored as JSON and it is flexible, unlike the relational database alternatives.
After reading the data successfully, we can try to print it back to the client:
1 |
echo json_encode($result->value); |
If we wanted to, we could use other SDK commands such as replace
or remove
depending on our needs. However, most of the time we’re going to be working with massive amounts of data where we might not know all the keys to query.
This is where N1QL and PHP can come together.
Performing SQL Queries Against NoSQL With PHP and N1QL
If this is your first time hearing of N1QL, it is a SQL syntax that works with JSON data in Couchbase. This means that if you know SQL from a relational database like Postgres, you’ll know most of N1QL.
Let’s take the previous CRUD example, and get it prepared for N1QL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<?php header("Content-Type: application/json"); $bucketName = "default"; $cluster = new CouchbaseCluster("couchbase://localhost"); $bucket = $cluster->openBucket($bucketName, ""); try { $query = CouchbaseN1qlQuery::fromString("CREATE INDEX `people` ON `$bucketName` (lastname)"); $bucket->query($query); } catch (CouchbaseException $e) { printf("Couldn't create index. Maybe it already exists? (code: %d)\n", $e->getCode()); } $query = CouchbaseN1qlQuery::fromString("SELECT firstname, lastname, social_media FROM `$bucketName` WHERE lastname = \$lastname"); $query->namedParams(array("lastname" => "Raboy")); $result = $bucket->query($query); if(empty($result->rows)) { $data = array( "firstname" => "Nic", "lastname" => "Raboy", "social_media" => array( "twitter" => "https://www.twitter.com/nraboy", "website" => "https://www.thepolyglotdeveloper.com" ) ); $query = CouchbaseN1qlQuery::fromString("INSERT INTO `$bucketName` (KEY, VALUE) VALUES ('nraboy', " . json_encode($data) . ")"); $bucket->query($query); $query = CouchbaseN1qlQuery::fromString("SELECT firstname, lastname, social_media FROM `$bucketName` WHERE lastname = \$lastname"); $query->namedParams(array("lastname" => "Raboy")); $query->consistency(CouchbaseN1qlQuery::REQUEST_PLUS); $result = $bucket->query($query); } echo json_encode($result->rows); ?> |
Like with the previous example, we’re establishing a connection and opening a particular NoSQL Bucket. Because we plan to query the documents in the database by values other than their key, we need to create indexes.
1 2 3 4 5 6 |
try { $query = CouchbaseN1qlQuery::fromString("CREATE INDEX `people` ON `$bucketName` (lastname)"); $bucket->query($query); } catch (CouchbaseException $e) { printf("Couldn't create index. Maybe it already exists? (code: %d)\n", $e->getCode()); } |
The above creates an index that we’re calling people
that indexes the lastname
property of documents within the default
Bucket. This means that we’ll be able to query for all documents based on a lastname
value.
If the index already exists we’ll get an exception, which is why we’re trying to catch it.
With the index in place, we want to try to query for documents:
1 2 3 |
$query = CouchbaseN1qlQuery::fromString("SELECT firstname, lastname, social_media FROM `$bucketName` WHERE lastname = \$lastname"); $query->namedParams(array("lastname" => "Raboy")); $result = $bucket->query($query); |
In the above, we’re constructing a query for various properties within documents that meet the conditional logic of the WHERE
clause. If we didn’t want to define each property we could have easily used the asterisk character.
Like with a relational database, NoSQL databases can be victim to SQL injection attacks, which is why we’re using a parameterized query. This allows us to break data that is potentially user generated into parameters. This data might have been sent via a form.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if(empty($result->rows)) { $data = array( "firstname" => "Nic", "lastname" => "Raboy", "social_media" => array( "twitter" => "https://www.twitter.com/nraboy", "website" => "https://www.thepolyglotdeveloper.com" ) ); $query = CouchbaseN1qlQuery::fromString("INSERT INTO `$bucketName` (KEY, VALUE) VALUES ('nraboy', " . json_encode($data) . ")"); $bucket->query($query); $query = CouchbaseN1qlQuery::fromString("SELECT firstname, lastname, social_media FROM `$bucketName` WHERE lastname = \$lastname"); $query->namedParams(array("lastname" => "Raboy")); $query->consistency(CouchbaseN1qlQuery::REQUEST_PLUS); $result = $bucket->query($query); } |
If the results of the previous query come back as an empty array, it means no documents were found with that criteria. If the array is empty, we want to create new data similarly to how we did it in the previous example. This time we are creating data via a N1QL query and an INSERT
statement.
After the data is inserted we want to query for it. The catch here is that we’re querying based on an index and that index may not have been updated yet. To guarantee that we get the latest data back, we can set the query consistency and wait until the index has been updated.
Finally, we print the result to the screen.
Conclusion
You just got a quick introduction to using NoSQL and PHP example applications running on Couchbase. In the examples we saw, it was all CRUD or all N1QL. You are, by no means, obligated to go all in with one or the other. You can use CRUD and N1QL within the same application.
Docker works great for creating microservice containers using this example. To see how to containerize this application, check out the previous tutorial I wrote titled, Deploy a PHP with Couchbase Application as Docker Containers.
If you’d like more information on using the Couchbase PHP SDK, check out the Couchbase Developer Portal.