Skip to content

Commit

Permalink
Merge pull request #2 from BowlOfSoup/develop
Browse files Browse the repository at this point in the history
developer to master
  • Loading branch information
BowlOfSoup authored Feb 27, 2019
2 parents 3994ffc + 9dc991d commit d9c03c2
Show file tree
Hide file tree
Showing 11 changed files with 1,033 additions and 498 deletions.
139 changes: 121 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,166 @@
[![Build Status](https://travis-ci.org/BowlOfSoup/couchbase-access-layer.svg?branch=master)](https://travis-ci.org/BowlOfSoup/couchbase-access-layer)
[![Coverage Status](https://coveralls.io/repos/github/BowlOfSoup/couchbase-access-layer/badge.svg?branch=master)](https://coveralls.io/github/BowlOfSoup/couchbase-access-layer?branch=master)

* [Installation](#installation)
* [Usage](#usage)
- [Do use Parameters](#do-use-parameters)
- [The query builder supports the following N1QL clauses](#the-query-builder-supports-the-following-n1ql-clauses)
- [Examples](#examples)
- [Using a sub query in a FROM statement](#using-a-sub-query-in-a-from-statement)
* [Unit tests](#unit-tests)

Installation
------------
composer require bowlofsoup/couchbase-access-layer

Couchbase Access Layer
======================

A **simple** layer on top of the PHP Couchbase SDK which can help you to:
A **simple** layer on top of the PHP Couchbase SDK. Basically you get a _bucket_ repository class which acts as a layer between your code and Couchbase.

The repository helps you to:
- Quickly setup a Couchbase connection.
- A handy to use BucketRepository to quickly query a **single** bucket.
- Create queries with a so called 'Query Builder', this helps you build maintainable and easy to read N1ql queries and prevent injections.
- Create queries with a so called '**query builder**', this helps you build maintainable and easy to read N1ql queries.
- Processes the differences in result syntax you can get back from Couchbase into a consistent query result.
- See below for more examples!

Usage
-----

#### Do use Parameters

Important: When building a query, always try to use parameters.

Incorrect:

$queryBuilder
->where('data.creationDate >= ' . $creationDate);

Correct:

$queryBuilder
->where('data.creationDate >= $creationDate')
->setParameter('creationDate', '2019-01-10');

This will prevent injection.

#### The query builder supports the following N1QL clauses

- `SELECT` with optional `DISTINCT` and `RAW`
- `FROM` with optional alias, subquery also implemented (= mandatory alias)
- `USE` which means: `USE KEYS` and `USE INDEX`
- `WHERE`
- `GROUP BY`
- `ORDER BY`
- `LIMIT`
- `OFFSET`

Documentation for clauses can be found [On the Couchbase site](https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/selectintro.html).

#### Examples

<?php

declare(strict_types=1);

namespace Some\Name\Space;

use BowlOfSoup\CouchbaseAccessLayer\Exception\CouchbaseQueryException;
use BowlOfSoup\CouchbaseAccessLayer\Factory\ClusterFactory;
use BowlOfSoup\CouchbaseAccessLayer\Model\Result;
use BowlOfSoup\CouchbaseAccessLayer\Repository\BucketRepository;

class Foo
{
public function someExamples()
public function exampleUsingTheQueryBuilder()
{
$result = $this->querySomeBucket();

foreach ($result as $item) {
// each $item contains a Couchbase document which matched the query.
}

// $result implements the \JsonSerializableInterface.
$jsonEncoded = json_encode($result);

// These (can) only differ when query with limit/offset is done.
$resultCount = $result->getCount();
$resultTotalCount = $result->getTotalCount();

// Return just the data you queried
$justTheData = $result->get();

// Get only one (or the first) result
$singleResult = $result->getFirstResult();
}

public function exampleUsingDirectCalls()
{
$clusterFactory = new ClusterFactory('couchbaseHost', 'couchbaseUsername', 'couchbasePassword');
$bucketRepository = new BucketRepository('someBucketName', $clusterFactory);

$bucketRepository->upsert('some document id', 'the content of the document');

$bucketRepository->remove('some document id);

$documentContent = $bucketRepository->getByKey('some document id');
}

public function exampleExecutingManualQuery()
{
$clusterFactory = new ClusterFactory('couchbaseHost', 'couchbaseUsername', 'couchbasePassword');
$bucketRepository = new BucketRepository('someBucketName', $clusterFactory);

// Creating an index for a bucket
$bucketRepository->executeQuery(
CREATE INDEX i_foo_field_name
ON `' . $bucketRepository->getBucketName() . '`(`field`, `data.name`)
WHERE (`documentType` = "foo")
);

$result = $bucketRepository->executeQuery(
'SELECT someField FROM `bucket name` WHERE someOtherField = $someOtherField',
['someOtherField' => 'some value']
);

// This will only return one result.
$result = $bucketRepository->executeQueryWithOneResult();
}

/**
* @return \BowlOfSoup\CouchbaseAccessLayer\Model\Result
*/
private function querySomeBucket(): Result
{
$clusterFactory = new ClusterFactory('couchbaseHost', 'couchbaseUsername', 'couchbasePassword');
$bucketRepository = new BucketRepository('someBucketName', $clusterFactory);

$queryBuilder = $bucketRepository->createQueryBuilder();

$queryBuilder
->select('data.name')
// See that you can put in your own logic, like COUNT and such:
->select('COUNT(data.name) AS count')
->where('foo = $foo')
->where('someField = $someField')
->where('type = $type');

$queryBuilder->setParameters([
'foo' => 'someFooValue',
'someKey' => 'someKeyValue',
'type' => 'someDocumentType',
]);

$queryBuilder
->where('data.creationDate >= $creationDate')
->setParameter('creationDate', '2019-01-10');

$queryBuilder
->groupBy('data.name')
->limit(10)
->offset(20);

try {
return $bucketRepository->getResult($queryBuilder);
} catch (CouchbaseQueryException $e) {
Expand All @@ -90,6 +170,29 @@ Usage
}
}

For a `WHERE` clause you can put in logic yourself:

$queryBuilder->where('CONTAINS(SUBSTR(name,0,1),"C")');

For a `GROUP BY` clause you can put in logic yourself too:

$queryBuilder->groupBy('city LETTING MinimumThingsToSee = 400 HAVING COUNT(DISTINCT name) > MinimumThingsToSee');

#### Using a sub query in a FROM statement

When using a sub select in a `FROM` statement with `$queryBuilder->fromSubQuery()` and you're using parameters,
put the parameters in the 'master' query builder. More info on [this](https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/from.html) page.

$queryBuilderForSubSelect = new QueryBuilder('bucket_name');
$queryBuilderForSubSelect->where('type = $foo');

$queryBuilder = new QueryBuilder('bucket_name');

$queryBuilder
->select('q1.someFieldOfTheSubSelect')
->fromSubQuery($queryBuilderForSubSelect, 'q1')
->setParameter('foo', 'value1');

Unit tests
----------

Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<directory>src</directory>
<exclude>
<directory>src/Exception</directory>
<directory>src/Model</directory>
<directory>src/Model/Result.php</directory>
</exclude>
</whitelist>
</filter>
Expand Down
111 changes: 101 additions & 10 deletions src/Builder/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,91 @@
namespace BowlOfSoup\CouchbaseAccessLayer\Builder;

use BowlOfSoup\CouchbaseAccessLayer\Model\Query;
use Couchbase\Bucket;

class QueryBuilder
{
const RESULT_AS_ARRAY = true;

/** @var \Couchbase\Bucket */
private $bucket;

/** @var \BowlOfSoup\CouchbaseAccessLayer\Model\Query */
private $query;

/** @var array */
private $parameters = [];

/**
* @param \Couchbase\Bucket $bucket
* @param string|null $bucketName
*/
public function __construct(Bucket $bucket)
public function __construct(string $bucketName = null)
{
$this->bucket = $bucket;
$this->query = (new Query())->setFrom($bucket->getName());
$this->query = new Query();
if (null !== $bucketName) {
$this->query->setFrom($bucketName);
}
}

/**
* @param string $select
* @param bool $distinct
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function select(string $select, bool $distinct = false): self
{
$this->query->addSelect($select, $distinct);

return $this;
}

/**
* @param array $selects
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function selectMultiple(array $selects): self
{
foreach ($selects as $select) {
$this->query->addSelect($select);
}

return $this;
}

/**
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function selectRaw(): self
{
$this->query->setSelectRaw(true);

return $this;
}

/**
* @param string $from
* @param string|null $alias
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function from(string $from, string $alias = null): self
{
$this->query->setFrom($from, $alias);

return $this;
}

/**
* Because we can't get a parsed query (with parameters) the parameters for the sub query need to be on the 'master' query builder.
*
* @param \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder $queryBuilder
* @param string $alias
*
* @throws \BowlOfSoup\CouchbaseAccessLayer\Exception\CouchbaseQueryException
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function select(string $select): self
public function fromSubQuery(QueryBuilder $queryBuilder, string $alias): self
{
$this->query->addSelect($select);
$this->query->setFromWithSubQuery($queryBuilder, $alias);

return $this;
}
Expand Down Expand Up @@ -102,6 +155,44 @@ public function offset($offset): self
return $this;
}

/**
* @param string $index
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function useIndex(string $index): self
{
$this->query->setUseIndex($index);

return $this;
}

/**
* @param string $key
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function useKey(string $key): self
{
$this->query->addUseKey($key);

return $this;
}

/**
* @param array $keys
*
* @return \BowlOfSoup\CouchbaseAccessLayer\Builder\QueryBuilder
*/
public function useKeys(array $keys): self
{
foreach ($keys as $key) {
$this->query->addUseKey($key);
}

return $this;
}

/**
* @param string $parameter
* @param string|int $content
Expand Down
Loading

0 comments on commit d9c03c2

Please sign in to comment.