Getting Started w/ PHP on GCP

The “Missing Tutorials” series

Daz Wilkin
Google Cloud - Community
7 min readSep 23, 2017

--

Writing a short series of ‘getting started’ posts for those of you, like me, who may get to the point of wanting to write code against a Google service, having a language chosen but then, having not written code for a week or two, I’m stalled by “How exactly do I get started?”

PHP

PHP is a very popular language that is prevalent in server-side (web) scripting. I’ve encountered PHP several times but don’t recall ever having written much code with it. Facebook is possibly the most well-known PHP application and WordPress comes a close second.

It’s one of the 7 languages formally supported by Google on Google Cloud Platform (GCP).

Setup

I’m running Linux (Ubuntu 16.04). I assume there’s a package manager that’s (commonly-used) with PHP and, of course, a PHP runtime. To my knowledge neither of those is currently installed on my Linux machine. So, let’s start there.

PROJECT_ID=[[YOUR-PROJECT-ID]]
LANG=php
mkdir -p ${HOME}/${PROJECT_ID}/${LANG}
cd ${HOME}/${PROJECT_ID}/${LANG}

PHP

A little Googling got me to the PHP CLI and I installed this using:

https://getcomposer.org/doc/01-basic-usage.md

Composer

The Google API Client Library for PHP documentation refers to “composer.json” and that got me to getcomposer.org. The instructions are straightforward:

https://getcomposer.org/download/

And then:

php composer.phar   ______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 1.5.2 2017-09-11 16:59:25
Usage:
command [options] [arguments]

The Google document provides a snippet for the composer.json that needs to be wrapped in braces. My file contains:

{
"require": {
"google/apiclient": "^2.0"
}
}

And then:

php composer.phar install
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 12 installs, 0 updates, 0 removals
- Installing psr/http-message (1.0.1): Downloading (100%)
- Installing guzzlehttp/psr7 (1.4.2): Downloading (100%)
- Installing guzzlehttp/promises (v1.3.1): Downloading (100%)
- Installing guzzlehttp/guzzle (6.3.0): Downloading (100%)
- Installing phpseclib/phpseclib (2.0.6): Downloading (100%)
- Installing psr/log (1.0.2): Downloading (100%)
- Installing monolog/monolog (1.23.0): Downloading (100%)
- Installing firebase/php-jwt (v5.0.0): Downloading (100%)
- Installing google/apiclient-services (v0.26): Downloading (100%)
- Installing psr/cache (1.0.1): Downloading (100%)
- Installing google/auth (v1.0.1): Downloading (100%)
- Installing google/apiclient (v2.2.0): Downloading (100%)
...Writing lock file
Generating autoload files

yielding:

ls -lcomposer.json
composer.lock
composer.phar
vendor

and:

ls -l vendor

autoload.php
composer
firebase
google
guzzlehttp
monolog
phpseclib
psr

OK. That doesn’t look wrong ;-) Let’s continue and see what happens…

Application Default Credentials

One, consistent recommendation in this series is that, if you can, you should use Application Default Credentials (ADCs). The page contains instructions for using ADCs with each of the Google-support programming languages. Here’s the link for PHP:

https://developers.google.com/identity/protocols/application-default-credentials#callingphp

And the code:

$client = new Google_Client();
$client->useApplicationDefaultCredentials();

Google Cloud Storage

With the majority of the other posts in this series, I’ve been using Google Cloud Storage (GCS) and I will use that service with PHP too.

In order to facilitate writing code that operates on GCS Buckets and Objects, the following script will create (hopefully) a uniquely named bucket for you and populate it with 10 differently named but identical content images. Please set $FILE to a value that paths to a small file that you wish to use as a template.

BUCKET=$(whoami)-$(date +%y%m%d%H%M)
FILE=[[/Path/To/Your/File]]
gsutil mb -p ${PROJECT_ID} gs://${BUCKET}
Creating gs://${BUCKET}/...
for i in $(seq -f "%02g" 1 10)
do
gsutil cp $FILE gs://${BUCKET}/${i}
done
gsutil ls gs://${BUCKET}
gs://${BUCKET}/01
gs://${BUCKET}/02
gs://${BUCKET}/03
gs://${BUCKET}/04
gs://${BUCKET}/05
gs://${BUCKET}/06
gs://${BUCKET}/07
gs://${BUCKET}/08
gs://${BUCKET}/09
gs://${BUCKET}/10

Solution #1: Using API Client Libraries

