ravendb/ravendb-php-client

RavenDB PHP 客户端

5.2.6 2023-12-06 19:25 UTC

This package is not auto-updated.

Last update: 2024-09-11 22:58:34 UTC


README

安装

您可以通过 Composer 将库安装到项目中

$ composer require ravendb/ravendb-php-client

版本发布

  • 所有 5.2.x 版本的客户端完全兼容并支持 RavenDB 服务器 5.4 和 6.0 版本。

  • 点击此处 查看所有版本和更新日志。

文档

本说明提供了以下简短示例

入门,
CRUD 示例,
查询文档,
附件,
时间序列,
修订,
建议,
修复,
使用类,
PHP 使用,
与安全服务器协同工作,
运行测试

有关更多信息,请访问在线 RavenDB 文档

有关如何使用 RavenDBLaravel 的更多信息,请查看 Raven Laravel 示例应用程序

入门

  1. 从 ravendb 包中引入 DocumentStore
use RavenDB\Documents\DocumentStore;
  1. 初始化文档存储(每个应用程序应有一个 DocumentStore 实例)
    $store = new DocumentStore('http://live-test.ravendb.net', 'databaseName');
    $store->initialize();
  1. 打开一个会话
    $session = $store->openSession();
  1. 完成时调用 saveChanges()
    $user = $session->load('users/1-A'); // Load document
    $user->setPassword(PBKDF2('new password')); // Update data
    
    $session->saveChanges(); // Save changes
    // Data is now persisted
    // You can proceed e.g. finish web request
    

CRUD 示例

存储文档

$product = new Product();
$product->setTitle("iPhone X");
$product->setPrice(999.99);
$product->setCurrency("USD");
$product->setStorage(64);
$product->setManufacturer("Apple");
$product->setInStock(true);

$session->store($product, 'products/1-A');
echo $product->id; // products/1-A

$session->saveChanges();
相关测试

在相同会话中存储具有相同 ID 的文档应引发异常

加载文档

$product = $session->load(Product::class, 'products/1-A');
echo $product->getTitle(); // iPhone X
echo $product->getId();    // products/1-A

加载带有包含的文档

// users/1
// {
//      "name": "John",
//      "kids": ["users/2", "users/3"]
// }

$session = $store->openSession();

try {
    $user1 = $session
        ->include("kids")
        ->load("users/1");
        // Document users/1 and all docs referenced in "kids"
        // will be fetched from the server in a single request.

    $user2 = $session->load("users/2"); // this won't call server again

    $this->assertNotNull($user1);
    $this->assertNotNull($user2);
    $this->assertEqual(1, $session->advanced()->getNumberOfRequests());
} finally {
    $session->close();
}
相关测试

可以包含加载

更新文档

$product = $session->load(Product::class, 'products/1-A');
$product->setInStock(false);
$product->setLastUpdate(new Date());
$session->saveChanges();
// ...
$product = $session->load(Product::class, 'products/1-A');
echo $product->getInStock();    // false
echo $product->getLastUpdate(); // the current date

删除文档

  1. 使用实体
$product = $session->load('products/1-A');
$session->delete($product);
$session->saveChanges();

$product = $session->load('products/1-A');
$this->assertNull($product); // null
  1. 使用文档 ID
$session->delete('products/1-A');
相关测试

按实体删除文档
按 ID 删除文档
在按 ID 删除之前调用 onBeforeDelete
不能删除未跟踪的实体
加载已删除的文档返回 null

查询文档

  1. 使用 query() 会话方法

按集合查询

$query = $session->query(Product::class, Query::collection('products'));

按索引名称查询

$query = $session->query(Product::class, Query::indexName('productsByCategory'));

按索引查询

$query = $session->query(Product::class, Products_ByCategory::class);

按实体类型查询

$query = $session->query(Product::class);
  1. 构建查询 - 应用搜索条件,设置排序等。
    查询支持链式调用
$query
    ->waitForNonStaleResults()
    ->usingDefaultOperator('AND') 
    ->whereEquals('manufacturer', 'Apple')
    ->whereEquals('in_stock', true)
    ->whereBetween('last_update', new DateTime('- 1 week'), new DateTime())
    ->orderBy('price');
  1. 执行查询以获取结果
$results = $query->toList(); // get all results
// ...
$firstResult = $query->first(); // gets first result
// ...
$single = $query->single();  // gets single result 

查询方法概述

selectFields() - 使用单个字段进行投影

// RQL
// from users select name

// Query
$userNames = $session->query(User::class)
    ->selectFields("name")
    ->toList();

// Sample results
// John, Stefanie, Thomas
相关测试

