uam/propel-s3object-behavior

一个允许对象将文件保存到AWS S3的Propel行为。

2.0.0 2017-11-01 06:41 UTC

README

S3Object行为允许对象与存储在AWS S3中的文件保持关联。

要求

此行为需要Propel >= 1.6.0以及PHP的AWS SDK >= 2.2.0。

安装

将以下行添加到您的 composer.json 文件中

	require: {
		
		"uam/propel-s3object-behavior": "dev-master"
	}

然后运行

php composer.phar install

或者

php composer.phar update

配置

将以下内容添加到您的 propel.inibuild.properties 文件中。

propel.behavior.s3object.class: S3ObjectBehavior

您需要在AWS S3上有一个有效的活动账户,以及/或访问有效的AWS S3存储桶。有关详细信息,请参阅AWS网站。

用法

在Propel模式中

将行为添加到相关表的模式定义中

<?xml version="1.0" encoding="utf-8"?>
<database name="default" namespace="My\App\lib" defaultIdMethod="native">
    <table name="document" phpName="Document" idMethod="native">
        <!-- you column definitions -->
        <behavior name="s3object" />
    </table>
</database>

在您的代码中(独立)

  1. 创建一个 Aws\S3\S3Client 实例
  2. 创建一个 BasicS3ObjectManager 实例。
  3. 将其设置在您的对象实例上。
  4. 调用S3Object行为添加到对象实例上的方法。
$s3 = Aws::factory('/path/to/config.php')->get('s3');

$bucket = 'my-bucket';
$region = 'eu-west-1'; // Any AWS region
$serverSideEncryption = false; // No need to encrypt the documents on S3
$reducedRedundancyStorage = true; // Use reduced redundancy storage

$manager = new BasicS3ObjectManager(
    $s3,
    $bucket,
    $region,
    $serverSideEncryption,
    $reducedRedundancyStorage
);

$document = DocumentQuery::create()
    ->findPk($id);

$document->setS3ObjectManager($manager);

$url = $document->getPresignedUrl("+5minutes");

在symfony2中

  1. 创建一个 Aws\S3\S3Client 实例并将其定义为服务
  2. 创建一个 BasicS3ObjectManager 实例并将其定义为服务。
  3. 将其设置在您的对象实例上。
  4. 调用S3Object行为添加到对象实例上的方法。

以下示例中,我们使用 UAMAwsBundle 来提供 S3Client 服务。您可以使用任何包或代码作为服务返回有效的 Aws\S3\S3Client 实例。

# services.yml

parameters:
    my_app.document_manager.class: BasicS3ObjectManager
    my_app.documents.aws_bucket: my-bucket
    my_app.documents.aws_region: eu-west-1
    my_app.documents.aws_sse: false
    my_app.documents.aws_rrs: true

services:
    my_app.document_manager:
        class: '%my_app.document_manager.class%'
        arguments:
            - '@uam_aws.s3'
            - '%my_app.documents.aws_bucket%'
            - '%my_app.documents.aws_region%'
            - '%my_app.documents.aws_sse%'
            - '%my_app.documents.aws_rrs%'

上传文件

通常通过包含文件输入小部件的表单来完成。处理提交的表单时,您必须使用从上传文件中获取的值设置正在编辑的对象实例的 originalFilenamepathname 属性。然后保存对象实例。如上所述,您必须首先获取 BasicS3ObjectManager 的实例并将其设置在对象上。

$document_manager = /* see above */;

$document = DocumentQuery::create()
    ->findPk($id);

$document->setS3ObjectManager($document_manager);

$document->setOriginalFilename($filename);
$document->setPathname($path);

$document->save();

获取S3文件的链接

只需调用 getPresignedUrl 方法并使用该url即可。

当点击该链接时,关联的文件将使用对象实例的 original_filename 从AWS S3下载,而不管文件在AWS S3中存储的键是什么。

为了提供一定程度的混淆,我们建议以下模式

  • 在您的网页中,使用内部url,即指向您应用程序域名中页面的url
  • 服务器端,当请求该页面时,将其重定向到AWS S3的预签名url。
$id = $_GET['document_id'];

$document = DocumentQuery::create()
    ->findPk($id);

$document_manager = /* see above */;

$document->setS3ObjectManager($document_manager);

$url = $document->getPresignedUrl();

Header("Location: " . $url);

这种方法有两个优点

  • 它将避免使用指向AWS的直接链接来混淆用户。
  • 从安全角度来看,它允许您创建非常短命的预签名url(默认为5分钟,但可以进一步减少)。由于预签名url是针对每个请求生成的,这给用户带来极小的麻烦(如果页面太旧,只需刷新页面即可),同时确保生成的链接,如果有人获取,将毫无用处。

使用默认值

S3Object允许您为每个对象实例使用不同的存储桶,甚至不同的区域。尽管如此,在大多数情况下,所有对象都将共享同一个区域和存储桶。这些可以以多种方式设置默认值

第一种方法是更新您的表并设置适当的默认值。这种方法很简单,但有一个实际的缺点:默认值适用于所有开发环境。换句话说,如果您想在开发和生产环境中使用不同的存储桶或其他设置,您必须使用不同的数据库。

我们推荐的第二种方法是使用BasicS3ObjectManager类作为默认值。在创建BasicS3ObjectManager实例时,传递给构造函数的参数将作为与管理者关联的所有对象实例的默认值。请参阅此类源代码。