The cloud.google.com documentation for PHP runs out of steam at this point :-( The API Client Libraries are machine-generated from the Discovery documents that define each of Google’s services. As a result the Libraries closely mirror the service APIs and, as a the PHP documentation encourages, it’s a good practice to use Google API Explorer as a way to infer how to use the Library. Given that the documentation for PHP is so sparse, API Explorer is pretty much all we have. Unfortunately, even with API Explorer for GCS, it’s still difficult to determine the PHP code:

Cloud Storage JSON API V1
Calling storage.buckets.list

As you’ll see below, it’s easy to determine that we’ll use storage.buckets.list to list the buckets and storage.objects.list to list the objects. But, these commands are mapped directly to storage->buckets->list rather storage->buckets->listBuckets. So, back to the documentation.

Knowing that Google open-sources the libraries on GitHub, I found:

https://github.com/google/google-api-php-client

I then Googled (at random) a guess on the service name (Google_Service_Storage) and found this:

...
$storage = new Google_Service_Storage($client);
...

And then I found (seriously is this way to learn this Library?) the following solution on StackOverflow:

...
$object = $service->objects->listObjects(BUCKET, OBJECT);
...

So, I began exploring to see what I could get working:

define("BUCKET_NAME", "[[BUCKET]]");$request = $service->objects->listObjects(BUCKET_NAME);
print_r($request);

This results in the following (redacted):

Google_Service_Storage_Objects Object
(
[itemsType:protected] => Google_Service_Storage_StorageObject
[itemsDataType:protected] => array
[kind] => storage#objects
[nextPageToken] =>
[prefixes] =>
[internal_gapi_mappings:protected] => Array
(
)
[items] => Array
(
[0] => Google_Service_Storage_StorageObject Object
(
[bucket] => [[BUCKET]]
[id] => [[BUCKET]]/01/1234567890123456
[kind] => storage#object
[name] => 01
[size] => 22976
...
[1] => Google_Service_Storage_StorageObject Object
(
[bucket] => [[BUCKET]]
[id] => [[BUCKET]]/02/1234567890123456
[kind] => storage#object
[name] => 02
[size] => 22976
...

And the “items” array correctly represents the 10 objects that I uploaded to the bucket. So then, further Google got me the way to iterate over the array and grab the “name” property:

foreach ($request["items"] as $object)
printf("%s\n", $object["name"]);

But, it turns out, there are getters for these properties and so — I suspect — the better way to do this is:

foreach ($request["items"] as $object)
printf("%s\n", $object->getName());

Which results (correctly) in:

01
02
03
04
05
06
07
08
09
10

Haven’t been to this rodeo once before, I guessed that the methods would be similar for enumerating buckets ($service →buckets) but incorrectly initially guessed the method (listObjects) and then realized this needs to be:

define("PROJECT_ID", "dazwilkin-170828-medium");$request = $service->buckets->listBuckets(PROJECT_ID);
print_r($request);

resulting in:

Google_Service_Storage_Buckets Object
(
[collection_key:protected] => items
[itemsType:protected] => Google_Service_Storage_Bucket
[itemsDataType:protected] => array
[kind] => storage#buckets
[nextPageToken] =>
...
[items] => Array
(
[0] => Google_Service_Storage_Bucket Object
(
[id] => [[BUCKET]]
[kind] => storage#bucket
[labels] =>
[location] => US
[name] => [[BUCKET]]
[storageClass] => STANDARD
[timeCreated] => 2017-08-31T00:00:00.000Z
[updated] => 2017-08-31T00:00:00.000Z
...

and so it was quick to get to:

foreach ($request["items"] as $bucket)
printf("%s\n", $bucket->getName());

Putting this together:

Running the solution:

php cloudstorage.php[[BUCKET]]
01
02
03
04
05
06
07
08
09
10

This was a rather disappointing experience. Let’s hope the Cloud Client Libraries are better documented!

PS I discovered Packagist:

Packagist: google/apiclient

Solution #2: Using Cloud Client Libraries

Using the Cloud Client Libraries with PHP looks more promising. Cloud Storage includes a PHP sample that provides much of what we need. I’ve also now discovered Packagist too:

Packagist: google/cloud
Packagist: google/cloud-storage

And, this show that it depends upon google/cloud-core, so I assume we can just reference google/cloud-storage (NB the version is v.1.1.5) and we’ll be good:

composer.json:

{
"require": {
"google/cloud-storage": "^1.1.5"
}
}

Then:

php composer.phar install
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 12 installs, 0 updates, 0 removals
- Installing psr/http-message (1.0.1): Loading from cache
- Installing psr/log (1.0.2): Loading from cache
- Installing monolog/monolog (1.23.0): Loading from cache
- Installing guzzlehttp/psr7 (1.4.2): Loading from cache
- Installing guzzlehttp/promises (v1.3.1): Loading from cache
- Installing guzzlehttp/guzzle (6.3.0): Loading from cache
- Installing psr/cache (1.0.1): Loading from cache
- Installing firebase/php-jwt (v5.0.0): Loading from cache
- Installing google/auth (v1.0.1): Loading from cache
- Installing rize/uri-template (0.3.2): Downloading (100%)
- Installing google/cloud-core (v1.9.0): Downloading (100%)
- Installing google/cloud-storage (v1.1.5): Downloading (100%)
...Writing lock file
Generating autoload files

While Application Default Credentials is supported with this Library, the implementation differs. This works for me:

define("PROJECT_ID", [[YOUR-PROJECT-ID]]);$gcloud = new ServiceBuilder([
"projectId" => PROJECT_ID
]);

API Documentation !! :-)

Specifically for Cloud Storage. Makes life *much* easier. You can see from the documentation that a StorageClient object has a “buckets” method and this provides exactly the code that we will need:

$buckets = $storage->buckets();
foreach ($buckets as $bucket) {
printf("%s\n", $bucket->getName());
}

We can guess the code to enumerate the objects but, let’s review the documentation. Such an improvement!

$bucket = $storage->bucket(BUCKET);
$objects = $bucket->objects();
foreach ($objects as $object) {
printf("%s\n", $object->name());
}

Putting it altogether:

Running the solution:

php cloudstorage.php[[BUCKET]]
01
02
03
04
05
06
07
08
09
10

Tidy-up

You may delete the bucket (and all of its objects) when you’re done. Be very careful to specify the correct bucket name as the operation is irrevocable.

gsutil rm -r gs://${BUCKET}

Conclusion

Unless you’re maintaining existing code *or* you want consistency across multiple Google services and one or more of these services does *not* have a Cloud Client Library, I recommend you to strongly consider using the Cloud Client Libraries for PHP. The API Client Libraries, although comprehensive, are poorly documented and this makes development challenging.

The Cloud Client Libraries for PHP are great!

--

--