查询单个属性

可以投影 ID 字段

selectFields() - 使用多个字段进行投影

// RQL
// from users select name, age

// Query
$session->query(User::class)
    ->selectFields([ "name", "age" ])
    ->toList();

// Sample results
// [ [ name: 'John', age: 30 ],
//   [ name: 'Stefanie', age: 25 ],
//   [ name: 'Thomas', age: 25 ] ]
相关测试

带有投影的查询
可以投影 ID 字段

distinct()

// RQL
// from users select distinct age

// Query
$session->query(User::class)
    ->selectFields("age")
    ->distinct()
    ->toList();

// Sample results
// [ 30, 25 ]
相关测试

查询 distinct

whereEquals() / whereNotEquals()

// RQL
// from users where age = 30 

// Query
$session->query(User::class)
    ->whereEquals("age", 30)
    ->toList();

// Sample results
// [ User {
//    name: 'John',
//    age: 30,
//    kids: [...],
//    registeredAt: 2017-11-10T23:00:00.000Z } ]
相关测试

等于
不等于

whereIn()

// RQL
// from users where name in ("John", "Thomas")

// Query
$session->query(User::class)
    ->whereIn("name", ["John", "Thomas"])
    ->toList();

// Sample results
// [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [...],
//     id: 'users/1-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
相关测试

带有 where in 的查询

whereStartsWith() / whereEndsWith()

// RQL
// from users where startsWith(name, 'J')

// Query
$session->query(User::class)
    ->whereStartsWith("name", "J")
    ->toList();

// Sample results
// [ User {
//    name: 'John',
//    age: 30,
//    kids: [...],
//    registeredAt: 2017-11-10T23:00:00.000Z } ]
相关测试

带有 where 子句的查询

whereBetween()

// RQL
// from users where registeredAt between '2016-01-01' and '2017-01-01'

// Query
$session->query({ collection: "users" })
    ->whereBetween("registeredAt", DateTime::createFromFormat('Y-m-d', '2016-01-01'), DateTime::createFromFormat('Y-m-d', '2017-01-01'))
    ->toList();

// Sample results
// [ User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
相关测试

带有 where between 的查询

whereGreaterThan() / whereGreaterThanOrEqual() / whereLessThan() / whereLessThanOrEqual()

// RQL
// from users where age > 29

// Query
$session->query(User::class)
    ->whereGreaterThan("age", 29)
    ->toList();

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: [...],
//   id: 'users/1-A' } ]
相关测试

带有 where less than 的查询
带有 where less than or equal 的查询
带有 where greater than 的查询
带有 where greater than or equal 的查询

whereExists()

检查字段是否存在。

// RQL
// from users where exists("age")

// Query
$session->query(User::class)
    ->whereExists("kids")
    ->toList();

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: [...],
//   id: 'users/1-A' } ]
相关测试

带有 where exists 的查询

containsAny() / containsAll()

// RQL
// from users where kids in ('Mara')

// Query
$session->query(User::class)
    ->containsAll("kids", ["Mara", "Dmitri"])
    ->toList();

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: ["Dmitri", "Mara"]
//   id: 'users/1-A' } ]
相关测试

带有 contains 的查询

search()

执行全文搜索。

// RQL
// from users where search(kids, 'Mara')

// Query
$session->query(User::class)
    ->search("kids", "Mara Dmitri")
    ->toList();

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: ["Dmitri", "Mara"]
//   id: 'users/1-A' } ]
相关测试

带有 or 的查询 search
query_CreateClausesForQueryDynamicallyWithOnBeforeQueryEvent

openSubclause() / closeSubclause()

// RQL
// from users where exists(kids) or (age = 25 and name != Thomas)

// Query
$session->query(User::class)
    ->whereExists("kids")
    ->orElse()
    ->openSubclause()
        ->whereEquals("age", 25)
        ->whereNotEquals("name", "Thomas")
    ->closeSubclause()
    ->toList();

// Sample results
// [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: ["Dmitri", "Mara"]
//     id: 'users/1-A' },
//   User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' } ]
相关测试

处理子句

not()

// RQL
// from users where age != 25

// Query
$session->query(User::class)
    ->not()
    ->whereEquals("age", 25)
    ->toList();

// Sample results
// [ User {
//   name: 'John',
//   age: 30,
//   registeredAt: 2017-11-10T23:00:00.000Z,
//   kids: ["Dmitri", "Mara"]
//   id: 'users/1-A' } ]
相关测试

查询 where not

orElse() / andAlso()

// RQL
// from users where exists(kids) or age < 30