这允许您为每个环境(开发或生产)创建或配置独立的BasicS3ObjectManager实例,并为每个环境使用不同的存储桶。

密钥策略

设计一个策略以生成密钥,确保此类密钥在AWS S3上的唯一性,或者如果不要求唯一性,则设计一个策略以确保密钥与存储在AWS S3上的文件集的一致性,这是开发者的责任。

密钥是AWS S3桶内文件的唯一标识符。

存储桶和密钥也作为对象实例的属性存储,并在类表中持久化,在bucketkey列下。注意,如果所有对象共享相同的存储桶,或者它已在其他地方定义为默认值,则存储桶字段可能为null。

在大多数情况下,每个类实例都将与一个唯一的文件关联。因此,您表中的[bucket, key]组合必须是唯一的,反映该桶在AWS S3上密钥的唯一性。

在某些其他情况下,您的应用程序可能需要多个对象实例共享相同的文件,并且您可能希望仅在AWS S3上存储此文件一次。在这种情况下,您必须确保相关对象实例的密钥相同。

当关联的文件上传到AWS S3时,每个对象实例的密钥由generateKey方法生成,然后将其设置为对象实例的key属性。如果您需要为您的密钥实现一些自定义逻辑,只需在您的类中重写generateKey方法。

默认实现返回基于对象的original_filename属性的slug。请注意,在某些情况下,这可能会导致密钥的不一致性,例如,如果两个文件名仅通过大小写不同(例如,一个文件名中的字母为大写,而另一个文件名为小写;每个的slug将相同)。

一个简单的替代实现是返回对象实例的id。这自然确保了密钥的唯一性。然而,这意味着如果需要直接通过AWS管理控制台等管理AWS S3上的文件,则文件不易识别。

请注意,在所有情况下,getPresignedUrl都使用原始文件名从AWS S3下载文件。

修剪“孤儿”文件

“孤儿”文件是在AWS S3上存在的文件,它不匹配您的应用程序中的任何对象实例(也不匹配数据库中的任何记录)。

S3Object行为提供了两种内置机制来避免孤儿文件。

  • 删除对象时,S3Object行为将自动删除关联的AWS S3文件。

  • 保存对象时,S3Object行为将自动检查密钥是否已更改,如果是,将删除与旧密钥关联的文件。

此实现有两个已知限制:

  1. 它假定存储桶未更改。如果存储桶已更改,则旧存储桶中的文档不会被删除。
  2. 如果您的应用程序允许多个对象实例共享相同的文件(换句话说,如果它不要求密钥的唯一性),则可能会导致悬挂密钥出现。请参阅下一章。

悬挂密钥

悬挂密钥是在对象实例(或数据库记录)上设置的密钥,对于该密钥,在AWS S3上不存在文件。

以下情况下可能会发生这种情况:

  1. 文件已通过另一个应用程序、API或通过AWS控制台手动删除(或其他方式)从AWS S3删除。这超出了本包的范围。

  2. 如果您的应用程序允许多个对象实例共享同一个文件(换句话说,它不需要键的唯一性),那么在用新文件更新对象时可能会丢失一致性。在这种情况下,旧文件将从AWS S3删除,但所有其他与旧文件关联的对象实例都没有被更新,因此它们仍然具有旧键,而现在AWS S3上没有与该键对应的文件。为了解决这个问题,请覆盖由行为生成的类中的preUpdatepostUpdatepostDelete方法,并实现您自己的逻辑。

属性引用

S3Object行为在应用到的类上定义了以下属性。

original_filename

与对象实例关联的文件的原始文件名。

bucket

存储相关文件的AWS S3存储桶。

region

存储相关文件的AWS区域。

key

在AWS S3下存储相关文件的键。

sse

是否在AWS S3上使用服务器端加密存储相关文件。

rrs

是否在AWS S3上使用降低冗余存储存储相关文件。

pathname

与对象实例关联的文件的本地路径。此属性是瞬时的(不会持久化到数据库),用于在文件上传期间使用(例如,通过表单编辑对象实例)。

s3object_manager

与对象实例关联的S3ObjectManager实例,大多数方法将委托给它。

方法引用

除了上述属性的获取器和设置器之外,S3Object行为还将以下方法添加到应用到的类中。

以下所有方法都需要S3ObjectManager的一个实例。这可以是显式作为参数传递的(以下大多数方法都接受此类参数),或者必须在对象实例之前设置。后一种方法是推荐的。

getPresignedUrl($expires = "+5 minutes")

返回与对象关联的S3文件的预签名URL。接受一个参数,表示预签名URL的存活时间(请参阅AWS S3文档)。

fileExists

检查与对象实例关联的文件是否真的存在于S3上。

generateKey

生成一个新的键,用于在S3上存储与对象实例关联的文件。

upload($path)

将文件上传到S3。

deleteFile

在S3上删除与该对象实例关联的文件。

S3ObjectManager接口

S3ObjectManager接口定义了实现S3Object行为所需的大部分逻辑。几乎添加到对象类上的所有行为方法都是通过委托到与对象实例关联的S3ObjectManager实例来实现的。

S3ObjectManager是一个接口。行为提供了一个默认实现,名为BasicS3ObjectManager,它是基于AWS PHP SDK v2.2构建的。

您可以自由创建和使用自己的S3ObjectManager接口实现。特别是,如果您出于某些原因需要使用AWS PHP SDK的v1版本,那么创建基于它的S3ObjectManager实现是完全可能的。