ano / media-bundle
匿名化媒体包
Requires
- knplabs/gaufrette: 0.1.*
- symfony/framework-bundle: >=2.1
Suggests
- imagine/Imagine: 0.2.*
This package is not auto-updated.
Last update: 2024-09-14 12:21:08 UTC
README
MediaBundle提供了类似于SonataMediaBundle的功能,但没有紧密耦合的SonataAdminBundle部分,并采用了不同的面向对象设计方法。
重要提示
再次强调,这里不是在创新,这个包受到了伟大的SonataMediaBundle的极大启发,而@rande则是上下文/提供者行为的幕后大脑;
AnoMediaBundle在处理模型、上下文/提供者和背后的事件方面有所不同。这也是为什么README文件基本上是关于模型的:p
然而,我并没有选择简单地复制SonataMediaBundle,而是从头开始重写代码。这就是我选择理解所有DIC高级配置,并创建一个最适合我个人需求的基础包的方式。但请记住,SonataMediaBundle中的大量代码在某些地方被重用。
MediaBundle提供了一种通用且通用的方式来处理项目中不同类型的媒体。它提供了一系列服务,每个服务负责媒体旅程中的特定任务。
它试图通过优雅有趣的KnpLabs Gaufrette库来抽象文件系统,同时也提供了CDN使用的抽象。
理解包的行为需要一些面向对象设计的基础知识。理解代码本身,特别是配置部分,需要强大的Symfony 2和依赖注入系统/概念技能。
安装
依赖项
AnoSystemBundle
大多数Ano包都依赖于一个AnoSystemBundle,它提供了常见的行为,如扩展中的DIC参数重映射、屈折变化器或甚至AclManager。
如果您不想使用依赖项,只需将AnoSystemBundle扩展中的remapParametersNamespaces和remapParameters复制到MediaExtension文件中,就可以删除AnoSystemBundle。
Gaufrette
该包依赖于KnpLabs的Gaufrette库,因此您需要将其安装到项目中,并确保为它配置自动加载器。
Imagine
如果您打算使用默认的ImageManipulator实现,则需要安装Bulat Shakirzyanov的出色Imagine库。
MediaBundle
包位于vendor/bundles/Ano/Bundle目录中(因此您应该有vendor/bundles/Ano/Bundle/MediaBundle)
概念
上下文/提供者概念
MediaBundle概念的关键是关于context。什么是context?context用于标识应用程序中媒体的使用情况。MediaBundle需要它能够为特定媒体类型(例如视频或图像)使用适当的媒体提供者,并知道如何处理它。
一个 provider 是一个对象,负责检索特定类型的媒体,例如视频,并执行与此媒体类型相关的操作。例如,ImageProvider 将知道如何从文件系统检索图像以及如何生成缩略图(通过图像处理接口),而 VideoProvider 将知道如何从 Dailymotion 或 YouTube 等WS API检索元数据。
因此,provider 由 context 确定,并根据用户需求(例如生成多个缩略图大小)提供一些配置。根据要处理的媒体类型,为 Provider 提供一些工具,例如 Gaufrette 驱动程序用于访问文件系统,或用于生成缩略图的图像处理实现等。
实现
好了,不谈了,让我们来看一个例子。
场景
假设我们的应用程序中的用户可以添加图像作为头像,并且最终可以删除它。该应用程序是在 Symfony 2 下实现的,用户可以通过标准表单进行交互。
图像文件存储在项目结构中的 medias 文件夹内,在本地文件系统中,但可以通过不同的主机访问。
出于显示目的,系统需要两个特定的缩略图大小,除了原始文件外,还需要 small: 50x50 和 medium: 90x90。
配置
首先,让我们添加一些根据我们的需求配置。我们需要在 config.yml 文件中添加以下内容
ano_media:
cdn:
local:
default: true
id: ano_media.cdn.remote_server
options:
base_url: "http://img.my.local" #(ideally, this parameter should lives in the parameters.yml file)
provider:
image: # So we need the image provider here
default: true
id: ano_media.provider.image
filesystem:
local:
default: true
id: ano_media.filesystem.local
options:
base_path: %kernel.root_dir%/../medias
create: true #creates the directory it it doesn't exist yet
contexts:
user_picture:
formats:
small: { width: 50, height: 50 }
medium: { width: 90, height: 90 }
配置就这么多,这就是我们需要的全部。
模型
现在,我们需要定义我们的模型以满足我们的需求,但在我们这样做之前,让我们先做一些理论上的探讨。
在一个单一的应用程序中,往往需要不同类型的媒体对象:用户的头像,文章或新闻条目的图像等。
在面向对象的世界中,这应该通过“独立”的类定义来表示,所有这些都共享共同的行为。但你可能想知道,我们如何持久化它们呢?
答案是:没关系,有了现代 ORM 的力量,你不必担心这一点。只需创建你的模型,以后再处理。
所以,让我们开始吧
首先,我们需要一个基础 Media 类,这在大多数情况下不会直接使用。
namespace My\SiteBundle\Model;
use Ano\Bundle\MediaBundle\Model\Media as BaseMedia;
abstract class Media extends BaseMedia
{
}
现在,我们需要为每个媒体“用例”创建一个对象。从面向对象的角度来看,用户图片(头像)和其他用于任何其他目的的图像应该由它们自己的类表示,因为它们在系统中的用途不同。
所以,对于我们的用户图片,我们需要一个特定的 Media
namespace My\UserBundle\Model;
use My\SiteBundle\Model\Media as BaseMedia;
class UserPicture extends BaseMedia
{
protected $context = 'user_picture';
}
当然,我们需要定义与我们的 User 对象的关系(这里是一维的)
namespace My\UserBundle\Model;
class User
{
private $name;
private $picture;
public function setPicture(UserPicture $picture)
{
$this->picture = $picture;
}
public function getPicture()
{
return $this->picture;
}
}
这就是我们的模型,简单且优雅的面向对象。
ORM 配置
现在我们已经定义了模型,我们需要为 ORM 创建元数据。这里我们将使用 Doctrine 2 和标准的关系数据库。
正如我们之前所说的,一个应用程序中可能有几种不同的媒体。但由于我的不同媒体对象没有定义特定的数据,我们可以将它们持久化到一个共享的数据库表中。我们将使用 Doctrine 单表继承行为来完成此操作。
首先,我们需要为我们的 Media 基础模型添加一个标识符,这样我们就可以将其转换为实体(在 Doctrine 世界中)。为了简洁起见,我将直接在 Media 模型类中添加标识符(但我们也可以创建另一个名为 Entity 的命名空间,并在其中创建媒体子类,以便隔离 Doctrine 的特定内容)。
namespace My\SiteBundle\Model;
use Ano\Bundle\MediaBundle\Model\Media as BaseMedia;
abstract class Media extends BaseMedia
{
protected $id;
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
}
现在,我们的映射
My/SiteBundle/Resources/config/doctrine/Media.orm.xml :
<entity name="My\SiteBundle\Model\Media" table="medias" inheritance-type="SINGLE_TABLE">
<id name="id" type="integer" column="id">
<generator strategy="AUTO" />
</id>
<discriminator-column name="discr" type="string" />
<discriminator-map>
<discriminator-mapping value="media" class="Media" />
<discriminator-mapping value="user_picture" class="My\UserBundle\Model\UserPicture" />
</discriminator-map>
</entity>
My/UserBundle/Resources/config/doctrine/UserPicture.orm.xml :
<entity name="My\UserBundle\Model\UserPicture" />
My/UserBundle/Resources/config/doctrine/User.orm.xml :
<entity name="My\UserBundle\Model\User" table="users">
<id name="id" type="integer" column="id">
<generator strategy="AUTO" />
</id>
<field name="name" column="name" type="string" length="30" />
<one-to-one field="picture" target-entity="My\UserBundle\Model\UserPicture">
<cascade>
<cascade-all />
</cascade>
</one-to-one>
</entity>
如你所见,我使用了 Doctrine 操作级联。这意味着当你对一个 User 实例执行持久化操作($em->persist($user))时,Doctrine 会将此操作级联到 User 对象中,该对象位于 User 内部。
这样可以避免手动持久化用户照片,这实际上并不合理。用户照片并不是独立于用户实例存在的,所以从面向对象的角度来看,当我们修改用户照片时,我们实际上是在修改用户本身,然后,用户数据需要被保存(因此用户照片也会被保存)。
当然,删除操作也是完全相同的案例。
前端操作
用户需要与系统交互,以提供他想要作为头像的照片。我们将仅使用简单的Symfony表单来完成此操作。为了简单起见,我将使用一个简单的数组作为表单的数据。
我们的想法是需要使用表单组件将用户上传的文件映射到File对象,这样我们就可以轻松地获取文件的二进制内容并将其设置到我们的UserPicture对象中。
My/UserBundle/Form/UserProfileType :
namespace My\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class UserProfileType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('picture', 'file')
;
}
public function getName()
{
return 'user_profile';
}
}
控制器:
public function editPictureAction(Request $request)
{
$data = array('picture');
$form = $this->formFactory->create(new UserProfileType(), $data);
if ('POST' == $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$user = $this->getCurrentUser();
if (!empty($data['picture'])) {
$userPicture = new UserPicture();
$userPicture->setName($data['picture']->getClientOriginalName());
$userPicture->setContent($data['picture']);
$user->setPicture($userPicture);
$this->userManager->saveUser($user);
$this->session->setFlash('notice', 'Avatar saved !');
}
return $this->getResponseRedirect('my_user_profile_edit');
}
else {
$this->session->setFlash('errors', 'Validation errors, please fix your inputs');
}
}
return $this->render('MyUserBundle:User:edit-profile', array(
'form' => $form->createView(),
));
}
就是这样,MediaBundle将神奇地执行所有必要的用户照片操作。如果你查看你的媒体目录,你应该能看到所有生成的缩略图,如果你查看你的数据库,你会看到媒体已正确持久化:-)