// Query
$session->query(User::class)
    ->whereExists("kids")
    ->orElse()
    ->whereLessThan("age", 30)
    ->toList();

// Sample results
//  [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [ 'Dmitri', 'Mara' ],
//     id: 'users/1-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' },
//   User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' } ]
相关测试

处理子句

usingDefaultOperator()

如果没有调用 andAlso()orElse(),则查询过滤条件之间的默认运算符将是 AND
您可以使用usingDefaultOperator来覆盖它,该函数必须在任何其他where条件之前调用。

// RQL
// from users where exists(kids) or age < 29

// Query
$session->query(User::class)
    ->usingDefaultOperator("OR") // override the default 'AND' operator
    ->whereExists("kids")
    ->whereLessThan("age", 29)
    ->toList();

// Sample results
//  [ User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [ 'Dmitri', 'Mara' ],
//     id: 'users/1-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' },
//   User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' } ]

orderBy() / orderByDesc() / orderByScore() / randomOrdering()

// RQL
// from users order by age

// Query
$session->query(User::class)
    ->orderBy("age")
    ->toList();

// Sample results
// [ User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' },
//   User {
//     name: 'John',
//     age: 30,
//     registeredAt: 2017-11-10T23:00:00.000Z,
//     kids: [ 'Dmitri', 'Mara' ],
//     id: 'users/1-A' } ]
相关测试

查询随机排序
按字母数字排序
带boost的查询 - 按分数排序

take()

限制查询结果数量。

// RQL
// from users order by age

// Query
$session->query(User::class)
    ->orderBy("age") 
    ->take(2) // only the first 2 entries will be returned
    ->toList();

// Sample results
// [ User {
//     name: 'Stefanie',
//     age: 25,
//     registeredAt: 2015-07-29T22:00:00.000Z,
//     id: 'users/2-A' },
//   User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
相关测试

查询跳过take

skip()

从开始位置跳过指定数量的结果。

// RQL
// from users order by age

// Query
$session->query(User::class)
    ->orderBy("age") 
    ->take(1) // return only 1 result
    ->skip(1) // skip the first result, return the second result
    ->toList();

// Sample results
// [ User {
//     name: 'Thomas',
//     age: 25,
//     registeredAt: 2016-04-24T22:00:00.000Z,
//     id: 'users/3-A' } ]
相关测试

查询跳过take

获取查询统计信息

使用statistics()方法获取查询统计信息。

// Query
$stats = new QueryStatistics();
$results = $session->query(User::class)
    ->whereGreaterThan("age", 29)
    ->statistics($stats)
    ->toList();

// Sample results
// QueryStatistics {
//   isStale: false,
//   durationInMs: 744,
//   totalResults: 1,
//   skippedResults: 0,
//   timestamp: 2018-09-24T05:34:15.260Z,
//   indexName: 'Auto/users/Byage',
//   indexTimestamp: 2018-09-24T05:34:15.260Z,
//   lastQueryTime: 2018-09-24T05:34:15.260Z,
//   resultEtag: 8426908718162809000 }

all() / first() / single() / count()

all() - 返回所有结果

first() - 只返回第一个结果

single() - 返回第一个结果,如果有多条记录将抛出错误

count() - 返回结果中的条目数(不受take()的影响)

相关测试

查询第一个和单个
查询计数

附件

存储附件

$doc = new User();
$doc->setName('John');

// Store a document, the entity will be tracked.
$session->store($doc);

// Get read stream or buffer to store
$fileStream = file_get_contents("../photo.png");

// Store attachment using entity
$session->advanced()->attachments()->store($doc, "photo.png", $fileStream, "image/png");

// OR store attachment using document ID
$session->advanced()->attachments()->store($doc->getId(), "photo.png", $fileStream, "image/png");

// Persist all changes
$session->saveChanges();
相关测试

可以放置附件

获取附件

// Get an attachment
$attachment = $session->advanced()->attachments()->get($documentId, "photo.png")

// Attachment.details contains information about the attachment:
//     { 
//       name: 'photo.png',
//       documentId: 'users/1-A',
//       contentType: 'image/png',
//       hash: 'MvUEcrFHSVDts5ZQv2bQ3r9RwtynqnyJzIbNYzu1ZXk=',
//       changeVector: '"A:3-K5TR36dafUC98AItzIa6ow"',
//       size: 4579 
//     }

// Attachment.data is a Readable.
$fileBytes = $attachment->getData();
file_put_contents('../photo.png', $fileBytes);
相关测试

可以获取和删除附件

检查附件是否存在

$session->advanced()->attachments()->exists($doc->getId(), "photo.png");
// true

