Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
mongo-php-library
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sinan
mongo-php-library
Commits
f2666e19
Commit
f2666e19
authored
Jan 11, 2016
by
Jeremy Mikola
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Apply CS fixes and refactor GridFS classes
parent
0b31800c
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
824 additions
and
303 deletions
+824
-303
GridFSFileNotFoundException.php
src/Exception/GridFSFileNotFoundException.php
+4
-3
Bucket.php
src/GridFS/Bucket.php
+267
-133
GridFSCollectionsWrapper.php
src/GridFS/GridFSCollectionsWrapper.php
+45
-72
GridFSDownload.php
src/GridFS/GridFSDownload.php
+163
-0
GridFSUpload.php
src/GridFS/GridFSUpload.php
+254
-0
StreamWrapper.php
src/GridFS/StreamWrapper.php
+62
-48
BucketFunctionalTest.php
tests/GridFS/BucketFunctionalTest.php
+8
-8
GridFSStreamTest.php
tests/GridFS/GridFSStreamTest.php
+21
-39
No files found.
src/Exception/GridFSFileNotFoundException.php
View file @
f2666e19
...
...
@@ -4,7 +4,8 @@ namespace MongoDB\Exception;
class
GridFSFileNotFoundException
extends
\MongoDB\Driver\Exception\RuntimeException
implements
Exception
{
public
function
__construct
(
$fname
,
$nameSpace
){
parent
::
__construct
(
sprintf
(
'Unable to find file by: %s in %s'
,
$fname
,
$nameSpace
));
}
public
function
__construct
(
$filename
,
$namespace
)
{
parent
::
__construct
(
sprintf
(
'Unable to find file "%s" in namespace "%s"'
,
$filename
,
$namespace
));
}
}
src/GridFS/Bucket.php
View file @
f2666e19
<?php
namespace
MongoDB\GridFS
;
use
MongoDB\Collection
;
use
MongoDB\BSON\ObjectId
;
use
MongoDB\Driver\Cursor
;
use
MongoDB\Driver\Manager
;
use
MongoDB\Exception\GridFSFileNotFoundException
;
use
MongoDB\Exception\InvalidArgumentTypeException
;
use
MongoDB\Operation\Find
;
/**
* Bucket provides a public API for interacting with the GridFS files and chunks
* collections.
*
* @api
* Bucket abstracts the GridFS files and chunks collections.
*/
class
Bucket
{
private
$databaseName
;
private
static
$streamWrapper
;
private
$collectionsWrapper
;
private
$databaseName
;
private
$options
;
private
static
$streamWrapper
;
/**
* Constructs a GridFS bucket.
*
...
...
@@ -29,199 +38,324 @@ class Bucket
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param Manager
$manager Manager instance from the driver
* @param string
$databaseName Database name
* @param array
$options Bucket options
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param array $options Bucket options
* @throws InvalidArgumentException
*/
public
function
__construct
(
Manager
$manager
,
$databaseName
,
array
$options
=
[])
{
$options
+=
[
'bucketName'
=>
'fs'
,
'chunkSizeBytes'
=>
261120
,
'bucketName'
=>
'fs'
];
if
(
isset
(
$options
[
'bucketName'
])
&&
!
is_string
(
$options
[
'bucketName'
]))
{
throw
new
InvalidArgumentTypeException
(
'"bucketName" option'
,
$options
[
'bucketName'
],
'string'
);
}
if
(
isset
(
$options
[
'chunkSizeBytes'
])
&&
!
is_integer
(
$options
[
'chunkSizeBytes'
]))
{
throw
new
InvalidArgumentTypeException
(
'"chunkSizeBytes" option'
,
$options
[
'chunkSizeBytes'
],
'integer'
);
}
if
(
isset
(
$options
[
'readPreference'
])
&&
!
$options
[
'readPreference'
]
instanceof
ReadPreference
)
{
throw
new
InvalidArgumentTypeException
(
'"readPreference" option'
,
$options
[
'readPreference'
],
'MongoDB\Driver\ReadPreference'
);
}
if
(
isset
(
$options
[
'writeConcern'
])
&&
!
$options
[
'writeConcern'
]
instanceof
WriteConcern
)
{
throw
new
InvalidArgumentTypeException
(
'"writeConcern" option'
,
$options
[
'writeConcern'
],
'MongoDB\Driver\WriteConcern'
);
}
$this
->
databaseName
=
(
string
)
$databaseName
;
$this
->
options
=
$options
;
$this
->
collectionsWrapper
=
new
GridFSCollectionsWrapper
(
$manager
,
$databaseName
,
$options
);
$collectionOptions
=
array_intersect_key
(
$options
,
[
'readPreference'
=>
1
,
'writeConcern'
=>
1
]);
$this
->
collectionsWrapper
=
new
GridFSCollectionsWrapper
(
$manager
,
$databaseName
,
$options
[
'bucketName'
],
$collectionOptions
);
$this
->
registerStreamWrapper
(
$manager
);
}
/**
* Opens a Stream for writing the contents of a file.
* Delete a file from the GridFS bucket.
*
* If the files collection document is not found, this method will still
* attempt to delete orphaned chunks.
*
* @param string $filename file to upload
* @param array $options Stream Options
* @return Stream uploadStream
* @param ObjectId $id ObjectId of the file
* @throws GridFSFileNotFoundException
*/
public
function
openUploadStream
(
$filename
,
array
$options
=
[]
)
public
function
delete
(
ObjectId
$id
)
{
$options
+=
[
'chunkSizeBytes'
=>
$this
->
options
[
'chunkSizeBytes'
]];
$streamOptions
=
[
'collectionsWrapper'
=>
$this
->
collectionsWrapper
,
'uploadOptions'
=>
$options
];
$context
=
stream_context_create
([
'gridfs'
=>
$streamOptions
]);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
databaseName
,
$filename
),
'w'
,
false
,
$context
);
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$id
]);
$this
->
collectionsWrapper
->
getChunksCollection
()
->
deleteMany
([
'files_id'
=>
$id
]);
if
(
$file
===
null
)
{
throw
new
GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
$this
->
collectionsWrapper
->
getFilesCollection
()
->
deleteOne
([
'_id'
=>
$id
]);
}
/**
*
Upload a file to this bucket by specifying the source stream file
*
Writes the contents of a GridFS file to a writable stream.
*
* @param String $filename Filename To Insert
* @param Stream $source Source Stream
* @param array $options Stream Options
* @return ObjectId
* @param ObjectId $id ObjectId of the file
* @param resource $destination Writable Stream
* @throws GridFSFileNotFoundException
*/
public
function
uploadFromStream
(
$filename
,
$source
,
array
$options
=
[]
)
public
function
downloadToStream
(
ObjectId
$id
,
$destination
)
{
$options
+=
[
'chunkSizeBytes'
=>
$this
->
options
[
'chunkSizeBytes'
]];
$gridFsStream
=
new
GridFsUpload
(
$this
->
collectionsWrapper
,
$filename
,
$options
);
return
$gridFsStream
->
uploadFromStream
(
$source
);
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
(
[
'_id'
=>
$id
],
[
'typeMap'
=>
[
'root'
=>
'stdClass'
]]
);
if
(
$file
===
null
)
{
throw
new
GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
$gridFsStream
=
new
GridFSDownload
(
$this
->
collectionsWrapper
,
$file
);
$gridFsStream
->
downloadToStream
(
$destination
);
}
/**
* Opens a Stream for reading the contents of a file specified by ID.
* Writes the contents of a GridFS file, which is selected by name and
* revision, to a writable stream.
*
* Supported options:
*
* * revision (integer): Which revision (i.e. documents with the same
* filename and different uploadDate) of the file to retrieve. Defaults
* to -1 (i.e. the most recent revision).
*
* Revision numbers are defined as follows:
*
* @param ObjectId $id
* @return Stream
* * 0 = the original stored file
* * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @param string $filename File name
* @param resource $destination Writable Stream
* @param array $options Download options
* @throws GridFSFileNotFoundException
*/
public
function
openDownloadStream
(
\MongoDB\BSON\ObjectId
$id
)
public
function
downloadToStreamByName
(
$filename
,
$destination
,
array
$options
=
[]
)
{
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$id
]);
if
(
is_null
(
$file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
return
$this
->
openDownloadStreamByFile
(
$file
);
$options
+=
[
'revision'
=>
-
1
];
$file
=
$this
->
findFileRevision
(
$filename
,
$options
[
'revision'
]);
$gridFsStream
=
new
GridFSDownload
(
$this
->
collectionsWrapper
,
$file
);
$gridFsStream
->
downloadToStream
(
$destination
);
}
/**
* Downloads the contents of the stored file specified by id and writes
* the contents to the destination Stream.
* @param ObjectId $id GridFS File Id
* @param Stream $destination Destination Stream
*/
public
function
downloadToStream
(
\MongoDB\BSON\ObjectId
$id
,
$destination
)
/**
* Find files from the GridFS bucket's files collection.
*
* @see Find::__construct() for supported options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return Cursor
*/
public
function
find
(
$filter
,
array
$options
=
[])
{
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$id
]);
if
(
is_null
(
$file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
$gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
$file
);
$gridFsStream
->
downloadToStream
(
$destination
);
return
$this
->
collectionsWrapper
->
getFilesCollection
()
->
find
(
$filter
,
$options
);
}
public
function
getCollectionsWrapper
()
{
return
$this
->
collectionsWrapper
;
}
public
function
getDatabaseName
()
{
return
$this
->
databaseName
;
}
/**
* Delete a file from the GridFS bucket. If the file collection entry is not found, still attempts to delete orphaned chunks
*
* @param ObjectId $id file id
* @throws GridFSFileNotFoundException
*/
public
function
delete
(
\MongoDB\BSON\ObjectId
$id
)
* Gets the ID of the GridFS file associated with a stream.
*
* @param resource $stream GridFS stream
* @return mixed
*/
public
function
getIdFromStream
(
$stream
)
{
$
file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$id
]
);
$this
->
collectionsWrapper
->
getChunksCollection
()
->
deleteMany
([
'files_id'
=>
$id
]);
if
(
is
_null
(
$file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
())
;
$
metadata
=
stream_get_meta_data
(
$stream
);
if
(
is
set
(
$metadata
[
'wrapper_data'
]
->
id
))
{
return
$metadata
[
'wrapper_data'
]
->
id
;
}
$this
->
collectionsWrapper
->
getFilesCollection
()
->
deleteOne
([
'_id'
=>
$id
]);
return
;
}
/**
* Open a stream to download a file from the GridFS bucket. Searches for the file by the specified name then returns a stream to the specified file
* @param string $filename name of the file to download
* @param int $revision the revision of the file to download
* @throws GridFSFileNotFoundException
*/
public
function
openDownloadStreamByName
(
$filename
,
$revision
=
-
1
)
{
$file
=
$this
->
findFileRevision
(
$filename
,
$revision
);
* Opens a readable stream for reading a GridFS file.
*
* @param ObjectId $id ObjectId of the file
* @return resource
* @throws GridFSFileNotFoundException
*/
public
function
openDownloadStream
(
ObjectId
$id
)
{
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
(
[
'_id'
=>
$id
],
[
'typeMap'
=>
[
'root'
=>
'stdClass'
]]
);
if
(
$file
===
null
)
{
throw
new
GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
return
$this
->
openDownloadStreamByFile
(
$file
);
}
/**
* Download a file from the GridFS bucket by name. Searches for the file by the specified name then loads data into the stream
*
* @param string $filename name of the file to download
* @param int $revision the revision of the file to download
* @throws GridFSFileNotFoundException
*/
public
function
downloadToStreamByName
(
$filename
,
$destination
,
$revision
=-
1
)
{
$file
=
$this
->
findFileRevision
(
$filename
,
$revision
);
$gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
$file
);
$gridFsStream
->
downloadToStream
(
$destination
);
}
/**
* Find files from the GridFS bucket files collection.
*
* @param array $filter filter to find by
* @param array $options options to
*/
public
function
find
(
$filter
,
array
$options
=
[])
* Opens a readable stream stream to read a GridFS file, which is selected
* by name and revision.
*
* Supported options:
*
* * revision (integer): Which revision (i.e. documents with the same
* filename and different uploadDate) of the file to retrieve. Defaults
* to -1 (i.e. the most recent revision).
*
* Revision numbers are defined as follows:
*
* * 0 = the original stored file
* * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @param string $filename File name
* @param array $options Download options
* @return resource
* @throws GridFSFileNotFoundException
*/
public
function
openDownloadStreamByName
(
$filename
,
array
$options
=
[])
{
return
$this
->
collectionsWrapper
->
getFilesCollection
()
->
find
(
$filter
,
$options
);
$options
+=
[
'revision'
=>
-
1
];
$file
=
$this
->
findFileRevision
(
$filename
,
$options
[
'revision'
]);
return
$this
->
openDownloadStreamByFile
(
$file
);
}
/**
* Gets the id of the GridFs file associated with $stream
*
* @param resource $stream wrapped gridFsStream
*/
public
function
getIdFromStream
(
$stream
)
* Opens a writable stream for writing a GridFS file.
*
* Supported options:
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size.
*
* @param string $filename File name
* @param array $options Stream options
* @return resource
*/
public
function
openUploadStream
(
$filename
,
array
$options
=
[])
{
$metadata
=
stream_get_meta_data
(
$stream
);
if
(
isset
(
$metadata
[
"wrapper_data"
]
->
id
)){
return
$metadata
[
"wrapper_data"
]
->
id
;
}
return
null
;
$options
+=
[
'chunkSizeBytes'
=>
$this
->
options
[
'chunkSizeBytes'
]];
$streamOptions
=
[
'collectionsWrapper'
=>
$this
->
collectionsWrapper
,
'uploadOptions'
=>
$options
,
];
$context
=
stream_context_create
([
'gridfs'
=>
$streamOptions
]);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
databaseName
,
$filename
),
'w'
,
false
,
$context
);
}
/**
* Gets the id of the GridFs file associated with $stream
*
* @param \MongoDB\BSON\ObjectId $id id
of the file to rename
* @param string $newFilename new name for the fil
e
* @throws \MongoDB\Exception\
GridFSFileNotFoundException
*/
public
function
rename
(
\MongoDB\BSON\
ObjectId
$id
,
$newFilename
)
* Renames the GridFS file with the specified ID.
*
* @param ObjectId $id ID
of the file to rename
* @param string $newFilename New file nam
e
* @throws
GridFSFileNotFoundException
*/
public
function
rename
(
ObjectId
$id
,
$newFilename
)
{
$filesCollection
=
$this
->
collectionsWrapper
->
getFilesCollection
();
$file
=
$filesCollection
->
findOne
([
"_id"
=>
$id
]);
if
(
is_null
(
$file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
$file
=
$filesCollection
->
findOne
([
'_id'
=>
$id
]);
if
(
$file
===
null
)
{
throw
new
GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
$file
->
filename
=
$newFilename
;
$filesCollection
->
replaceOne
([
"_id"
=>
$id
],
$file
);
}
public
function
getCollectionsWrapper
()
{
return
$this
->
collectionsWrapper
;
$filesCollection
->
replaceOne
([
'_id'
=>
$id
],
$file
);
}
public
function
getDatabaseName
(){
return
$this
->
databaseName
;
}
private
function
openDownloadStreamByFile
(
$file
)
/**
* Writes the contents of a readable stream to a GridFS file.
*
* Supported options:
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size.
*
* @param string $filename File name
* @param resource $source Readable stream
* @param array $options Stream options
* @return ObjectId
*/
public
function
uploadFromStream
(
$filename
,
$source
,
array
$options
=
[])
{
$options
=
[
'collectionsWrapper'
=>
$this
->
collectionsWrapper
,
'file'
=>
$file
];
$context
=
stream_context_create
([
'gridfs'
=>
$options
]);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
databaseName
,
$file
->
filename
),
'r'
,
false
,
$context
);
$options
+=
[
'chunkSizeBytes'
=>
$this
->
options
[
'chunkSizeBytes'
]];
$gridFsStream
=
new
GridFSUpload
(
$this
->
collectionsWrapper
,
$filename
,
$options
);
return
$gridFsStream
->
uploadFromStream
(
$source
);
}
private
function
findFileRevision
(
$filename
,
$revision
)
{
if
(
$revision
<
0
)
{
$skip
=
abs
(
$revision
)
-
1
;
$skip
=
abs
(
$revision
)
-
1
;
$sortOrder
=
-
1
;
}
else
{
$skip
=
$revision
;
$sortOrder
=
1
;
}
$filesCollection
=
$this
->
collectionsWrapper
->
getFilesCollection
();
$file
=
$filesCollection
->
findOne
([
"filename"
=>
$filename
],
[
"sort"
=>
[
"uploadDate"
=>
$sortOrder
],
"limit"
=>
1
,
"skip"
=>
$skip
]);
if
(
is_null
(
$file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$filename
,
$filesCollection
->
getNameSpace
());
$file
=
$filesCollection
->
findOne
(
[
'filename'
=>
$filename
],
[
'skip'
=>
$skip
,
'sort'
=>
[
'uploadDate'
=>
$sortOrder
],
'typeMap'
=>
[
'root'
=>
'stdClass'
],
]
);
if
(
$file
===
null
)
{
throw
new
GridFSFileNotFoundException
(
$filename
,
$filesCollection
->
getNameSpace
());
}
return
$file
;
}
private
function
registerStreamWrapper
(
$manager
)
private
function
openDownloadStreamByFile
(
$file
)
{
if
(
isset
(
Bucket
::
$streamWrapper
)){
$options
=
[
'collectionsWrapper'
=>
$this
->
collectionsWrapper
,
'file'
=>
$file
,
];
$context
=
stream_context_create
([
'gridfs'
=>
$options
]);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
databaseName
,
$file
->
filename
),
'r'
,
false
,
$context
);
}
private
function
registerStreamWrapper
(
Manager
$manager
)
{
if
(
isset
(
self
::
$streamWrapper
))
{
return
;
}
Bucket
::
$streamWrapper
=
new
\MongoDB\GridFS\StreamWrapper
();
Bucket
::
$streamWrapper
->
register
(
$manager
);
self
::
$streamWrapper
=
new
StreamWrapper
();
self
::
$streamWrapper
->
register
(
$manager
);
}
}
src/GridFS/GridFSCollectionsWrapper.php
View file @
f2666e19
<?php
namespace
MongoDB\GridFS
;
use
MongoDB\Collection
;
use
MongoDB\Driver\Manager
;
use
MongoDB\Driver\ReadPreference
;
use
MongoDB\Driver\WriteConcern
;
use
MongoDB\Driver\Manager
;
use
MongoDB\Exception\InvalidArgumentTypeException
;
/**
* @internal
* GridFSCollectionsWrapper abstracts the GridFS files and chunks collections.
*
* @internal
*/
class
GridFSCollectionsWrapper
{
private
$filesCollection
;
private
$chunksCollection
;
private
$ensuredIndexes
=
false
;
private
$filesCollection
;
/**
* Constructs a GridFS bucket.
*
* Supported options:
*
* * bucketName (string): The bucket name, which will be used as a prefix
* for the files and chunks collections. Defaults to "fs".
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* 261120 (i.e. 255 KiB).
* Constructs a GridFS collection wrapper.
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param array $options Bucket options
* @see Collection::__construct() for supported options
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param string $bucketName Bucket name
* @param array $collectionOptions Collection options
* @throws InvalidArgumentException
*/
public
function
__construct
(
Manager
$manager
,
$databaseName
,
$
options
)
public
function
__construct
(
Manager
$manager
,
$databaseName
,
$
bucketName
,
array
$collectionOptions
=
[]
)
{
$collectionOptions
=
[];
if
(
isset
(
$options
[
'bucketName'
])
&&
!
is_string
(
$options
[
'bucketName'
]))
{
throw
new
InvalidArgumentTypeException
(
'"bucketName" option'
,
$options
[
'bucketName'
],
'string'
);
}
if
(
isset
(
$options
[
'chunkSizeBytes'
])
&&
!
is_integer
(
$options
[
'chunkSizeBytes'
]))
{
throw
new
InvalidArgumentTypeException
(
'"chunkSizeBytes" option'
,
$options
[
'chunkSizeBytes'
],
'integer'
);
}
if
(
isset
(
$options
[
'readPreference'
]))
{
if
(
!
$options
[
'readPreference'
]
instanceof
ReadPreference
)
{
throw
new
InvalidArgumentTypeException
(
'"readPreference" option'
,
$options
[
'readPreference'
],
'MongoDB\Driver\ReadPreference'
);
}
else
{
$collectionOptions
[
'readPreference'
]
=
$options
[
'readPreference'
];
}
}
if
(
isset
(
$options
[
'writeConcern'
]))
{
if
(
!
$options
[
'writeConcern'
]
instanceof
WriteConcern
)
{
throw
new
InvalidArgumentTypeException
(
'"writeConcern" option'
,
$options
[
'writeConcern'
],
'MongoDB\Driver\WriteConcern'
);
}
else
{
$collectionOptions
[
'writeConcern'
]
=
$options
[
'writeConcern'
];
}
}
$this
->
filesCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.files'
,
$databaseName
,
$options
[
'bucketName'
]),
$collectionOptions
);
$this
->
chunksCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.chunks'
,
$databaseName
,
$options
[
'bucketName'
]),
$collectionOptions
);
$this
->
filesCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.files'
,
$databaseName
,
$bucketName
),
$collectionOptions
);
$this
->
chunksCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.chunks'
,
$databaseName
,
$bucketName
),
$collectionOptions
);
}
public
function
chunkInsert
(
$toUpload
)
{
$this
->
ensureIndexes
();
$this
->
chunksCollection
->
insertOne
(
$toUpload
);
}
public
function
fileInsert
(
$toUpload
)
{
$this
->
ensureIndexes
();
$this
->
filesCollection
->
insertOne
(
$toUpload
);
}
public
function
getChunksCollection
()
{
return
$this
->
chunksCollection
;
}
public
function
getFilesCollection
()
{
return
$this
->
filesCollection
;
}
public
function
insertChunk
(
$chunk
)
{
$this
->
ensureIndexes
();
$this
->
chunksCollection
->
insertOne
(
$chunk
);
}
p
rivate
function
ensureIndexes
(
)
p
ublic
function
insertFile
(
$file
)
{
if
(
$this
->
ensuredIndexes
)
{
return
;
}
if
(
!
$this
->
isFilesCollectionEmpty
())
{
return
;
}
$this
->
ensureFilesIndex
();
$this
->
ensureChunksIndex
();
$this
->
ensuredIndexes
=
true
;
$this
->
ensureIndexes
();
$this
->
filesCollection
->
insertOne
(
$file
);
}
private
function
ensureChunksIndex
()
{
foreach
(
$this
->
chunksCollection
->
listIndexes
()
as
$index
)
{
...
...
@@ -110,8 +64,10 @@ class GridFSCollectionsWrapper
return
;
}
}
$this
->
chunksCollection
->
createIndex
([
'files_id'
=>
1
,
'n'
=>
1
],
[
'unique'
=>
true
]);
}
private
function
ensureFilesIndex
()
{
foreach
(
$this
->
filesCollection
->
listIndexes
()
as
$index
)
{
...
...
@@ -119,8 +75,25 @@ class GridFSCollectionsWrapper
return
;
}
}
$this
->
filesCollection
->
createIndex
([
'filename'
=>
1
,
'uploadDate'
=>
1
]);
}
private
function
ensureIndexes
()
{
if
(
$this
->
ensuredIndexes
)
{
return
;
}
if
(
!
$this
->
isFilesCollectionEmpty
())
{
return
;
}
$this
->
ensureFilesIndex
();
$this
->
ensureChunksIndex
();
$this
->
ensuredIndexes
=
true
;
}
private
function
isFilesCollectionEmpty
()
{
return
null
===
$this
->
filesCollection
->
findOne
([],
[
...
...
src/GridFS/GridF
s
Download.php
→
src/GridFS/GridF
S
Download.php
View file @
f2666e19
<?php
namespace
MongoDB\GridFS
;
use
MongoDB\
Collec
tion
;
use
\
MongoDB\Exception\GridFSCorruptFileException
;
use
\MongoDB\Exception\InvalidArgumentTypeException
;
use
MongoDB\
Driver\Exception\Excep
tion
;
use
MongoDB\Exception\GridFSCorruptFileException
;
use
stdClass
;
/**
* GridFSDownload abstracts the process of reading a GridFS file.
*
* @internal
* GridFSDownload abstracts the processes of downloading from a GridFSBucket
*/
class
GridF
s
Download
class
GridF
S
Download
{
private
$buffer
;
private
$bufferEmpty
=
true
;
private
$bufferFresh
=
true
;
private
$bytesSeen
=
0
;
private
$chunkOffset
=
0
;
private
$chunksIterator
;
private
$bytesSeen
=
0
;
private
$numChunks
;
private
$iteratorEmpty
=
false
;
private
$firstCheck
=
true
;
private
$bufferFresh
=
true
;
private
$bufferEmpty
=
true
;
private
$collectionsWrapper
;
private
$chunkOffset
=
0
;
private
$buffer
;
private
$file
;
private
$firstCheck
=
true
;
private
$iteratorEmpty
=
false
;
private
$numChunks
;
/**
* Constructs a GridFS download stream
*
* Constructs a GridFS download stream.
*
* @param GridFSCollectionsWrapper $collectionsWrapper
File options
* @param
\stdClass $file GridFS file to use
* @throws GridFSCorruptFileException
, InvalidArgumentTypeException
* @param GridFSCollectionsWrapper $collectionsWrapper
GridFS collections wrapper
* @param
stdClass $file GridFS file document
* @throws GridFSCorruptFileException
*/
public
function
__construct
(
GridFSCollectionsWrapper
$collectionsWrapper
,
$file
)
public
function
__construct
(
GridFSCollectionsWrapper
$collectionsWrapper
,
stdClass
$file
)
{
if
(
!
(
$file
instanceof
\stdClass
)){
throw
new
\MongoDB\Exception\InvalidArgumentTypeException
(
'"file"'
,
$file
,
'stdClass'
);
}
$this
->
collectionsWrapper
=
$collectionsWrapper
;
$this
->
file
=
$file
;
try
{
$cursor
=
$this
->
collectionsWrapper
->
getChunksCollection
()
->
find
([
'files_id'
=>
$this
->
file
->
_id
],
[
'sort'
=>
[
'n'
=>
1
]]);
}
catch
(
\MongoDB\Exception
$e
){
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
try
{
$cursor
=
$this
->
collectionsWrapper
->
getChunksCollection
()
->
find
(
[
'files_id'
=>
$this
->
file
->
_id
],
[
'sort'
=>
[
'n'
=>
1
]]
);
}
catch
(
Exception
$e
)
{
// TODO: Why do we replace a driver exception with GridFSCorruptFileException here?
throw
new
GridFSCorruptFileException
();
}
$this
->
chunksIterator
=
new
\IteratorIterator
(
$cursor
);
if
(
$this
->
file
->
length
>=
0
)
{
$this
->
numChunks
=
ceil
(
$this
->
file
->
length
/
$this
->
file
->
chunkSize
);
}
else
{
$this
->
numChunks
=
0
;
}
$this
->
numChunks
=
(
$file
->
length
>=
0
)
?
ceil
(
$file
->
length
/
$file
->
chunkSize
)
:
0
;
$this
->
buffer
=
fopen
(
'php://temp'
,
'w+'
);
}
public
function
downloadToStream
(
$destination
)
public
function
close
()
{
while
(
$this
->
advanceChunks
())
{
fwrite
(
$destination
,
$this
->
chunksIterator
->
current
()
->
data
->
getData
());
}
fclose
(
$this
->
buffer
);
}
public
function
downloadNumBytes
(
$numToRead
)
{
public
function
downloadNumBytes
(
$numToRead
)
{
$output
=
""
;
if
(
$this
->
bufferFresh
)
{
rewind
(
$this
->
buffer
);
$this
->
bufferFresh
=
false
;
$this
->
bufferFresh
=
false
;
}
// TODO: Should we be checking for fread errors here?
$output
=
fread
(
$this
->
buffer
,
$numToRead
);
if
(
strlen
(
$output
)
==
$numToRead
)
{
return
$output
;
}
fclose
(
$this
->
buffer
);
$this
->
buffer
=
fopen
(
"php://temp"
,
"w+"
);
$this
->
bufferFresh
=
true
;
$this
->
bufferEmpty
=
true
;
$this
->
bufferFresh
=
true
;
$this
->
bufferEmpty
=
true
;
$bytesLeft
=
$numToRead
-
strlen
(
$output
);
while
(
strlen
(
$output
)
<
$numToRead
&&
$this
->
advanceChunks
())
{
while
(
strlen
(
$output
)
<
$numToRead
&&
$this
->
advanceChunks
())
{
$bytesLeft
=
$numToRead
-
strlen
(
$output
);
$output
.=
substr
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
(),
0
,
$bytesLeft
);
}
if
(
!
$this
->
iteratorEmpty
&&
$this
->
file
->
length
>
0
&&
$bytesLeft
<
strlen
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
()))
{
if
(
!
$this
->
iteratorEmpty
&&
$this
->
file
->
length
>
0
&&
$bytesLeft
<
strlen
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
()))
{
fwrite
(
$this
->
buffer
,
substr
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
(),
$bytesLeft
));
$this
->
bufferEmpty
=
false
;
$this
->
bufferEmpty
=
false
;
}
return
$output
;
}
public
function
getSize
()
public
function
downloadToStream
(
$destination
)
{
return
$this
->
file
->
length
;
while
(
$this
->
advanceChunks
())
{
// TODO: Should we be checking for fwrite errors here?
fwrite
(
$destination
,
$this
->
chunksIterator
->
current
()
->
data
->
getData
());
}
}
public
function
getFile
()
{
return
$this
->
file
;
}
public
function
getId
()
{
return
$this
->
file
->
_id
;
}
public
function
getFile
()
public
function
getSize
()
{
return
$this
->
file
;
return
$this
->
file
->
length
;
}
public
function
isEOF
()
{
return
(
$this
->
iteratorEmpty
&&
$this
->
bufferEmpty
);
}
private
function
advanceChunks
()
{
if
(
$this
->
chunkOffset
>=
$this
->
numChunks
)
{
$this
->
iteratorEmpty
=
true
;
if
(
$this
->
chunkOffset
>=
$this
->
numChunks
)
{
$this
->
iteratorEmpty
=
true
;
return
false
;
}
if
(
$this
->
firstCheck
)
{
if
(
$this
->
firstCheck
)
{
$this
->
chunksIterator
->
rewind
();
$this
->
firstCheck
=
false
;
$this
->
firstCheck
=
false
;
}
else
{
$this
->
chunksIterator
->
next
();
}
if
(
!
$this
->
chunksIterator
->
valid
())
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
if
(
!
$this
->
chunksIterator
->
valid
())
{
throw
new
GridFSCorruptFileException
();
}
if
(
$this
->
chunksIterator
->
current
()
->
n
!=
$this
->
chunkOffset
)
{
throw
new
\MongoDB\Exception\
GridFSCorruptFileException
();
throw
new
GridFSCorruptFileException
();
}
$chunkSizeIs
=
strlen
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
());
if
(
$this
->
chunkOffset
==
$this
->
numChunks
-
1
)
{
$chunkSizeShouldBe
=
$this
->
file
->
length
-
$this
->
bytesSeen
;
if
(
$chunkSizeShouldBe
!=
$chunkSizeIs
)
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
}
}
else
if
(
$this
->
chunkOffset
<
$this
->
numChunks
-
1
)
{
if
(
$chunkSizeIs
!=
$this
->
file
->
chunkSize
)
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
}
$actualChunkSize
=
strlen
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
());
$expectedChunkSize
=
(
$this
->
chunkOffset
==
$this
->
numChunks
-
1
)
?
(
$this
->
file
->
length
-
$this
->
bytesSeen
)
:
$this
->
file
->
chunkSize
;
if
(
$actualChunkSize
!=
$expectedChunkSize
)
{
throw
new
GridFSCorruptFileException
();
}
$this
->
bytesSeen
+=
$chunkSizeIs
;
$this
->
bytesSeen
+=
$actualChunkSize
;
$this
->
chunkOffset
++
;
return
true
;
}
public
function
close
()
{
fclose
(
$this
->
buffer
);
}
public
function
isEOF
()
{
$eof
=
$this
->
iteratorEmpty
&&
$this
->
bufferEmpty
;
return
$eof
;
return
true
;
}
}
src/GridFS/GridF
s
Upload.php
→
src/GridFS/GridF
S
Upload.php
View file @
f2666e19
<?php
namespace
MongoDB\GridFS
;
use
MongoDB\Collection
;
use
MongoDB\BSON\Binary
;
use
MongoDB\BSON\ObjectId
;
use
MongoDB\BSON\UTCDateTime
;
use
MongoDB\Driver\Exception\Exception
;
use
MongoDB\Exception\InvalidArgumentTypeException
;
use
MongoDB\BSON
;
/**
* GridFSUpload abstracts the process of writing a GridFS file.
*
* @internal
* GridFsupload abstracts the processes of inserting into a GridFSBucket
*/
class
GridF
s
Upload
class
GridF
S
Upload
{
private
$
ctx
;
private
$
buffer
;
private
$bufferLength
=
0
;
private
$indexChecker
;
private
$length
=
0
;
private
$collectionsWrapper
;
private
$chunkOffset
=
0
;
private
$chunkSize
;
private
$buffer
;
private
$collectionsWrapper
;
private
$ctx
;
private
$file
;
private
$indexChecker
;
private
$isClosed
=
false
;
private
$length
=
0
;
/**
* Constructs a GridFS upload stream
* Constructs a GridFS upload stream
.
*
* Supported options:
*
* * contentType (string): DEPRECATED content type to be stored with the file.
* This information should now be added to the metadata
* * aliases (array of strings): DEPRECATED An array of aliases.
* Applications wishing to store aliases should add an aliases field to
* the metadata document instead.
*
* * aliases (array of strings): DEPRECATED An array of aliases.
* Applications wishing to store aliases should add an aliases field to the
* metadata document instead.
*
* * metadata (array or object): User data for the 'metadata' field of the files
* collection document.
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* 261120 (i.e. 255 KiB).
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
* * contentType (string): DEPRECATED content type to be stored with the
* file. This information should now be added to the metadata.
*
* * chunkSizeBytes: size of each chunk
* * metadata (document): User data for the "metadata" field of the files
* collection document.
*
* @param GridFSCollectionsWrapper $collectionsWrapper
Files Collection
* @param string $filename
Filename to insert
* @param array $options
File
options
* @param GridFSCollectionsWrapper $collectionsWrapper
GridFS collections wrapper
* @param string $filename
File name
* @param array $options
Upload
options
* @throws InvalidArgumentTypeException
*/
public
function
__construct
(
GridFsCollectionsWrapper
$collectionsWrapper
,
$filename
,
array
$options
=
[]
)
public
function
__construct
(
GridFSCollectionsWrapper
$collectionsWrapper
,
$filename
,
array
$options
=
[])
{
$this
->
ctx
=
hash_init
(
'md5'
);
$this
->
collectionsWrapper
=
$collectionsWrapper
;
$this
->
buffer
=
fopen
(
'php://temp'
,
'w+'
);
$options
+=
[
'chunkSizeBytes'
=>
261120
];
$options
+=
[
'chunkSizeBytes'
=>
261120
];
$this
->
chunkSize
=
$options
[
'chunkSizeBytes'
];
$time
=
$this
->
millitime
();
$uploadDate
=
new
\MongoDB\BSON\UTCDateTime
(
$time
);
$objectId
=
new
\MongoDB\BSON\ObjectId
();
$main_file
=
[
"chunkSize"
=>
$this
->
chunkSize
,
"filename"
=>
$filename
,
"uploadDate"
=>
$uploadDate
,
"_id"
=>
$objectId
];
$fileOptions
=
[];
if
(
isset
(
$options
[
'contentType'
]))
{
if
(
is_string
(
$options
[
'contentType'
]))
{
$fileOptions
[
'contentType'
]
=
$options
[
'contentType'
];
}
else
{
throw
new
\MongoDB\Exception\InvalidArgumentTypeException
(
'"contentType" option'
,
$options
[
'contentType'
],
'string'
);
}
}
if
(
isset
(
$options
[
'aliases'
]))
{
if
(
\MongoDB\is_string_array
(
$options
[
'aliases'
]))
{
$fileOptions
[
'aliases'
]
=
$options
[
'aliases'
];
}
else
{
throw
new
\MongoDB\Exception\InvalidArgumentTypeException
(
'"aliases" option'
,
$options
[
'aliases'
],
'array of strings'
);
}
if
(
isset
(
$options
[
'aliases'
])
&&
!
\MongoDB\is_string_array
(
$options
[
'aliases'
]))
{
throw
new
InvalidArgumentTypeException
(
'"aliases" option'
,
$options
[
'aliases'
],
'array of strings'
);
}
if
(
isset
(
$options
[
'metadata'
]))
{
if
(
is_array
(
$options
[
'metadata'
])
||
is_object
(
$options
[
'metadata'
]))
{
$fileOptions
[
'metadata'
]
=
$options
[
'metadata'
];
}
else
{
throw
new
\MongoDB\Exception\InvalidArgumentTypeException
(
'"metadata" option'
,
$options
[
'metadata'
],
'object or array'
);
}
if
(
isset
(
$options
[
'contentType'
])
&&
!
is_string
(
$options
[
'contentType'
]))
{
throw
new
InvalidArgumentTypeException
(
'"contentType" option'
,
$options
[
'contentType'
],
'string'
);
}
$this
->
file
=
array_merge
(
$main_file
,
$fileOptions
);
}
/**
* Reads data from a stream into GridFS
*
* @param Stream $source Source Stream
* @return ObjectId
*/
public
function
uploadFromStream
(
$source
)
{
if
(
!
is_resource
(
$source
)
||
get_resource_type
(
$source
)
!=
"stream"
)
{
throw
new
UnexpectedTypeException
(
'stream'
,
$source
);
}
else
{
$streamMetadata
=
stream_get_meta_data
(
$source
);
}
while
(
$data
=
$this
->
readChunk
(
$source
))
{
$this
->
insertChunk
(
$data
);
}
return
$this
->
fileCollectionInsert
();
}
/**
* Insert a chunks into GridFS from a string
*
* @param string $toWrite String to upload
* @return int
*/
public
function
insertChunks
(
$toWrite
)
{
if
(
$this
->
isClosed
){
return
;
if
(
isset
(
$options
[
'metadata'
])
&&
!
is_array
(
$options
[
'metadata'
])
&&
!
is_object
(
$options
[
'metadata'
]))
{
throw
new
InvalidArgumentTypeException
(
'"metadata" option'
,
$options
[
'metadata'
],
'array or object'
);
}
$readBytes
=
0
;
while
(
$readBytes
!=
strlen
(
$toWrite
))
{
$addToBuffer
=
substr
(
$toWrite
,
$readBytes
,
$this
->
chunkSize
-
$this
->
bufferLength
);
fwrite
(
$this
->
buffer
,
$addToBuffer
);
$readBytes
+=
strlen
(
$addToBuffer
);
$this
->
bufferLength
+=
strlen
(
$addToBuffer
);
if
(
$this
->
bufferLength
==
$this
->
chunkSize
)
{
rewind
(
$this
->
buffer
);
$this
->
insertChunk
(
stream_get_contents
(
$this
->
buffer
));
ftruncate
(
$this
->
buffer
,
0
);
$this
->
bufferLength
=
0
;
}
}
return
$readBytes
;
$this
->
chunkSize
=
$options
[
'chunkSizeBytes'
];
$this
->
collectionsWrapper
=
$collectionsWrapper
;
$this
->
buffer
=
fopen
(
'php://temp'
,
'w+'
);
$this
->
ctx
=
hash_init
(
'md5'
);
$this
->
file
=
[
'_id'
=>
new
ObjectId
(),
'chunkSize'
=>
$this
->
chunkSize
,
'filename'
=>
(
string
)
$filename
,
'uploadDate'
=>
$this
->
createUploadDate
(),
]
+
array_intersect_key
(
$options
,
[
'aliases'
=>
1
,
'contentType'
=>
1
,
'metadata'
=>
1
]);
}
/**
* Close an active stream, pushes all buffered data to GridFS
*
*/
* Closes an active stream and flushes all buffered data to GridFS.
*/
public
function
close
()
{
if
(
$this
->
isClosed
){
if
(
$this
->
isClosed
)
{
// TODO: Should this be an error condition? e.g. BadMethodCallException
return
;
}
rewind
(
$this
->
buffer
);
$cached
=
stream_get_contents
(
$this
->
buffer
);
if
(
strlen
(
$cached
)
>
0
)
{
if
(
strlen
(
$cached
)
>
0
)
{
$this
->
insertChunk
(
$cached
);
}
fclose
(
$this
->
buffer
);
$this
->
fileCollectionInsert
();
$this
->
isClosed
=
true
;
}
public
function
getSize
()
public
function
getChunkSize
()
{
return
$this
->
length
;
return
$this
->
chunkSize
;
}
public
function
getFile
()
{
return
$this
->
file
;
}
public
function
getId
()
{
return
$this
->
file
[
"_id"
];
return
$this
->
file
[
'_id'
];
}
public
function
getLength
()
{
return
$this
->
length
;
}
public
function
getChunkSize
()
public
function
getSize
()
{
return
$this
->
chunkSize
;
return
$this
->
length
;
}
public
function
getFile
()
/**
* Inserts binary data into GridFS via chunks.
*
* Data will be buffered internally until chunkSizeBytes are accumulated, at
* which point a chunk's worth of data will be inserted and the buffer
* reset.
*
* @param string $toWrite Binary data to write
* @return int
*/
public
function
insertChunks
(
$toWrite
)
{
return
$this
->
file
;
if
(
$this
->
isClosed
)
{
// TODO: Should this be an error condition? e.g. BadMethodCallException
return
;
}
$readBytes
=
0
;
while
(
$readBytes
!=
strlen
(
$toWrite
))
{
$addToBuffer
=
substr
(
$toWrite
,
$readBytes
,
$this
->
chunkSize
-
$this
->
bufferLength
);
fwrite
(
$this
->
buffer
,
$addToBuffer
);
$readBytes
+=
strlen
(
$addToBuffer
);
$this
->
bufferLength
+=
strlen
(
$addToBuffer
);
if
(
$this
->
bufferLength
==
$this
->
chunkSize
)
{
rewind
(
$this
->
buffer
);
$this
->
insertChunk
(
stream_get_contents
(
$this
->
buffer
));
ftruncate
(
$this
->
buffer
,
0
);
$this
->
bufferLength
=
0
;
}
}
return
$readBytes
;
}
public
function
isEOF
()
{
return
$this
->
isClosed
;
}
/**
* Writes the contents of a readable stream to a GridFS file.
*
* @param resource $source Readable stream
* @return ObjectId
*/
public
function
uploadFromStream
(
$source
)
{
if
(
!
is_resource
(
$source
)
||
get_resource_type
(
$source
)
!=
"stream"
)
{
throw
new
InvalidArgumentTypeException
(
'$stream'
,
$source
,
'resource'
);
}
$streamMetadata
=
stream_get_meta_data
(
$source
);
while
(
$data
=
$this
->
readChunk
(
$source
))
{
$this
->
insertChunk
(
$data
);
}
return
$this
->
fileCollectionInsert
();
}
private
function
abort
()
{
$this
->
collectionsWrapper
->
getChunksCollection
()
->
deleteMany
([
"files_id"
=>
$this
->
file
[
"_id"
]]);
$this
->
collectionsWrapper
->
getFilesCollection
()
->
deleteOne
([
"_id"
=>
$this
->
file
[
'_id'
]]);
$this
->
collectionsWrapper
->
getChunksCollection
()
->
deleteMany
([
'files_id'
=>
$this
->
file
[
'_id'
]]);
$this
->
collectionsWrapper
->
getFilesCollection
()
->
deleteOne
([
'_id'
=>
$this
->
file
[
'_id'
]]);
$this
->
isClosed
=
true
;
}
private
function
insertChunk
(
$data
)
// From: http://stackoverflow.com/questions/3656713/how-to-get-current-time-in-milliseconds-in-php
private
function
createUploadDate
()
{
if
(
$this
->
isClosed
){
return
;
}
$toUpload
=
[
"files_id"
=>
$this
->
file
[
'_id'
],
"n"
=>
$this
->
chunkOffset
,
"data"
=>
new
\MongoDB\BSON\Binary
(
$data
,
\MongoDB\BSON\Binary
::
TYPE_GENERIC
)];
hash_update
(
$this
->
ctx
,
$data
);
$this
->
collectionsWrapper
->
chunkInsert
(
$toUpload
);
$this
->
length
+=
strlen
(
$data
);
$this
->
chunkOffset
++
;
$parts
=
explode
(
' '
,
microtime
());
$milliseconds
=
sprintf
(
'%d%03d'
,
$parts
[
1
],
$parts
[
0
]
*
1000
);
return
new
UTCDateTime
(
$milliseconds
);
}
private
function
fileCollectionInsert
()
{
if
(
$this
->
isClosed
){
if
(
$this
->
isClosed
)
{
// TODO: Should this be an error condition? e.g. BadMethodCallException
return
;
}
$md5
=
hash_final
(
$this
->
ctx
);
$this
->
file
=
array_merge
(
$this
->
file
,
[
'length'
=>
$this
->
length
,
'md5'
=>
$md5
]);
$this
->
collectionsWrapper
->
fileInsert
(
$this
->
file
);
$this
->
file
[
'length'
]
=
$this
->
length
;
$this
->
file
[
'md5'
]
=
$md5
;
$this
->
collectionsWrapper
->
insertFile
(
$this
->
file
);
return
$this
->
file
[
'_id'
];
}
//from: http://stackoverflow.com/questions/3656713/how-to-get-current-time-in-milliseconds-in-php
private
function
millitime
()
{
$microtime
=
microtime
();
$comps
=
explode
(
' '
,
$microtime
);
return
sprintf
(
'%d%03d'
,
$comps
[
1
],
$comps
[
0
]
*
1000
);
private
function
insertChunk
(
$data
)
{
if
(
$this
->
isClosed
)
{
// TODO: Should this be an error condition? e.g. BadMethodCallException
return
;
}
$toUpload
=
[
'files_id'
=>
$this
->
file
[
'_id'
],
'n'
=>
$this
->
chunkOffset
,
'data'
=>
new
Binary
(
$data
,
Binary
::
TYPE_GENERIC
),
];
hash_update
(
$this
->
ctx
,
$data
);
$this
->
collectionsWrapper
->
insertChunk
(
$toUpload
);
$this
->
length
+=
strlen
(
$data
);
$this
->
chunkOffset
++
;
}
private
function
readChunk
(
$source
)
{
$data
;
try
{
try
{
$data
=
fread
(
$source
,
$this
->
chunkSize
);
}
catch
(
Exception
$e
)
{
}
catch
(
Exception
$e
)
{
$this
->
abort
();
throw
$e
;
}
return
$data
;
}
}
src/GridFS/StreamWrapper.php
View file @
f2666e19
<?php
namespace
MongoDB\GridFS
;
use
MongoDB\Collection
;
namespace
MongoDB\GridFS
;
/**
* Stream wrapper for reading and writing a GridFS file.
*
* @internal
* @see MongoDB\GridFS\Bucket::openUploadStream(), MongoDB\GridFS\Bucket::openDownloadStream()
* @see Bucket::openUploadStream()
* @see Bucket::openDownloadStream()
*/
class
StreamWrapper
{
public
$context
;
private
$filename
;
private
$protocol
=
'gridfs'
;
private
$mode
;
private
$gridFsStream
;
private
$collectionsWrapper
;
public
$id
;
private
$collectionsWrapper
;
private
$gridFSStream
;
private
$mode
;
public
function
openReadStream
()
{
$context
=
stream_context_get_options
(
$this
->
context
);
$this
->
gridFSStream
=
new
GridFSDownload
(
$this
->
collectionsWrapper
,
$context
[
'gridfs'
][
'file'
]);
$this
->
id
=
$this
->
gridFSStream
->
getId
();
return
true
;
}
public
function
openWriteStream
()
{
$context
=
stream_context_get_options
(
$this
->
context
);
$options
=
$context
[
'gridfs'
][
'uploadOptions'
];
$this
->
gridFSStream
=
new
GridFSUpload
(
$this
->
collectionsWrapper
,
$this
->
identifier
,
$options
);
$this
->
id
=
$this
->
gridFSStream
->
getId
();
return
true
;
}
/**
* Register the GridFS stream wrapper.
*/
...
...
@@ -27,63 +45,59 @@ class StreamWrapper
if
(
in_array
(
'gridfs'
,
stream_get_wrappers
()))
{
stream_wrapper_unregister
(
'gridfs'
);
}
stream_wrapper_register
(
'gridfs'
,
get_called_class
(),
STREAM_IS_URL
);
}
public
function
stream_write
(
$data
)
{
$this
->
gridFsStream
->
insertChunks
(
$data
);
return
strlen
(
$data
);
}
public
function
stream_read
(
$count
)
{
return
$this
->
gridFsStream
->
downloadNumBytes
(
$count
);
}
public
function
stream_eof
()
{
return
$this
->
gridFsStream
->
isEOF
();
stream_wrapper_register
(
'gridfs'
,
get_called_class
(),
\STREAM_IS_URL
);
}
public
function
stream_close
()
{
$this
->
gridF
s
Stream
->
close
();
$this
->
gridF
S
Stream
->
close
();
}
public
function
stream_stat
()
public
function
stream_eof
()
{
$stat
=
$this
->
getStatTemplate
();
$stat
[
7
]
=
$stat
[
'size'
]
=
$this
->
gridFsStream
->
getSize
();
return
$stat
;
return
$this
->
gridFSStream
->
isEOF
();
}
public
function
stream_open
(
$path
,
$mode
,
$options
,
&
$openedPath
)
{
$this
->
initProtocol
(
$path
);
$context
=
stream_context_get_options
(
$this
->
context
);
$this
->
collectionsWrapper
=
$context
[
'gridfs'
][
'collectionsWrapper'
];
$this
->
collectionsWrapper
=
$context
[
'gridfs'
][
'collectionsWrapper'
];
$this
->
mode
=
$mode
;
switch
(
$this
->
mode
)
{
case
'
w'
:
return
$this
->
openWrite
Stream
();
case
'
r'
:
return
$this
->
openRead
Stream
();
default
:
return
false
;
case
'
r'
:
return
$this
->
openRead
Stream
();
case
'
w'
:
return
$this
->
openWrite
Stream
();
default
:
return
false
;
}
}
public
function
openWriteStream
()
{
$context
=
stream_context_get_options
(
$this
->
context
);
$options
=
$context
[
'gridfs'
][
'uploadOptions'
];
$this
->
gridFsStream
=
new
GridFsUpload
(
$this
->
collectionsWrapper
,
$this
->
identifier
,
$options
);
$this
->
id
=
$this
->
gridFsStream
->
getId
();
return
true
;
public
function
stream_read
(
$count
)
{
return
$this
->
gridFSStream
->
downloadNumBytes
(
$count
);
}
public
function
openReadStream
()
{
$context
=
stream_context_get_options
(
$this
->
context
);
$this
->
gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
$context
[
'gridfs'
][
'file'
]);
$this
->
id
=
$this
->
gridFsStream
->
getId
();
return
true
;
public
function
stream_stat
()
{
$stat
=
$this
->
getStatTemplate
();
$stat
[
7
]
=
$stat
[
'size'
]
=
$this
->
gridFSStream
->
getSize
();
return
$stat
;
}
public
function
stream_write
(
$data
)
{
$this
->
gridFSStream
->
insertChunks
(
$data
);
return
strlen
(
$data
);
}
/**
* Gets a URL stat template with default values
* from https://github.com/aws/aws-sdk-php/blob/master/src/S3/StreamWrapper.php
* @return array
*/
* Gets a URL stat template with default values
* from https://github.com/aws/aws-sdk-php/blob/master/src/S3/StreamWrapper.php
* @return array
*/
private
function
getStatTemplate
()
{
return
[
...
...
@@ -102,10 +116,10 @@ class StreamWrapper
12
=>
-
1
,
'blocks'
=>
-
1
,
];
}
private
function
initProtocol
(
$path
)
{
$parsed_path
=
parse_url
(
$path
);
$this
->
databaseName
=
$parsed_path
[
"host"
];
$this
->
identifier
=
substr
(
$parsed_path
[
"path"
],
1
);
$this
->
identifier
=
substr
(
$parsed_path
[
'path'
],
1
);
}
}
tests/GridFS/BucketFunctionalTest.php
View file @
f2666e19
...
...
@@ -176,25 +176,25 @@ class BucketFunctionalTest extends FunctionalTestCase
$this
->
bucket
->
uploadFromStream
(
"test"
,
$this
->
generateStream
(
"bar"
));
$this
->
bucket
->
uploadFromStream
(
"test"
,
$this
->
generateStream
(
"baz"
));
$this
->
assertEquals
(
"foo"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
0
)));
$this
->
assertEquals
(
"bar"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
1
)));
$this
->
assertEquals
(
"baz"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
2
)));
$this
->
assertEquals
(
"foo"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
0
]
)));
$this
->
assertEquals
(
"bar"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
1
]
)));
$this
->
assertEquals
(
"baz"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
2
]
)));
$this
->
assertEquals
(
"baz"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
-
1
)));
$this
->
assertEquals
(
"bar"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
-
2
)));
$this
->
assertEquals
(
"foo"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
-
3
)));
$this
->
assertEquals
(
"baz"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
-
1
]
)));
$this
->
assertEquals
(
"bar"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
-
2
]
)));
$this
->
assertEquals
(
"foo"
,
stream_get_contents
(
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
-
3
]
)));
$fileNotFound
=
'\MongoDB\Exception\GridFSFileNotFoundException'
;
$error
=
null
;
try
{
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
3
);
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
3
]
);
}
catch
(
\MongoDB\Exception\Exception
$e
)
{
$error
=
$e
;
}
$this
->
assertTrue
(
$error
instanceof
$fileNotFound
);
$error
=
null
;
try
{
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
-
4
);
$this
->
bucket
->
openDownloadStreamByName
(
"test"
,
[
'revision'
=>
-
4
]
);
}
catch
(
\MongoDB\Exception\Exception
$e
)
{
$error
=
$e
;
}
...
...
tests/GridFS/GridF
s
StreamTest.php
→
tests/GridFS/GridF
S
StreamTest.php
View file @
f2666e19
...
...
@@ -7,12 +7,12 @@ use MongoDB\GridFS;
/**
* Functional tests for the Bucket class.
*/
class
GridF
s
StreamTest
extends
FunctionalTestCase
class
GridF
S
StreamTest
extends
FunctionalTestCase
{
public
function
testBasic
()
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
->
insertChunks
(
"hello world"
);
$id
=
$upload
->
getId
();
$upload
->
close
();
...
...
@@ -22,7 +22,7 @@ class GridFsStreamTest extends FunctionalTestCase
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
"_id"
=>
$id
]);
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$download
->
downloadToStream
(
$stream
);
rewind
(
$stream
);
...
...
@@ -31,7 +31,7 @@ class GridFsStreamTest extends FunctionalTestCase
fclose
(
$stream
);
#make sure it's still there!
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$download
->
downloadToStream
(
$stream
);
rewind
(
$stream
);
...
...
@@ -39,7 +39,7 @@ class GridFsStreamTest extends FunctionalTestCase
$this
->
assertEquals
(
"hello world"
,
$contents
);
fclose
(
$stream
);
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$id
=
$upload
->
getId
();
$upload
->
close
();
...
...
@@ -47,7 +47,7 @@ class GridFsStreamTest extends FunctionalTestCase
$this
->
assertEquals
(
1
,
$this
->
collectionsWrapper
->
getChunksCollection
()
->
count
());
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
"_id"
=>
$id
]);
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$download
->
downloadToStream
(
$stream
);
rewind
(
$stream
);
...
...
@@ -58,7 +58,7 @@ class GridFsStreamTest extends FunctionalTestCase
public
function
testMd5
()
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
->
insertChunks
(
"hello world
\n
"
);
$id
=
$upload
->
getId
();
$upload
->
close
();
...
...
@@ -68,7 +68,7 @@ class GridFsStreamTest extends FunctionalTestCase
}
public
function
testUploadDefaultOpts
()
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$this
->
assertTrue
(
$upload
->
getId
()
instanceof
\MongoDB\BSON\ObjectId
);
$this
->
assertTrue
(
$upload
->
getFile
()[
"uploadDate"
]
instanceof
\MongoDB\BSON\UTCDateTime
);
...
...
@@ -89,7 +89,7 @@ class GridFsStreamTest extends FunctionalTestCase
"aliases"
=>
[
"foo"
,
"bar"
],
"metadata"
=>
[
"foo"
=>
1
,
"bar"
=>
2
]
];
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
$this
->
assertEquals
(
$upload
->
getChunkSize
(),
1
);
$this
->
assertEquals
(
$upload
->
getFile
()[
"contentType"
],
"text/html"
);
$this
->
assertEquals
(
$upload
->
getFile
()[
"aliases"
],
[
"foo"
,
"bar"
]);
...
...
@@ -97,11 +97,11 @@ class GridFsStreamTest extends FunctionalTestCase
}
public
function
testDownloadDefaultOpts
()
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
->
close
();
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
"_id"
=>
$upload
->
getId
()]);
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
->
close
();
$this
->
assertEquals
(
$upload
->
getId
(),
$download
->
getId
());
...
...
@@ -120,12 +120,12 @@ class GridFsStreamTest extends FunctionalTestCase
"aliases"
=>
[
"foo"
,
"bar"
],
"metadata"
=>
[
"foo"
=>
1
,
"bar"
=>
2
]
];
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
$upload
->
insertChunks
(
"hello world"
);
$upload
->
close
();
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
"_id"
=>
$upload
->
getId
()]);
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$this
->
assertEquals
(
"test"
,
$download
->
getFile
()
->
filename
);
$this
->
assertEquals
(
$upload
->
getId
(),
$download
->
getId
());
...
...
@@ -141,7 +141,7 @@ class GridFsStreamTest extends FunctionalTestCase
*/
public
function
testInsertChunks
(
$data
)
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
->
insertChunks
(
$data
);
$upload
->
close
();
$stream
=
$this
->
bucket
->
openDownloadStream
(
$upload
->
getId
());
...
...
@@ -154,7 +154,7 @@ class GridFsStreamTest extends FunctionalTestCase
for
(
$i
=
0
;
$i
<
255
*
1024
+
1000
;
$i
++
){
$toUpload
.=
"a"
;
}
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
);
$upload
->
insertChunks
(
$toUpload
);
$upload
->
close
();
...
...
@@ -170,7 +170,7 @@ class GridFsStreamTest extends FunctionalTestCase
public
function
testSmallChunks
(
$data
)
{
$options
=
[
"chunkSizeBytes"
=>
1
];
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
$upload
->
insertChunks
(
$data
);
$upload
->
close
();
...
...
@@ -182,11 +182,11 @@ class GridFsStreamTest extends FunctionalTestCase
}
public
function
testMultipleReads
()
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
,
[
"chunkSizeBytes"
=>
3
]);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
,
[
"chunkSizeBytes"
=>
3
]);
$upload
->
insertChunks
(
"hello world"
);
$upload
->
close
();
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
"_id"
=>
$upload
->
getId
()]);
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$this
->
assertEquals
(
"he"
,
$download
->
downloadNumBytes
(
2
));
$this
->
assertEquals
(
"ll"
,
$download
->
downloadNumBytes
(
2
));
$this
->
assertEquals
(
"o "
,
$download
->
downloadNumBytes
(
2
));
...
...
@@ -202,11 +202,11 @@ class GridFsStreamTest extends FunctionalTestCase
*/
public
function
testProvidedMultipleReads
(
$data
)
{
$upload
=
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
,
[
"chunkSizeBytes"
=>
rand
(
1
,
5
)]);
$upload
=
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
,
[
"chunkSizeBytes"
=>
rand
(
1
,
5
)]);
$upload
->
insertChunks
(
$data
);
$upload
->
close
();
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
"_id"
=>
$upload
->
getId
()]);
$download
=
new
\MongoDB\GridFS\GridF
s
Download
(
$this
->
collectionsWrapper
,
$file
);
$download
=
new
\MongoDB\GridFS\GridF
S
Download
(
$this
->
collectionsWrapper
,
$file
);
$readPos
=
0
;
while
(
$readPos
<
strlen
(
$data
)){
...
...
@@ -227,7 +227,7 @@ class GridFsStreamTest extends FunctionalTestCase
*/
public
function
testUploadConstructorOptionTypeChecks
(
array
$options
)
{
new
\MongoDB\GridFS\GridF
s
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
new
\MongoDB\GridFS\GridF
S
Upload
(
$this
->
collectionsWrapper
,
"test"
,
$options
);
}
public
function
provideInvalidUploadConstructorOptions
()
...
...
@@ -248,22 +248,4 @@ class GridFsStreamTest extends FunctionalTestCase
}
return
$options
;
}
/**
* @expectedException \MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDownloadConstructorFile
*/
public
function
testDownloadConstructorFileCheck
(
$file
)
{
$download
=
new
\MongoDB\GridFS\GridFsDownload
(
$this
->
collectionsWrapper
,
$file
);
}
public
function
provideInvalidDownloadConstructorFile
()
{
$files
=
[];
$invalidFiles
=
[
123
,
3.14
,
true
,
[]];
foreach
(
$invalidFiles
as
$value
)
{
$files
[][]
=
$value
;
}
return
$files
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment