4
* (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
5
* (c) Copyright 2014 Rackspace US, Inc.
7
* Licensed under the Apache License, Version 2.0 (the "License"); you may
8
* not use this file except in compliance with the License. You may obtain
9
* a copy of the License at
11
* http://www.apache.org/licenses/LICENSE-2.0
13
* Unless required by applicable law or agreed to in writing, software
14
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
* License for the specific language governing permissions and limitations
20
namespace OpenStack\ObjectStore\v1\Resource;
22
use OpenStack\Common\Exception;
23
use OpenStack\Common\Transport\ClientInterface;
24
use OpenStack\Common\Transport\Exception\ResourceNotFoundException;
25
use OpenStack\Common\Transport\Guzzle\GuzzleAdapter;
28
* A container in an ObjectStorage.
30
* An Object Storage instance is divided into containers, where each
31
* container can hold an arbitrary number of objects. This class
32
* describes a container, providing access to its properties and to the
33
* objects stored inside of it.
35
* Containers are iterable, which means you can iterate over a container
36
* and access each file inside of it.
38
* Typically, containers are created using ObjectStorage::createContainer().
39
* They are retrieved using ObjectStorage::container() or
40
* ObjectStorage::containers().
43
* use \OpenStack\ObjectStore\v1\ObjectStorage;
44
* use \OpenStack\ObjectStore\v1\Resource\Container;
45
* use \OpenStack\ObjectStore\v1\Resource\Object;
47
* // Create a new ObjectStorage instance
48
* // For more examples on authenticating and creating an ObjectStorage
49
* // instance see functions below
50
* // @see \OpenStack\ObjectStore\v1\ObjectStorage::newFromIdentity()
51
* // @see \OpenStack\ObjectStore\v1\ObjectStorage::newFromServiceCatalog()
52
* $ostore = \OpenStack\ObjectStore\v1\ObjectStorage::newFromIdentity($yourIdentity, $yourRegion, $yourTransportClient);
54
* // Get the container called 'foo'.
55
* $container = $store->container('foo');
57
* // Create an object.
58
* $obj = new Object('bar.txt');
59
* $obj->setContent('Example content.', 'text/plain');
61
* // Save the new object in the container.
62
* $container->save($obj);
66
* Once you have a Container, you manipulate objects inside of the
69
* @todo Add support for container metadata.
71
class Container implements \Countable, \IteratorAggregate
74
* The prefix for any piece of metadata passed in HTTP headers.
76
const METADATA_HEADER_PREFIX = 'X-Object-Meta-';
77
const CONTAINER_METADATA_HEADER_PREFIX = 'X-Container-Meta-';
79
//protected $properties = array();
80
protected $name = null;
82
// These were both changed from 0 to null to allow lazy loading.
83
protected $count = null;
84
protected $bytes = null;
98
* Transform a metadata array into headers.
100
* This is used when storing an object in a container.
102
* @param array $metadata An associative array of metadata. Metadata is not
103
* escaped in any way (there is no codified spec by which to escape), so
104
* make sure that keys are alphanumeric (dashes allowed) and values are
105
* ASCII-armored with no newlines.
106
* @param string $prefix A prefix for the metadata headers.
108
* @return array An array of headers.
110
* @see http://docs.openstack.org/bexar/openstack-object-storage/developer/content/ch03s03.html#d5e635
111
* @see http://docs.openstack.org/bexar/openstack-object-storage/developer/content/ch03s03.html#d5e700
113
public static function generateMetadataHeaders(array $metadata, $prefix = null)
115
if (empty($prefix)) {
116
$prefix = Container::METADATA_HEADER_PREFIX;
119
foreach ($metadata as $key => $val) {
120
$headers[$prefix . $key] = $val;
126
* Create an object URL.
128
* Given a base URL and an object name, create an object URL.
130
* This is useful because object names can contain certain characters
131
* (namely slashes (`/`)) that are normally URLencoded when they appear
132
* inside of path sequences.
134
* Swift does not distinguish between `%2F` and a slash character, so
135
* this is not strictly necessary.
137
* @param string $base The base URL. This is not altered; it is just prepended
138
* to the returned string.
139
* @param string $oname The name of the object.
141
* @return string The URL to the object. Characters that need escaping will be
142
* escaped, while slash characters are not. Thus, the URL will
145
public static function objectUrl($base, $oname)
147
if (strpos($oname, '/') === false) {
148
return $base . '/' . rawurlencode($oname);
151
$oParts = explode('/', $oname);
153
foreach ($oParts as $part) {
154
$buffer[] = rawurlencode($part);
156
$newname = implode('/', $buffer);
158
return $base . '/' . $newname;
162
* Extract object attributes from HTTP headers.
164
* When OpenStack sends object attributes, it sometimes embeds them in
165
* HTTP headers with a prefix. This function parses the headers and
166
* returns the attributes as name/value pairs.
168
* Note that no decoding (other than the minimum amount necessary) is
169
* done to the attribute names or values. The Open Stack Swift
170
* documentation does not prescribe encoding standards for name or
171
* value data, so it is left up to implementors to choose their own
174
* @param array $headers An associative array of HTTP headers.
175
* @param string $prefix The prefix on metadata headers.
177
* @return array An associative array of name/value attribute pairs.
179
public static function extractHeaderAttributes($headers, $prefix = null)
181
if (empty($prefix)) {
182
$prefix = Container::METADATA_HEADER_PREFIX;
185
$offset = strlen($prefix);
186
foreach ($headers as $header => $value) {
188
$index = strpos($header, $prefix);
190
$key = substr($header, $offset);
191
$attributes[$key] = $value;
199
* Create a new Container from JSON data.
201
* This is used in lieue of a standard constructor when
202
* fetching containers from ObjectStorage.
204
* @param array $jsonArray An associative array as returned by
205
* json_decode($foo, true);
206
* @param string $token The auth token.
207
* @param string $url The base URL. The container name is automatically
208
* appended to this at construction time.
209
* @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client.
211
* @return \OpenStack\ObjectStore\v1\Resource\Container A new container object.
213
public static function newFromJSON($jsonArray, $token, $url, ClientInterface $client = null)
215
$container = new Container($jsonArray['name'], null, null, $client);
217
$container->baseUrl = $url;
219
$container->url = $url . '/' . rawurlencode($jsonArray['name']);
220
$container->token = $token;
222
// Access to count and bytes is basically controlled. This is is to
223
// prevent a local copy of the object from getting out of sync with
225
if (!empty($jsonArray['count'])) {
226
$container->count = $jsonArray['count'];
229
if (!empty($jsonArray['bytes'])) {
230
$container->bytes = $jsonArray['bytes'];
233
//syslog(LOG_WARNING, print_r($jsonArray, true));
238
* Given an OpenStack HTTP response, build a Container.
240
* This factory is intended for use by low-level libraries. In most
241
* cases, the standard constructor is preferred for client-size
242
* Container initialization.
244
* @param string $name The name of the container.
245
* @param object $response \OpenStack\Common\Transport\Response The HTTP response object from the Transporter layer
246
* @param string $token The auth token.
247
* @param string $url The base URL. The container name is automatically
248
* appended to this at construction time.
249
* @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client.
251
* @return \OpenStack\ObjectStore\v1\Resource\Container The Container object, initialized and ready for use.
253
public static function newFromResponse($name, $response, $token, $url, ClientInterface $client = null)
255
$container = new Container($name, null, null, $client);
256
$container->bytes = $response->getHeader('X-Container-Bytes-Used', 0);
257
$container->count = $response->getHeader('X-Container-Object-Count', 0);
258
$container->baseUrl = $url;
259
$container->url = $url . '/' . rawurlencode($name);
260
$container->token = $token;
262
$headers = self::reformatHeaders($response->getHeaders());
264
$container->acl = ACL::newFromHeaders($headers);
266
$prefix = Container::CONTAINER_METADATA_HEADER_PREFIX;
267
$metadata = Container::extractHeaderAttributes($headers, $prefix);
268
$container->setMetadata($metadata);
274
* Construct a new Container.
276
* Typically a container should be created by ObjectStorage::createContainer().
277
* Get existing containers with ObjectStorage::container() or
278
* ObjectStorage::containers(). Using the constructor directly has some
279
* side effects of which you should be aware.
281
* Simply creating a container does not save the container remotely.
283
* Also, this does no checking of the underlying container. That is, simply
284
* constructing a Container in no way guarantees that such a container exists
285
* on the origin object store.
287
* The constructor involves a selective lazy loading. If a new container is created,
288
* and one of its accessors is called before the accessed values are initialized, then
289
* this will make a network round-trip to get the container from the remote server.
291
* Containers loaded from ObjectStorage::container() or Container::newFromRemote()
292
* will have all of the necessary values set, and thus will not require an extra network
293
* transaction to fetch properties.
295
* The practical result of this:
297
* - If you are creating a new container, it is best to do so with
298
* ObjectStorage::createContainer().
299
* - If you are manipulating an existing container, it is best to load the
300
* container with ObjectStorage::container().
301
* - If you are simply using the container to fetch resources from the
302
* container, you may wish to use `new Container($name, $url, $token)`
303
* and then load objects from that container. Note, however, that
304
* manipulating the container directly will likely involve an extra HTTP
305
* transaction to load the container data.
306
* - When in doubt, use the ObjectStorage methods. That is always the safer
309
* @param string $name The name.
310
* @param string $url The full URL to the container.
311
* @param string $token The auth token.
312
* @param \OpenStack\Common\Transport\ClientInterface $client A HTTP transport client.
314
public function __construct($name , $url = null, $token = null, ClientInterface $client = null)
318
$this->token = $token;
320
// Guzzle is the default client to use.
321
if (is_null($client)) {
322
$this->client = GuzzleAdapter::create();
324
$this->client = $client;
329
* Get the name of this container.
331
* @return string The name of the container.
333
public function name()
339
* Get the number of bytes in this container.
341
* @return int The number of bytes in this container.
343
public function bytes()
345
if (is_null($this->bytes)) {
346
$this->loadExtraData();
353
* Get the container metadata.
355
* Metadata (also called tags) are name/value pairs that can be
356
* attached to a container.
358
* Names can be no longer than 128 characters, and values can be no
359
* more than 256. UTF-8 or ASCII characters are allowed, though ASCII
360
* seems to be preferred.
362
* If the container was loaded from a container listing, the metadata
363
* will be fetched in a new HTTP request. This is because container
364
* listings do not supply the metadata, while loading a container
367
* @return array An array of metadata name/value pairs.
369
public function metadata()
371
// If created from JSON, metadata does not get fetched.
372
if (!isset($this->metadata)) {
373
$this->loadExtraData();
376
return $this->metadata;
380
* Set the tags on the container.
382
* Container metadata (sometimes called "tags") provides a way of
383
* storing arbitrary name/value pairs on a container.
385
* Since saving a container is a function of the ObjectStorage
386
* itself, if you change the metadta, you will need to call
387
* ObjectStorage::updateContainer() to save the new container metadata
388
* on the remote object storage.
390
* (Similarly, when it comes to objects, an object's metdata is saved
393
* Names can be no longer than 128 characters, and values can be no
394
* more than 256. UTF-8 or ASCII characters are allowed, though ASCII
395
* seems to be preferred.
397
* @return \OpenStack\ObjectStore\v1\Resource\Container $this so the method can
398
* be used in chaining.
400
public function setMetadata($metadata)
402
$this->metadata = $metadata;
408
* Get the number of items in this container.
410
* Since Container implements Countable, the PHP builtin count() can be used
411
* on a Container instance:
414
* count($container) === $container->count();
417
* @return int The number of items in this container.
419
public function count()
421
if (is_null($this->count)) {
422
$this->loadExtraData();
429
* Save an Object into Object Storage.
431
* This takes an \OpenStack\ObjectStore\v1\Resource\Object
432
* and stores it in the given container in the present
433
* container on the remote object store.
435
* @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to
437
* @param resource $file An optional file argument that, if set, will be
438
* treated as the contents of the object.
440
* @return boolean true if the object was saved.
442
* @throws \OpenStack\Common\Transport\Exception\LengthRequiredException if the Content-Length could not be
443
* determined and chunked encoding was
444
* not enabled. This should not occur for
445
* this class, which always automatically
446
* generates Content-Length headers.
447
* However, subclasses could generate
449
* @throws \OpenStack\Common\Transport\Exception\UnprocessableEntityException if the checksum passed here does not
450
* match the checksum calculated remotely.
451
* @throws \OpenStack\Common\Exception when an unexpected (usually
452
* network-related) error condition arises.
454
public function save(Object $obj, $file = null)
456
if (empty($this->token)) {
457
throw new Exception('Container does not have an auth token.');
459
if (empty($this->url)) {
460
throw new Exception('Container does not have a URL to send data.');
463
//$url = $this->url . '/' . rawurlencode($obj->name());
464
$url = self::objectUrl($this->url, $obj->name());
466
// See if we have any metadata.
468
$md = $obj->metadata();
470
$headers = self::generateMetadataHeaders($md, Container::METADATA_HEADER_PREFIX);
473
// Set the content type.
474
$headers['Content-Type'] = $obj->contentType();
477
// Add content encoding, if necessary.
478
$encoding = $obj->encoding();
479
if (!empty($encoding)) {
480
$headers['Content-Encoding'] = rawurlencode($encoding);
483
// Add content disposition, if necessary.
484
$disposition = $obj->disposition();
485
if (!empty($disposition)) {
486
$headers['Content-Disposition'] = $disposition;
490
$headers['X-Auth-Token'] = $this->token;
492
// Add any custom headers:
493
$moreHeaders = $obj->additionalHeaders();
494
if (!empty($moreHeaders)) {
495
$headers += $moreHeaders;
499
// Now build up the rest of the headers:
500
$headers['Etag'] = $obj->eTag();
502
// If chunked, we set transfer encoding; else
503
// we set the content length.
504
if ($obj->isChunked()) {
505
// How do we handle this? Does the underlying
506
// stream wrapper pay any attention to this?
507
$headers['Transfer-Encoding'] = 'chunked';
509
$headers['Content-Length'] = $obj->contentLength();
511
$response = $this->client->put($url, $obj->content(), ['headers' => $headers]);
516
// XXX: What do we do about Content-Length header?
517
//$headers['Transfer-Encoding'] = 'chunked';
518
$stat = fstat($file);
519
$headers['Content-Length'] = $stat['size'];
522
$hash = hash_init('md5');
523
hash_update_stream($hash, $file);
524
$etag = hash_final($hash);
525
$headers['Etag'] = $etag;
527
// Not sure if this is necessary:
530
$response = $this->client->put($url, $file, ['headers' => $headers]);
533
if ($response->getStatusCode() != 201) {
534
throw new Exception('An unknown error occurred while saving: ' . $response->status());
541
* Update an object's metadata.
543
* This updates the metadata on an object without modifying anything
544
* else. This is a convenient way to set additional metadata without
545
* having to re-upload a potentially large object.
547
* Swift's behavior during this operation is sometimes unpredictable,
548
* particularly in cases where custom headers have been set.
551
* @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to update.
553
* @return boolean true if the metadata was updated.
555
* @throws \OpenStack\Common\Transport\Exception\ResourceNotFoundException if the object does not already
556
* exist on the object storage.
558
public function updateMetadata(Object $obj)
560
$url = self::objectUrl($this->url, $obj->name());
561
$headers = ['X-Auth-Token' => $this->token];
563
// See if we have any metadata. We post this even if there
565
$metadata = $obj->metadata();
566
if (!empty($metadata)) {
567
$headers += self::generateMetadataHeaders($metadata, Container::METADATA_HEADER_PREFIX);
570
// In spite of the documentation's claim to the contrary,
571
// content type IS reset during this operation.
572
$headers['Content-Type'] = $obj->contentType();
574
// The POST verb is for updating headers.
576
$response = $this->client->post($url, $obj->content(), ['headers' => $headers]);
578
if ($response->getStatusCode() != 202) {
579
throw new Exception(sprintf(
580
"An unknown error occurred while saving: %d", $response->status()
588
* Copy an object to another place in object storage.
590
* An object can be copied within a container. Essentially, this will
591
* give you duplicates of the file, each with a new name.
593
* An object can be copied to another container if the name of the
594
* other container is specified, and if that container already exists.
596
* Note that there is no MOVE operation. You must copy and then DELETE
597
* in order to achieve that.
599
* @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to
600
* copy. This object MUST already be saved on the remote server. The body of
601
* the object is not sent. Instead, the copy operation is performed on the
602
* remote server. You can, and probably should, use a RemoteObject here.
603
* @param string $newName The new name of this object. If you are copying a
604
* cross containers, the name can be the same. If you are copying within
605
* the same container, though, you will need to supply a new name.
606
* @param string $container The name of the alternate container. If this is
607
* set, the object will be saved into this container. If this is not sent,
608
* the copy will be performed inside of the original container.
610
public function copy(Object $obj, $newName, $container = null)
612
$sourceUrl = self::objectUrl($this->url, $obj->name());
614
if (empty($newName)) {
615
throw new Exception("An object name is required to copy the object.");
618
// Figure out what container we store in.
619
if (empty($container)) {
620
$container = $this->name;
622
$container = rawurlencode($container);
623
$destUrl = self::objectUrl('/' . $container, $newName);
626
'X-Auth-Token' => $this->token,
627
'Destination' => $destUrl,
628
'Content-Type' => $obj->contentType(),
631
$response = $this->client->send(
632
$this->client->createRequest('COPY', $sourceUrl, null, ['headers' => $headers])
635
if ($response->getStatusCode() != 201) {
636
throw new Exception("An unknown condition occurred during copy. " . $response->getStatusCode());
643
* Get the object with the given name.
645
* This fetches a single object with the given name. It downloads the
646
* entire object at once. This is useful if the object is small (under
647
* a few megabytes) and the content of the object will be used. For
648
* example, this is the right operation for accessing a text file
649
* whose contents will be processed.
651
* For larger files or files whose content may never be accessed, use
652
* proxyObject(), which delays loading the content until one of its
653
* content methods (e.g. RemoteObject::content()) is called.
655
* This does not yet support the following features of Swift:
657
* - Byte range queries.
658
* - If-Modified-Since/If-Unmodified-Since
659
* - If-Match/If-None-Match
661
* @param string $name The name of the object to load.
663
* @return \OpenStack\ObjectStore\v1\Resource\RemoteObject A remote object with the content already stored locally.
665
public function object($name)
667
$url = self::objectUrl($this->url, $name);
668
$headers = ['X-Auth-Token' => $this->token];
670
$response = $this->client->get($url, ['headers' => $headers]);
672
if ($response->getStatusCode() != 200) {
673
throw new Exception('An unknown error occurred while saving: ' . $response->status());
676
$remoteObject = RemoteObject::newFromHeaders($name, self::reformatHeaders($response->getHeaders()), $this->token, $url, $this->client);
677
$remoteObject->setContent($response->getBody());
679
return $remoteObject;
683
* Fetch an object, but delay fetching its contents.
685
* This retrieves all of the information about an object except for
686
* its contents. Size, hash, metadata, and modification date
687
* information are all retrieved and wrapped.
689
* The data comes back as a RemoteObject, which can be used to
690
* transparently fetch the object's content, too.
694
* The regular object() call will fetch an entire object, including
695
* its content. This may not be desireable for cases where the object
698
* This method can fetch the relevant metadata, but delay fetching
699
* the content until it is actually needed.
701
* Since RemoteObject extends Object, all of the calls that can be
702
* made to an Object can also be made to a RemoteObject. Be aware,
703
* though, that calling RemoteObject::content() will initiate another
706
* @param string $name The name of the object to fetch.
708
* @return \OpenStack\ObjectStore\v1\Resource\RemoteObject A remote object ready for use.
710
public function proxyObject($name)
712
$url = self::objectUrl($this->url, $name);
713
$headers = ['X-Auth-Token' => $this->token];
715
$response = $this->client->head($url, ['headers' => $headers]);
717
if ($response->getStatusCode() != 200) {
718
throw new Exception('An unknown error occurred while saving: ' . $response->status());
721
$headers = self::reformatHeaders($response->getHeaders());
723
return RemoteObject::newFromHeaders($name, $headers, $this->token, $url, $this->client);
727
* Get a list of objects in this container.
729
* This will return a list of objects in the container. With no parameters, it
730
* will attempt to return a listing of all objects in the container. However,
731
* by setting contraints, you can retrieve only a specific subset of objects.
733
* Note that OpenStacks Swift will return no more than 10,000 objects
734
* per request. When dealing with large datasets, you are encouraged
739
* Paging is done with a combination of a limit and a marker. The
740
* limit is an integer indicating the maximum number of items to
741
* return. The marker is the string name of an object. Typically, this
742
* is the last object in the previously returned set. The next batch
743
* will begin with the next item after the marker (assuming the marker
746
* @param int $limit An integer indicating the maximum number of items to
747
* return. This cannot be greater than the Swift maximum (10k).
748
* @param string $marker The name of the object to start with. The query will
749
* begin with the next object AFTER this one.
751
* @return array List of RemoteObject or Subdir instances.
753
public function objects($limit = null, $marker = null)
755
return $this->objectQuery([], $limit, $marker);
759
* Retrieve a list of Objects with the given prefix.
761
* Object Storage containers support directory-like organization. To
762
* get a list of items inside of a particular "subdirectory", provide
763
* the directory name as a "prefix". This will return only objects
764
* that begin with that prefix.
766
* (Directory-like behavior is also supported by using "directory
767
* markers". See objectsByPath().)
771
* Prefixes are basically substring patterns that are matched against
772
* files on the remote object storage.
774
* When a prefix is used, object storage will begin to return not just
775
* Object instsances, but also Subdir instances. A Subdir is simply a
776
* container for a "path name".
780
* Object Storage (OpenStack Swift) does not have a native concept of
781
* files and directories when it comes to paths. Instead, it merely
782
* represents them and simulates their behavior under specific
785
* The default behavior (when prefixes are used) is to treat the '/'
786
* character as a delimiter. Thus, when it encounters a name like
787
* this: `foo/bar/baz.txt` and the prefix is `foo/`, it will
788
* parse return a Subdir called `foo/bar`.
790
* Similarly, if you store a file called `foo:bar:baz.txt` and then
791
* set the delimiter to `:` and the prefix to `foo:`, it will return
792
* the Subdir `foo:bar`. However, merely setting the delimiter back to
793
* `/` will not allow you to query `foo/bar` and get the contents of
796
* Setting $delimiter will tell the Object Storage server which
797
* character to parse the filenames on. This means that if you use
798
* delimiters other than '/', you need to be very consistent with your
799
* usage or else you may get surprising results.
801
* @param string $prefix The leading prefix.
802
* @param string $delimiter The character used to delimit names. By default,
804
* @param int $limit An integer indicating the maximum number of items to
805
* return. This cannot be greater than the Swift maximum (10k).
806
* @param string $marker The name of the object to start with. The query will
807
* begin with the next object AFTER this one.
809
* @return array List of RemoteObject or Subdir instances.
811
public function objectsWithPrefix($prefix, $delimiter = '/', $limit = null, $marker = null)
815
'delimiter' => $delimiter
818
return $this->objectQuery($params, $limit, $marker);
822
* Specify a path (subdirectory) to traverse.
824
* OpenStack Swift provides two basic ways to handle directory-like
825
* structures. The first is using a prefix (see objectsWithPrefix()).
826
* The second is to create directory markers and use a path.
828
* A directory marker is just a file with a name that is
829
* directory-like. You create it exactly as you create any other file.
830
* Typically, it is 0 bytes long.
833
* $dir = new Object('a/b/c', '');
834
* $container->save($dir);
837
* Using objectsByPath() with directory markers will return a list of
838
* Object instances, some of which are regular files, and some of
839
* which are just empty directory marker files. When creating
840
* directory markers, you may wish to set metadata or content-type
841
* information indicating that they are directory markers.
843
* At one point, the OpenStack documentation suggested that the path
844
* method was legacy. More recent versions of the documentation no
845
* longer indicate this.
847
* @param string $path The path prefix.
848
* @param string $delimiter The character used to delimit names. By default,
850
* @param int $limit An integer indicating the maximum number of items to
851
* return. This cannot be greater than the Swift maximum (10k).
852
* @param string $marker The name of the object to start with. The query will
853
* begin with the next object AFTER this one.
855
public function objectsByPath($path, $delimiter = '/', $limit = null, $marker = null)
859
'delimiter' => $delimiter,
862
return $this->objectQuery($params, $limit, $marker);
866
* Get the URL to this container.
868
* Any container that has been created will have a valid URL. If the
869
* Container was set to be public (See
870
* ObjectStorage::createContainer()) will be accessible by this URL.
872
* @return string The URL.
874
public function url()
882
* Currently, if the ACL wasn't added during object construction,
883
* calling acl() will trigger a request to the remote server to fetch
884
* the ACL. Since only some Swift calls return ACL data, this is an
885
* unavoidable artifact.
887
* Calling this on a Container that has not been stored on the remote
888
* ObjectStorage will produce an error. However, this should not be an
889
* issue, since containers should always come from one of the
890
* ObjectStorage methods.
892
* @todo Determine how to get the ACL from JSON data.
894
* @return \OpenStack\ObjectStore\v1\Resource\ACL An ACL, or null if the ACL could not be retrieved.
896
public function acl()
898
if (!isset($this->acl)) {
899
$this->loadExtraData();
906
* Get missing fields.
908
* Not all containers come fully instantiated. This method is sometimes
909
* called to "fill in" missing fields.
911
* @return \OpenStack\ObjectStore\v1\Resource\Container
913
protected function loadExtraData()
915
// If URL and token are empty, we are dealing with a local item that
916
// has not been saved, and was not created with Container::createContainer().
917
// We treat this as an error condition.
918
if (empty($this->url) || empty($this->token)) {
919
throw new Exception('Remote data cannot be fetched. A Token and endpoint URL are required.');
922
// Do a GET on $url to fetch headers.
923
$headers = ['X-Auth-Token' => $this->token];
924
$response = $this->client->get($this->url, ['headers' => $headers]);
926
$headers = self::reformatHeaders($response->getHeaders());
928
$this->acl = ACL::newFromHeaders($headers);
930
// Update size and count.
931
$this->bytes = $response->getHeader('X-Container-Bytes-Used', 0);
932
$this->count = $response->getHeader('X-Container-Object-Count', 0);
935
$prefix = Container::CONTAINER_METADATA_HEADER_PREFIX;
936
$this->setMetadata(Container::extractHeaderAttributes($headers, $prefix));
942
* Perform the HTTP query for a list of objects and de-serialize the
945
protected function objectQuery($params = [], $limit = null, $marker = null)
948
$params['limit'] = (int) $limit;
949
if (!empty($marker)) {
950
$params['marker'] = (string) $marker;
954
// We always want JSON.
955
$params['format'] = 'json';
957
$query = http_build_query($params);
958
$query = str_replace('%2F', '/', $query);
959
$url = $this->url . '?' . $query;
961
$headers = ['X-Auth-Token' => $this->token];
963
$response = $this->client->get($url, ['headers' => $headers]);
965
// The only codes that should be returned are 200 and the ones
966
// already thrown by GET.
967
if ($response->getStatusCode() != 200) {
968
throw new Exception('An unknown exception occurred while processing the request.');
971
$json = $response->json();
973
// Turn the array into a list of RemoteObject instances.
975
foreach ($json as $item) {
976
if (!empty($item['subdir'])) {
977
$list[] = new Subdir($item['subdir'], $params['delimiter']);
978
} elseif (empty($item['name'])) {
979
throw new Exception('Unexpected entity returned.');
981
//$url = $this->url . '/' . rawurlencode($item['name']);
982
$url = self::objectUrl($this->url, $item['name']);
983
$list[] = RemoteObject::newFromJSON($item, $this->token, $url, $this->client);
991
* Return the iterator of contents.
993
* A Container is Iterable. This means that you can use a container in
994
* a `foreach` loop directly:
997
* foreach ($container as $object) {
998
* print $object->name();
1002
* The above is equivalent to doing the following:
1005
* $objects = $container->objects();
1006
* foreach ($objects as $object) {
1007
* print $object->name();
1011
* Note that there is no way to pass any constraints into an iterator.
1012
* You cannot limit the number of items, set an marker, or add a
1015
public function getIterator()
1017
return new \ArrayIterator($this->objects());
1021
* Remove the named object from storage.
1023
* @param string $name The name of the object to remove.
1025
* @return boolean true if the file was deleted, false if no such file is
1028
public function delete($name)
1030
$url = self::objectUrl($this->url, $name);
1032
'X-Auth-Token' => $this->token,
1036
$response = $this->client->delete($url, ['headers' => $headers]);
1037
} catch (ResourceNotFoundException $e) {
1041
if ($response->getStatusCode() != 204) {
1042
throw new Exception(sprintf(
1043
"An unknown exception occured while deleting %s", $name
1051
* Reformat the headers array to remove a nested array.
1053
* For example, headers coming in could be in the format:
1056
* 'Content-Type' => [
1061
* This method would reformat the array into:
1064
* 'Content-Type' => 'Foo',
1067
* Note, for cases where multiple values for a header are needed this method
1068
* should not be used.
1070
* @param array $headers A headers array from the response.
1072
* @return array A new shallower array.
1074
public static function reformatHeaders(array $headers)
1078
foreach ($headers as $name => $header) {
1079
$newHeaders[$name] = $header[0];