$session->advanced()->attachments()->exists($doc->getId(), "not_there.avi");
// false
相关测试

附件存在 2

获取附件名称

// Use a loaded entity to determine attachments' names
$session->advanced()->attachments()->getNames($doc);

// Sample results:
// [ { name: 'photo.png',
//     hash: 'MvUEcrFHSVDts5ZQv2bQ3r9RwtynqnyJzIbNYzu1ZXk=',
//     contentType: 'image/png',
//     size: 4579 } ]
相关测试

获取附件名称 2

时间序列

存储时间序列

$session = $store->openSession();

// Create a document with time series
$session->store(new User(), "users/1");
$tsf = $session->timeSeriesFor("users/1", "heartbeat");

// Append a new time series entry
$tsf->append(new DateTime(), 120);

$session->saveChanges();
相关测试

canCreateSimpleTimeSeries
使用不同的标签
可以存储和读取多个时间戳
可以存储大量值
应在删除文档时删除时间序列

获取文档的时间序列

$session = $store->openSession();

// Get time series for document by time series name
$tsf = $session->timeSeriesFor("users/1", "heartbeat");

// Get all time series entries
$heartbeats = $tsf->get();
相关测试

canCreateSimpleTimeSeries
可以存储大量值
可以请求不存在的时间序列范围
可以获取时间序列名称2
可以跳过和获取时间序列

修订

注意:在尝试以下操作之前,请确保已启用修订。

$user = new User();
$user->setName("Marcin");
$user->setAge(30);
$user->setPet("Cat");

$session = $store->openSession();

// Store a document
$session->store($user, "users/1");
$session->saveChanges();

// Modify the document to create a new revision
$user->setName("Roman");
$user->setAge(40);
$session->saveChanges();

// Get revisions
$revisions = $session->advanced()->revisions()->getFor("users/1");

// Sample results:
// [ { name: 'Roman',
//     age: 40,
//     pet: 'Cat',
//     '@metadata': [Object],
//     id: 'users/1' },
//   { name: 'Marcin',
//     age: 30,
//     pet: 'Cat',
//     '@metadata': [Object],
//     id: 'users/1' }
// ]

建议

为类似/拼写错误的术语建议选项

// Some documents in users collection with misspelled name term
// [ User {
//     name: 'Johne',
//     age: 30,
//     ...
//     id: 'users/1-A' },
//   User {
//     name: 'Johm',
//     age: 31,
//     ...
//     id: 'users/2-A' },
//   User {
//     name: 'Jon',
//     age: 32,
//     ...
//     id: 'users/3-A' },
// ]

// Static index definition
class UsersIndex extends AbstractJavaScriptIndexCreationTask {
    public function __construct() {
        parent::__construct();
                
        $this->map = "from user in docs.users select new { user.name }";
        
        // Enable the suggestion feature on index-field 'name'
        $this->suggestion("name"); 
    }
}

// ...
$session = $store->openSession();

// Query for similar terms to 'John'
// Note: the term 'John' itself will Not be part of the results

$suggestedNameTerms = $session->query(User::class, UsersIndex::class)
    ->suggestUsing(function($x) { return $x->byField("name", "John"); }) 
    ->execute();

// Sample results:
// { name: { name: 'name', suggestions: [ 'johne', 'johm', 'jon' ] } }

高级修补

// Increment 'age' field by 1
$session->advanced()->increment("users/1", "age", 1);

// Set 'underAge' field to false
$session->advanced->patch("users/1", "underAge", false);

$session->saveChanges();
相关测试

可以修补
可以修补复杂
可以添加到数组
可以递增
修补将在保存更改后更新跟踪文档
可以修补单个文档

使用类表示实体

  1. 将您的模型定义为类。属性应该是公共属性
class Product {

    public ?string $id = null,
    public string $title = '',
    public int $price = 0,
    public string $currency = 'USD',
    public int $storage = 0,
    public string $manufacturer = '',
    public bool $in_stock = false,
    public ?DateTime $last_update = null

    public function __construct(
        $id = null,
        $title = '',
        $price = 0,
        $currency = 'USD',
        $storage = 0,
        $manufacturer = '',
        $in_stock = false,
        $last_update = null
    ) {
        $this->id = $id;
        $this->title = $title;
        $this->price = $price;
        $this->currency = $currency;
        $this->storage = $storage;
        $this->manufacturer = $manufacturer;
        $this->in_stock = $in_stock;
        $this->last_update = $last_update ?? new DateTime();
    }
}
  1. 要存储文档,请将其实例传递给store()
    集合名称将自动从实体的类名称中检测到。
use models\Product;

