我的图像存储在谷歌云存储中。它已经完成VichUploaderBundle
并KnpGaufretteBunlde
取得了成功。以下是我如何配置它们:
服务
services:
app.google_cloud_storage.service:
class: \Google_Service_Storage
factory: [Minn\AdsBundle\Factory\GoogleCloudStorageServiceFactory, createService]
minn.liip_imagine.cache.resolver.gcs:
class: Minn\AdsBundle\Resolver\GoogleCloudStorageResolver
arguments:
- @logger
- @app.google_cloud_storage.service
- '%bucket%'
- 'public-write'
knp_gaufrette 和 vich_uploader 配置
knp_gaufrette:
stream_wrapper: ~
adapters:
motors_adapter:
local:
directory: %kernel.root_dir%/../web/files/motors
gcsmotors_adapter:
google_cloud_storage:
service_id: 'app.google_cloud_storage.service'
bucket_name: '%bucket%'
options:
directory: motors
filesystems:
motors_fs:
adapter: motors_adapter
gcsmotors_fs:
adapter: gcsmotors_adapter
vich_uploader:
db_driver: orm
storage: gaufrette
mappings:
motors_file:
uri_prefix: motors
upload_destination: gcsmotors_fs
namer: vich_uploader.namer_uniqid
delete_on_remove: true
现在,我需要通过LiipImagineBundle
. 不幸的是,谷歌云存储不受LiipImagineBundle
. 因此,我尝试以与此处指定的类似方式实现 Google Cloud Storage Resolver :自定义缓存解析器。以下是我配置解析器的方式:
minn.liip_imagine.cache.resolver.gcs:
class: Minn\AdsBundle\Resolver\GoogleCloudStorageResolver
arguments:
- @logger
- @app.google_cloud_storage.service
- '%bucket%'
- 'public-write'
tags:
- { name: monolog.logger, channel: tester }
- { name: 'liip_imagine.cache.resolver', resolver: 'gcs' }
每当有 twig 调用图像时,解析器就会被实例化。它检查图像是否被存储(函数isStored($path)
被调用并且记录器告诉你)!函数resolve($path, $filter)
和store(BinaryInterface $binary, $path, $filter)
永远不会被调用。奇怪的!我说这可能是因为数据加载器工作不正常。因此,我创建了一个新的数据加载器,其定义如下:
minn.liip_imagine.binary.loader.stream.gcsmotors_fs:
class: Minn\AdsBundle\Loader\GCSStreamLoader
arguments:
- @logger
- gaufrette://gcsmotors_fs/
tags:
- { name: monolog.logger, channel: tester }
- { name: 'liip_imagine.binary.loader', loader: 'stream.gcsmotors_fs' }
你猜怎么着?自定义数据加载器永远不会被实例化!我错过了什么吗?
谢谢。
解析器和数据加载器
<?php
namespace Minn\AdsBundle\Resolver;
use \Google_Service_Storage;
use Liip\ImagineBundle\Imagine\Cache\Resolver\ResolverInterface;
use Liip\ImagineBundle\Binary\BinaryInterface;
use Liip\ImagineBundle\Exception\Imagine\Cache\Resolver\NotStorableException;
use Psr\Log\LoggerInterface;
/**
* Description of GoogleCloudStorageResolver
*
* @author Mohamed Amine Jallouli <jallouli.med.amine@gmail.com>
*/
class GoogleCloudStorageResolver implements ResolverInterface {
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var Google_Service_Storage
*/
protected $service;
/**
* @var \Gaufrette\Adapter\GoogleCloudStorage
*/
protected $adatpter;
/**
* @var \Gaufrette\Filesystem
*/
protected $filesystem;
/**
* @var string
*/
protected $bucket;
/**
* @var string
*/
protected $acl;
/**
* @var array
*/
protected $getOptions;
/**
* Object options added to PUT requests.
*
* @var array
*/
protected $putOptions;
/**
* @var string
*/
protected $cachePrefix= null;
/**
* @var string
*/
protected $key= null;
/**
* Constructs a cache resolver storing images on Google Cloud Storage.
*
* @param LoggerInterface $logger monolog logger.
* @param Google_Service_Storage $service The Google Cloud Storage API. It's required to know authentication information.
* @param string $bucket The bucket name to operate on.
* @param string $acl The ACL
* @param array $getOptions A list of options to be passed when retrieving the object url from Google Cloud Storage.
* @param array $putOptions A list of options to be passed when saving the object to Google Cloud Storage.
*/
public function __construct(
LoggerInterface $logger,
Google_Service_Storage $service,
$bucket,
$acl,
array $getOptions = array(),
array $putOptions = array()
) {
$this->logger = $logger;
$this->service = $service;
$this->bucket = $bucket;
$this->acl = $acl;
$this->getOptions = $getOptions;
$this->putOptions = $putOptions;
$this->logger->info( "__construct: bucket=".$bucket);
}
/**
* @param string $cachePrefix
*
*/
protected function setCachePrefix($cachePrefix){
$this->logger->info("cachePrefix= ".$cachePrefix);
$this->cachePrefix = $cachePrefix;
}
/**
* @param string $path
*/
public function setKeyCachePrefix($path){
$pos= strpos($path, "/");
$tmp=$path;
while($pos!==false ){
//$this->logger->notice("getKey [path=$path , pos=$pos" );
$path = substr($path,$pos+1);
$pos= strpos($path, "/");
}
$this->logger->notice("getKey: key=".$path );
$this->key = $path;
$this->setCachePrefix(substr($tmp, 0, strpos($tmp, $path)-1));
//$this->logger->notice("getKey: cachePrefix=". substr($tmp, 0, strpos($tmp, $path)-1));
}
/**
* {@inheritDoc}
*/
public function isStored($path, $filter){
$this->logger->info("isStored? = path=$path , filter=$filter");
return $this->objectExists($path, $filter);
}
/**
* {@inheritDoc}
*/
public function resolve($path, $filter){
$this->logger->info("resovle: path=$path , filter=$filter");
return $this->getObjectUrl($path, $filter);
}
/**
* {@inheritDoc}
*/
public function store(BinaryInterface $binary, $path, $filter){
$this->logger->notice("*-*-*-*-*-*-*-*- store() -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
$this->setKeyCachePrefix($path);
// Creation of the $adatpter
$this->adapter = new \Gaufrette\Adapter\GoogleCloudStorage(
$this->service,
$this->bucket, array(
'directory'=>$this->cachePrefix .'/'. $filter,
'acl'=>$this->acl), true);
// Creation of the $filesystem
$this->filesystem = new \Gaufrette\Filesystem($this->adapter);
// I assume here that the file is already in the bucket. That's why, a BinaryInterface is given.
// Consequently, Binary will be directly re-written in the bucket according to its filter.
// let's log a little bit out store() function to know what is happening!
$this->logger->info('store: key='.$this->key);
$this->logger->info('store: filter='.$filter);
$this->logger->info('store: binary='.$binary->getMimeType());
try {
$this->filesystem->write($this->key, $binary);
$this->logger->info('store: filtered image='. $this->getObjectUrl($path, $filter));
} catch (\Exception $e) {
$this->logError('The object could not be created on Google Cloud Storage.', array(
'objectPath' => $this->getObjectPath($path, null),
'filter' => $filter,
'bucket' => $this->bucket,
'exception' => $e,
));
throw new NotStorableException('The object could not be created on Google Cloud Storage.', null, $e);
}
}
/**
* {@inheritDoc}
*/
public function remove(array $paths, array $filters)
{
if (empty($paths) && empty($filters)) {
return;
}
if (empty($paths)) {
// I don't to do any thing for the moment!!
// Not urgent for the moment!
return;
}
foreach ($filters as $filter) {
foreach ($paths as $path) {
if (!$this->objectExists($path, $filter)) {
continue;
}
try {
$this->adapter = new \Gaufrette\Adapter\GoogleCloudStorage(
$this->service,
$this->bucket, array(
'directory'=>$this->cachePrefix .'/'. $filter,
'acl'=>$this->acl), true);
// Creation of the $filesystem
$this->filesystem = new \Gaufrette\Filesystem($this->adapter);
// the file to be delete is here
$f = $this->getObjectUrl($path, $filter);
// deleting the file
$this->filesystem->delete($path);
$this->logger->info('delete(): DONE FOR '.$f);
} catch (\Exception $e) {
$this->logError('The object could not be deleted from Google Cloud Storage.', array(
'path' => $path,
'filter' => $filter,
'bucket' => $this->bucket,
'exception' => $e,
));
}
}
}
}
/**
* Returns the object path within the bucket.
*
* @param string $path The base path of the resource.
* @param string $filter The name of the imagine filter in effect.
*
* @return string The path of the object on Google Cloud Storage.
*/
public function getObjectPath($path, $filter){
$this->setKeyCachePrefix($path);
$path= $this->key;
$path = $this->cachePrefix
? sprintf('%s/%s/%s', $this->cachePrefix, $filter, $path)
: sprintf('%s/%s', $filter, $path);
return str_replace('//', '/', $path);
}
/**
* Returns the URL for an object saved on Google Cloud Storage.
*
* @param string $path
* @param string $filter
*
* @return string
*/
public function getObjectUrl($path, $filter){
// AWS S3 solution (No need for it!)
//return $this->storage->getObjectUrl($this->bucket, $path, 0, $this->getOptions);
// Google Cloud Storage solution
return 'https://storage.googleapis.com/' .$this->bucket. '/' . $this->getObjectPath($path, $filter);
}
/**
* Checks whether an object exists.
*
* @param string $objectPath
*
* @return bool
*/
protected function objectExists($path, $filter){
// AWS S3 solution... (No need for it!)
//return $this->storage->doesObjectExist($this->bucket, $objectPath);
// Google Cloud Storage solution
$file = $this->getObjectUrl($path, $filter);
$file_headers = @get_headers($file);
$this->logger->info("objectExists: file=".$file);
if($file_headers[0] == 'HTTP/1.0 200 OK' || $file_headers[0] == 'HTTP/1.1 200 OK') {
$this->logger->info("objectExists: file_headers[0]=". $file_headers[0]);
$exists = true;
}
else {
$this->logger->info("objectExists: file_headers[0]=". $file_headers[0]);
$exists = false;
}
return $exists;
}
/**
* @param mixed $message
* @param array $context
*/
protected function logError($message, array $context = array())
{
if ($this->logger) {
$this->logger->error($message, $context);
}
}
}
这是我的自定义 StreamLoader:
<?php
namespace Minn\AdsBundle\Loader;
//use Imagine\Image\ImagineInterface;
use Liip\ImagineBundle\Imagine\Data\Loader\LoaderInterface;
use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException;
class GCSStreamLoader implements LoaderInterface{
/**
* @var LoggerInterface
*/
protected $logger;
/*
* @var ImagineInterface
*/
//protected $imagine;
/**
* The wrapper prefix to append to the path to be loaded.
*
* @var string
*/
protected $wrapperPrefix;
/**
* A stream context resource to use.
*
* @var resource|null
*/
protected $context;
/**
* @param LoggerInterface $logger
* @param string $wrapperPrefix
* @param resource|null $context
*
* @throws \InvalidArgumentException
*/
public function __construct(LoggerInterface $logger/*, ImagineInterface $imagine*/, $wrapperPrefix, $context = null){
$this->logger = $logger;
/*$this->imagine= $imagine;*/
$this->logger->notice("*************__construct (StreamLoader)*********************");
$this->wrapperPrefix = $wrapperPrefix;
$this->logger->notice("__construct: wrapperPrefix=$wrapperPrefix");
$this->logger->notice("__construct: context=$context");
if ($context && !is_resource($context)) {
$this->logger->notice("__construct: InvalidArgumentException!!!");
throw new \InvalidArgumentException('The given context is no valid resource.');
}
$this->context = $context;
}
/**
* {@inheritDoc}
*/
public function find($path)
{
$name = $this->wrapperPrefix.$path;
/*
* This looks strange, but at least in PHP 5.3.8 it will raise an E_WARNING if the 4th parameter is null.
* fopen() will be called only once with the correct arguments.
*
* The error suppression is solely to determine whether the file exists.
* file_exists() is not used as not all wrappers support stat() to actually check for existing resources.
*/
if (($this->context && !$resource = @fopen($name, 'r', null, $this->context)) || !$resource = @fopen($name, 'r')) {
$this->logger->notice("find: NotLoadableException (3)!!! Source image %s not found".$name);
throw new NotLoadableException(sprintf('Source image %s not found.', $name));
}
// Closing the opened stream to avoid locking of the resource to find.
fclose($resource);
try {
$content = file_get_contents($name, null, $this->context);
} catch (\Exception $e) {
$this->logger->notice("find: NotLoadableException (2)!!! Source image %s could not be loaded". $name);
throw new NotLoadableException(sprintf('Source image %s could not be loaded.', $name, $e));
}
if (false === $content) {
$this->logger->notice("find: NotLoadableException (3)!!! Source image %s could not be loaded". $name);
throw new NotLoadableException(sprintf('Source image %s could not be loaded.', $name));
}
return $content;
}
}