296 lines
8.2 KiB
PHP
296 lines
8.2 KiB
PHP
<?php
|
|
|
|
namespace KTXC\Db;
|
|
|
|
use MongoDB\Collection as MongoCollection;
|
|
use MongoDB\InsertOneResult;
|
|
use MongoDB\UpdateResult;
|
|
use MongoDB\DeleteResult;
|
|
|
|
/**
|
|
* Wrapper for MongoDB\Collection
|
|
* Provides abstraction layer for MongoDB collection operations
|
|
*/
|
|
class Collection
|
|
{
|
|
private MongoCollection $collection;
|
|
|
|
public function __construct(MongoCollection $collection)
|
|
{
|
|
$this->collection = $collection;
|
|
|
|
// Set type map to return plain arrays instead of objects
|
|
// This converts BSON types to PHP native types
|
|
$this->collection = $collection->withOptions([
|
|
'typeMap' => [
|
|
'root' => 'array',
|
|
'document' => 'array',
|
|
'array' => 'array'
|
|
]
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Find documents in the collection
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $options Query options
|
|
* @return Cursor
|
|
*/
|
|
public function find(array $filter = [], array $options = []): Cursor
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
/** @var \Iterator $cursor */
|
|
$cursor = $this->collection->find($filter, $options);
|
|
return new Cursor($cursor);
|
|
}
|
|
|
|
/**
|
|
* Find a single document
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $options Query options
|
|
* @return array|null Returns array with _id as string
|
|
*/
|
|
public function findOne(array $filter = [], array $options = []): ?array
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
$result = $this->collection->findOne($filter, $options);
|
|
|
|
if ($result === null) {
|
|
return null;
|
|
}
|
|
|
|
// Convert to array if it's an object
|
|
if (is_object($result)) {
|
|
$result = (array) $result;
|
|
}
|
|
|
|
return $this->convertBsonToNative($result);
|
|
}
|
|
|
|
/**
|
|
* Insert a single document
|
|
*
|
|
* @param array|object $document Document to insert
|
|
* @param array $options Insert options
|
|
* @return InsertOneResult
|
|
*/
|
|
public function insertOne(array|object $document, array $options = []): InsertOneResult
|
|
{
|
|
$document = $this->convertDocument($document);
|
|
return $this->collection->insertOne($document, $options);
|
|
}
|
|
|
|
/**
|
|
* Insert multiple documents
|
|
*
|
|
* @param array $documents Documents to insert
|
|
* @param array $options Insert options
|
|
*/
|
|
public function insertMany(array $documents, array $options = []): mixed
|
|
{
|
|
$documents = array_map(fn($doc) => $this->convertDocument($doc), $documents);
|
|
return $this->collection->insertMany($documents, $options);
|
|
}
|
|
|
|
/**
|
|
* Update a single document
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $update Update operations
|
|
* @param array $options Update options
|
|
* @return UpdateResult
|
|
*/
|
|
public function updateOne(array $filter, array $update, array $options = []): UpdateResult
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
$update = $this->convertDocument($update);
|
|
return $this->collection->updateOne($filter, $update, $options);
|
|
}
|
|
|
|
/**
|
|
* Update multiple documents
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $update Update operations
|
|
* @param array $options Update options
|
|
* @return UpdateResult
|
|
*/
|
|
public function updateMany(array $filter, array $update, array $options = []): UpdateResult
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
$update = $this->convertDocument($update);
|
|
return $this->collection->updateMany($filter, $update, $options);
|
|
}
|
|
|
|
/**
|
|
* Delete a single document
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $options Delete options
|
|
* @return DeleteResult
|
|
*/
|
|
public function deleteOne(array $filter, array $options = []): DeleteResult
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
return $this->collection->deleteOne($filter, $options);
|
|
}
|
|
|
|
/**
|
|
* Delete multiple documents
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $options Delete options
|
|
* @return DeleteResult
|
|
*/
|
|
public function deleteMany(array $filter, array $options = []): DeleteResult
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
return $this->collection->deleteMany($filter, $options);
|
|
}
|
|
|
|
/**
|
|
* Count documents matching filter
|
|
*
|
|
* @param array $filter Query filter
|
|
* @param array $options Count options
|
|
* @return int
|
|
*/
|
|
public function countDocuments(array $filter = [], array $options = []): int
|
|
{
|
|
$filter = $this->convertFilter($filter);
|
|
return $this->collection->countDocuments($filter, $options);
|
|
}
|
|
|
|
/**
|
|
* Execute aggregation pipeline
|
|
*
|
|
* @param array $pipeline Aggregation pipeline
|
|
* @param array $options Aggregation options
|
|
* @return Cursor
|
|
*/
|
|
public function aggregate(array $pipeline, array $options = []): Cursor
|
|
{
|
|
/** @var \Iterator $cursor */
|
|
$cursor = $this->collection->aggregate($pipeline, $options);
|
|
return new Cursor($cursor);
|
|
}
|
|
|
|
/**
|
|
* Create an index
|
|
*
|
|
* @param array $key Index specification
|
|
* @param array $options Index options
|
|
* @return string Index name
|
|
*/
|
|
public function createIndex(array $key, array $options = []): string
|
|
{
|
|
return $this->collection->createIndex($key, $options);
|
|
}
|
|
|
|
/**
|
|
* Drop the collection
|
|
*/
|
|
public function drop(): array|object|null
|
|
{
|
|
return $this->collection->drop();
|
|
}
|
|
|
|
/**
|
|
* Get collection name
|
|
*/
|
|
public function getCollectionName(): string
|
|
{
|
|
return $this->collection->getCollectionName();
|
|
}
|
|
|
|
/**
|
|
* Get database name
|
|
*/
|
|
public function getDatabaseName(): string
|
|
{
|
|
return $this->collection->getDatabaseName();
|
|
}
|
|
|
|
/**
|
|
* Convert ObjectId instances in filter to MongoDB ObjectId
|
|
*/
|
|
private function convertFilter(array $filter): array
|
|
{
|
|
return $this->convertArray($filter);
|
|
}
|
|
|
|
/**
|
|
* Convert ObjectId instances in document to MongoDB ObjectId
|
|
*/
|
|
private function convertDocument(array|object $document): array|object
|
|
{
|
|
if (is_array($document)) {
|
|
return $this->convertArray($document);
|
|
}
|
|
return $document;
|
|
}
|
|
|
|
/**
|
|
* Recursively convert ObjectId and UTCDateTime instances
|
|
*/
|
|
private function convertArray(array $data): array
|
|
{
|
|
foreach ($data as $key => $value) {
|
|
if ($value instanceof ObjectId) {
|
|
$data[$key] = $value->toBSON();
|
|
} elseif ($value instanceof UTCDateTime) {
|
|
$data[$key] = $value->toBSON();
|
|
} elseif (is_array($value)) {
|
|
$data[$key] = $this->convertArray($value);
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Get the underlying MongoDB Collection
|
|
* Use sparingly - prefer using wrapper methods
|
|
*/
|
|
public function getMongoCollection(): MongoCollection
|
|
{
|
|
return $this->collection;
|
|
}
|
|
|
|
/**
|
|
* Convert BSON objects to native PHP types
|
|
* Handles ObjectId, UTCDateTime, and other BSON types
|
|
*/
|
|
private function convertBsonToNative(mixed $data): mixed
|
|
{
|
|
if (is_array($data)) {
|
|
foreach ($data as $key => $value) {
|
|
$data[$key] = $this->convertBsonToNative($value);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
if (is_object($data)) {
|
|
// Convert MongoDB BSON ObjectId to string
|
|
if ($data instanceof \MongoDB\BSON\ObjectId) {
|
|
return (string) $data;
|
|
}
|
|
|
|
// Convert MongoDB BSON UTCDateTime to string or DateTime
|
|
if ($data instanceof \MongoDB\BSON\UTCDateTime) {
|
|
return (string) $data->toDateTime()->format('c');
|
|
}
|
|
|
|
// Convert other objects to arrays recursively
|
|
if (method_exists($data, 'bsonSerialize')) {
|
|
return $this->convertBsonToNative($data->bsonSerialize());
|
|
}
|
|
|
|
return (array) $data;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|