$product = new Product(
  null, 'iPhone X', 999.99, 'USD', 64, 'Apple', true, new Date('2017-10-01T00:00:00'));

$product = $session->store($product);

var_dump($product instanceof Product);                // true
var_dump(str_starts_with($product->id, 'products/')); // true

$session->saveChanges();
  1. 加载数据文档
$product = $session->load('products/1-A');
var_dump($product instanceof Product); // true
var_dump($product->id);                // products/1-A
  1. 查询文档
$products = $session->query(Product::class)->toList();

foreach($products as $product) {
  var_dump($product instanceof Product);                // true
  var_dump(str_starts_with($product->id, 'products/')); // true
});

与PHP的使用

PHP类型已嵌入到包中。确保在使用完毕后关闭会话。

// file models/product.php
class Product {
    public ?string $id = null,
    public string $title = '',
    public int $price = 0,
    public string $currency = 'USD',
    public int $storage = 0,
    public string $manufacturer = '',
    public bool $in_stock = false,
    public ?DateTime $last_update = null
    
    public function __construct(
        $id = null,
        $title = '',
        $price = 0,
        $currency = 'USD',
        $storage = 0,
        $manufacturer = '',
        $in_stock = false,
        $last_update = null
    ) {
        $this->id = $id;
        $this->title = $title;
        $this->price = $price;
        $this->currency = $currency;
        $this->storage = $storage;
        $this->manufacturer = $manufacturer;
        $this->in_stock = $in_stock;
        $this->last_update = $last_update ?? new DateTime();
    }
}

// file app.php
use models\Product;
use RavenDB\Documents\DocumentStore;
use RavenDB\Documents\Session\DocumentSession;

$store = new DocumentStore('url', 'database name');
try {
    $store->initialize();
    
    $productId = null;
    
    /** @var DocumentSession $session */
    $session = $store->openSession();
    try {
        $product = new Product(
          null, 'iPhone X', 999.99, 'USD', 64, 'Apple', true, new Date('2017-10-01T00:00:00'));

        $session->store($product);
        $session->saveChanges();
        
        var_dump($product instanceof Product);                // true
        var_dump(str_starts_with($product->id, 'products/')); // true
        
        $productId = $product->id;
    } finally {
        $session->close();    
    }
    
    $session = $store->openSession();
    try {
        /** @var Product $product */
        $product = $session->load(Product::class, $productId);
        
        var_dump($product instanceof Product);                // true
        var_dump($product->id); // products/1-A
        
        /** @var array<Product> $products */
        $products = $session->query(Query::collection('Products'))
                    ->waitForNonStaleResults()
                    ->whereEquals('manufacturer', 'Apple')
                    ->whereEquals('in_stock', true)
                    ->whereBetween('last_update', new DateTime('- 1 week'), new DateTime())
                    ->whereGreaterThanOrEqual('storage', 64)
                    ->toList();
    
        foreach ($products as $product) {
            var_dump($product instanceof Product);                // true
            var_dump(str_starts_with($product->id, 'products/')); // true
        }
       
    } finally {
        $session->close();    
    }
    
} finally {
    $store->close();
}

与安全服务器一起工作

您的证书和服务器证书应保存为PEM格式到您的机器。

  1. 创建AuthOptions
$authOptions = AuthOptions::pem(
    '../clientCertPath.pem',
    'clientCertPass',
    '../serverCaCertPath.pem'
);
  1. 将认证选项传递给DocumentStore对象
$store = new DocumentStore('url', 'databaseName');
$store->setAuthOptions($authOptions); // use auth options to connect on database
$store->initialize();

运行测试

克隆存储库

git clone https://github.com/ravendb/ravendb-php-client

安装依赖项

composer install

运行RavenDB服务器

https://a.phptest.development.run

设置环境变量。

# Set the following environment variables:
#
# - Certificate hostname
# RAVENDB_PHP_TEST_HTTPS_SERVER_URL=https://a.phptest.development.run
#
# RAVENDB_PHP_TEST_CA_PATH=
#
# - Certificate path for tests requiring a secure server:
# RAVENDB_PHP_TEST_CERTIFICATE_PATH=
#
# - Certificate for client
# RAVENDB_TEST_CLIENT_CERT_PATH=
# RAVENDB_TEST_CLIENT_CERT_PASSPHRASE=
#
# - For some tests, Developers licence is required in order to run them all 
# RAVEN_LICENSE=

运行PHPUnit

./vendor/bin/phpunit
错误跟踪器

http://issues.hibernatingrhinos.com/issues/RDBC

许可

MIT许可(MIT)。有关更多信息,请参阅许可文件