couchbase / docs-sdk-php

The PHP SDK documentation source files used in the new Couchbase Docs site.
https://docs.couchbase.com
Other
2 stars 14 forks source link

PCBC-863: Content suggestion: PHP SDK + Composer + Slim (web framework) #100

Open avsej opened 2 years ago

avsej commented 2 years ago

I've written a draft, which might be turned into blogpost or documentation page of some sort, or demo application.

Getting Started with Couchbase and Composer

In this small note, I demonstrate how to use Couchbase PHP SDK with Composer.

Here I assume, that composer command is available in the PATH. To check that, try to display its version:

$ composer --version
Composer version 2.2.7 2022-02-25 11:12:27

Now, lets create new project. Here I'm going to use [Slim framework][slim-home], as one of the many minimalistic PHP web framework avaialble in the ecosystem.

composer create-project slim/slim-skeleton couchbase-demo

This command will create new directory couchbase-demo with skeleton of the project.

Now navigate in the terminal to the directory and try to start webserver

php -S localhost:8080 -t public public/index.php

Default application will respond with "Hello world!" message:

$ curl http://localhost:8080
Hello world!

Lets add Couchbase SDK to the project. To do so, first we need to install the SDK in the system, SDK documentation describes it in details.

Once SDK installed, it should be displayed in the list of modules:

$ php -m | grep couchbase
couchbase

Next step is to update composer.json in the project.

diff --git i/composer.json w/composer.json
index b743a66..88e69da 100644
--- i/composer.json
+++ w/composer.json
@@ -24,6 +24,8 @@
     "require": {
         "php": "^7.4 || ^8.0",
         "ext-json": "*",
+        "ext-couchbase": "^4.0",
+        "couchbase/couchbase": "^4.0",
         "monolog/monolog": "^2.3",
         "php-di/php-di": "^6.3",
         "slim/psr7": "^1.5",

And run composer update to lock the change in composer.lock.

The default Slim skeleton uses InMemoryUserRepository as an implementation of the persistence. Lets replace it with class, that will keep user records in Couchbase.

Create src/Infrastructure/Persistence/User/CouchbaseUserRepository.php with the following content:

<?php
declare(strict_types=1);

namespace App\Infrastructure\Persistence\User;

use App\Domain\User\User;
use App\Domain\User\UserNotFoundException;
use App\Domain\User\UserRepository;
use Couchbase\Exception\DocumentNotFoundException;
use Couchbase\Scope;
use Couchbase\Collection;

class CouchbaseUserRepository implements UserRepository
{
    private Scope $scope;
    private Collection $collection;

    /**
     * @param Scope $scope
     */
    public function __construct(Scope $scope)
    {
        $this->scope = $scope;
        // we use default collection for brevity here, but it could be any other existing collection
        $this->collection = $this->scope->collection("_default");
    }

    /**
     * {@inheritdoc}
     */
    public function findAll(): array
    {
        $result = $this->scope->query('SELECT * FROM _default u ORDER BY id');
        return array_map(function ($row) {
            $document = $row['u'];
            return new User($document['id'], $document['username'], $document['firstName'], $document['lastName']);
        }, $result->rows());
    }

    /**
     * {@inheritdoc}
     */
    public function findUserOfId(int $id): User
    {
        try {
            $result = $this->collection->get(strval($id));
        } catch (DocumentNotFoundException $exception) {
            throw new UserNotFoundException();
        }
        $document = $result->content();
        return new User($document['id'], $document['username'], $document['firstName'], $document['lastName']);
    }
}

Couchbase is new dependency for the application, so it have to be registered in application container. I decided to register it as Couchbase\Scope::class, becase I'm going to keep all data for the application in the same scope.

Add new section to the app/settings.php

diff --git i/app/settings.php w/app/settings.php
index 016abd7..6e880d8 100644
--- i/app/settings.php
+++ w/app/settings.php
@@ -20,6 +20,12 @@ return function (ContainerBuilder $containerBuilder) {
                     'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
                     'level' => Logger::DEBUG,
                 ],
+                'couchbase' => [
+                    'connectionString' => 'couchbase://127.0.0.1',
+                    'username' => "Administrator",
+                    'password' => "password",
+                    'bucket' => "default",
+                ],
             ]);
         }
     ]);

Again, for simplicity in my case, I don't specify name of the scope here, because the repository is going to use default scope, but in real-world application, the scope name should be also set in the configuration.

Now, update app/dependencies.php, where we will create connection to Couchbase using credentials from the settings, and open default scope of the given bucket:

diff --git i/app/dependencies.php w/app/dependencies.php
index 9a948b5..05973cb 100644
--- i/app/dependencies.php
+++ w/app/dependencies.php
@@ -8,6 +8,7 @@ use Monolog\Logger;
 use Monolog\Processor\UidProcessor;
 use Psr\Container\ContainerInterface;
 use Psr\Log\LoggerInterface;
+use Couchbase\Scope;

 return function (ContainerBuilder $containerBuilder) {
     $containerBuilder->addDefinitions([
@@ -26,4 +27,20 @@ return function (ContainerBuilder $containerBuilder) {
             return $logger;
         },
     ]);
+
+    $containerBuilder->addDefinitions([
+        Scope::class => function (ContainerInterface $c) {
+            $settings = $c->get(SettingsInterface::class);
+
+            $couchbaseSettings = $settings->get('couchbase');
+
+            $clusterOptions = new Couchbase\ClusterOptions();
+            $clusterOptions->credentials($couchbaseSettings['username'], $couchbaseSettings['password']);
+
+            $cluster = new Couchbase\Cluster($couchbaseSettings['connectionString'], $clusterOptions);
+            $scope = $cluster->bucket($couchbaseSettings['bucket'])->defaultScope();
+
+            return $scope;
+        },
+    ]);
 };

And the last step is to replace in-memory implementation of the User repository with the one we mentioned previously:

diff --git i/app/repositories.php w/app/repositories.php
index 62a58f3..3bb7cbf 100644
--- i/app/repositories.php
+++ w/app/repositories.php
@@ -2,12 +2,12 @@
 declare(strict_types=1);

 use App\Domain\User\UserRepository;
-use App\Infrastructure\Persistence\User\InMemoryUserRepository;
+use App\Infrastructure\Persistence\User\CouchbaseUserRepository;
 use DI\ContainerBuilder;

 return function (ContainerBuilder $containerBuilder) {
-    // Here we map our UserRepository interface to its in memory implementation
+    // Here we map our UserRepository interface to its Couchbase implementation
     $containerBuilder->addDefinitions([
-        UserRepository::class => \DI\autowire(InMemoryUserRepository::class),
+        UserRepository::class => \DI\autowire(CouchbaseUserRepository::class)
     ]);
 };

That's all we need. Let's verify it now. Because the application provides read-only API, the users have to be created manually or by some other tool. The easiest way would be Couchbase Web UI. Create there couple for JSON documents like this:

id: "1" value:

{
    "id": 1,
    "username": "rick",
    "firstName": "Rick",
    "lastName": "Sanchez"
}

id: "2" value:

{
    "id": 2,
    "username": "morty",
    "firstName": "Morty",
    "lastName": "Smith"
}

Start the server:

$ composer start
> php -S localhost:8080 -t public
[Mon May 23 15:13:55 2022] PHP 8.0.19 Development Server (http://localhost:8080) started

Make few requests with cURL:

$ curl http://localhost:8080/users
{
    "statusCode": 200,
    "data": [
        {
            "id": 1,
            "username": "rick",
            "firstName": "Rick",
            "lastName": "Sanchez"
        },
        {
            "id": 2,
            "username": "morty",
            "firstName": "Morty",
            "lastName": "Smith"
        }
    ]
}
$ curl http://localhost:8080/users/1
{
    "statusCode": 200,
    "data": {
        "id": 1,
        "username": "rick",
        "firstName": "Rick",
        "lastName": "Sanchez"
    }
}
maria-robobug commented 2 years ago

Ah, so couchbase/couchbase 4.0 is now published on packagist: https://packagist.org/packages/couchbase/couchbase, so I can pull it with composer. This wasn't the case when we released, so probably explains why I couldn't pull it.

avsej commented 2 years ago

yeah, I had to hit update button there, also packagist only contains the pure php part, extension still have to be installed

avsej commented 1 month ago

@RichardSmedley this demo still works, maybe we could use it in the docs somehow?

demo https://asciinema.org/a/VHclkVxdIm4ykmd0eI5HcuLaN

RichardSmedley commented 1 month ago

I think turning it into a blog post is a great idea - and we can then link from both https://docs.couchbase.com/php-sdk/current/hello-world/start-using-sdk.html & https://docs.couchbase.com/php-sdk/current/hello-world/platform-help.html

Putting it in the docs directly would raise problems of maintenance, atm.