<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Symfony中文教程 &#187; Doctrine</title>
	<atom:link href="http://www.newlifeclan.com/symfony/archives/category/cookbook/cookbook-2-6/doctrine/feed" rel="self" type="application/rss+xml" />
	<link>http://www.newlifeclan.com/symfony</link>
	<description>站在巨人肩膀上的phpweb框架</description>
	<lastBuildDate>Fri, 12 Dec 2025 00:58:27 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.0.38</generator>
	<item>
		<title>(Doctrine)如何处理Doctrine文件上传</title>
		<link>http://www.newlifeclan.com/symfony/archives/199</link>
		<comments>http://www.newlifeclan.com/symfony/archives/199#comments</comments>
		<pubDate>Thu, 04 Dec 2014 06:36:11 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[Cook 2.6]]></category>
		<category><![CDATA[Cookbook]]></category>
		<category><![CDATA[Doctrine]]></category>
		<category><![CDATA[symfony2.6]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=199</guid>
		<description><![CDATA[<p>以Doctrine形式上传文件和一些其他方式上传图片是没有什么不同的。换句话说，你可以在表单提交后，自由的处理 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/199">(Doctrine)如何处理Doctrine文件上传</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>以Doctrine形式上传文件和一些其他方式上传图片是没有什么不同的。换句话说，你可以在表单提交后，自由的处理文件。对于如何做到这一点，请参阅 <a href="http://symfony.com/doc/current/reference/forms/types/file.html" target="_blank">http://symfony.com/doc/current/reference/forms/types/file.html</a></p>
<p><span id="more-199"></span></p>
<p>你可以将上传文件的处理集成到Entity的生命周期中（如创建，修改和删除时调用）。</p>
<p>在这种情况下的好处，在Doctrine中创建、修改和删除数据时，上传文件和删除文件也会自动完成（必须在控制器中做任何操作）</p>
<h2>基本设置</h2>
<p>创建一个简单的Doctrine实体类：</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 */
class Document
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank
     */
    public $name;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    public $path;

    public function getAbsolutePath()
    {
        return null === $this-&gt;path
            ? null
            : $this-&gt;getUploadRootDir().'/'.$this-&gt;path;
    }

    public function getWebPath()
    {
        return null === $this-&gt;path
            ? null
            : $this-&gt;getUploadDir().'/'.$this-&gt;path;
    }

    protected function getUploadRootDir()
    {
        // the absolute directory path where uploaded
        // documents should be saved
        return __DIR__.'/../../../../web/'.$this-&gt;getUploadDir();
    }

    protected function getUploadDir()
    {
        // get rid of the __DIR__ so it doesn't screw up
        // when displaying uploaded doc/image in the view.
        return 'uploads/documents';
    }
}</pre><p>该document实体有一个名称与文件相关联。这个path属性存储一个文件的相对路径并且在数据库中存储。这个getAbsolutePath()会返回一个绝对路径，getWebPath()会返回一个web路径，用于模板加入上传文件链接。</p>
<blockquote><p>如果你还没有这样做的话，你应该阅读http://symfony.com/doc/current/reference/forms/types/file.html首先了解基本的上传过程。</p>
<p>如果您使用注释来验证规则（如本例所示），请确保你启用了注释验证（见http://symfony.com/doc/current/book/validation.html#book-validation-configuration）。</p>
<p>&nbsp;</p></blockquote>
<p>在处理一个实际的文件上传时，使用一个“虚拟”的file字段。例如，如果你在controller中直接构建一个form，他可能是这样的：</p><pre class="crayon-plain-tag">public function uploadAction()
{
    // ...

    $form = $this-&gt;createFormBuilder($document)
        -&gt;add('name')
        -&gt;add('file')
        -&gt;getForm();

    // ...
}</pre><p>下一步，创建file这个属性到你的Document类中并且添加一些验证规则：</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\File\UploadedFile;

// ...
class Document
{
    /**
     * @Assert\File(maxSize="6000000")
     */
    private $file;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
    }

    /**
     * Get file.
     *
     * @return UploadedFile
     */
    public function getFile()
    {
        return $this-&gt;file;
    }
}</pre><p>annotations</p><pre class="crayon-plain-tag">// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Document
{
    /**
     * @Assert\File(maxSize="6000000")
     */
    private $file;

    // ...
}</pre><p></p>
<blockquote><p> 当你使用File约束，symfony会自动猜测表单字段输入的是一个文件上传。这就是当你创建表单（-&gt;add(&#8216;file&#8217;)）时，为什么没有在表单明确设置为文件上传的原因。</p></blockquote>
<p>下面的控制器，告诉您如何处理全部过程：</p><pre class="crayon-plain-tag">// ...
use Acme\DemoBundle\Entity\Document;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
// ...

/**
 * @Template()
 */
public function uploadAction(Request $request)
{
    $document = new Document();
    $form = $this-&gt;createFormBuilder($document)
        -&gt;add('name')
        -&gt;add('file')
        -&gt;getForm();

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        $em = $this-&gt;getDoctrine()-&gt;getManager();

        $em-&gt;persist($document);
        $em-&gt;flush();

        return $this-&gt;redirect($this-&gt;generateUrl(...));
    }

    return array('form' =&gt; $form-&gt;createView());
}</pre><p>以前的controller当提交name自动的存储Document实体，但是他不会做任何关于文件的事情并且path属性也将是空白。</p>
<p>处理文件上传一个简单的方法就是在entity持久化之前设置相应的path属性。在某一时刻处理文件上传时，要调用Document实体类一个upload()方法给path赋值。</p><pre class="crayon-plain-tag">if ($form-&gt;isValid()) {
    $em = $this-&gt;getDoctrine()-&gt;getManager();

    $document-&gt;upload();

    $em-&gt;persist($document);
    $em-&gt;flush();

    return $this-&gt;redirect(...);
}</pre><p>这个upload()方法利用UploadedFile对象，是它提交后返回file字段：</p><pre class="crayon-plain-tag">public function upload()
{
    // the file property can be empty if the field is not required
    // 该file属性为空这个属性就不需要了
    if (null === $this-&gt;getFile()) {
        return;
    }

    // use the original file name here but you should
    // sanitize it at least to avoid any security issues
    //  这里你应该使用原文件名但是应该至少审核它避免一些安全问题
    // move takes the target directory and then the
    // target filename to move to
    // 将目标文件移动到目标目录
    $this-&gt;getFile()-&gt;move(
        $this-&gt;getUploadRootDir(),
        $this-&gt;getFile()-&gt;getClientOriginalName()
    );

    // set the path property to the filename where you've saved the file
    // 设置path属性为你保存文件的文件名
    $this-&gt;path = $this-&gt;getFile()-&gt;getClientOriginalName();

    // clean up the file property as you won't need it anymore
    // 清理你不需要的file属性
    $this-&gt;file = null;
}</pre><p></p>
<h2> 使用生命周期回调</h2>
<blockquote><p>生命周期回调是一种有限的技术，他有一些缺点。如果你想移除Document::getUploadRootDir()方法里的写死的编码__DIR__，最好的方法是开始使用Doctrine listeners。在哪里你将能够注入内核参数，如kernel.root_dir来建立绝对路径。</p></blockquote>
<p>这种原理工作，他有一个缺陷：也就是说当entity持久化时会有什么问题呢？答：该文件已经转移到了它的最终位置，实体类下的path属性不能够正确的实体化。</p>
<p>（如果entity有持久化问题或者文件不能够移动，什么事情也没有发生）为了避免这些问题，你应该改变这种实现方式以便数据库操作和自动删除文件：</p><pre class="crayon-plain-tag">/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Document
{
}</pre><p>接下来,利用这些回调函数重构<tt class="docutils literal"><code>Document</code></tt>类:</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Document
{
    private $temp;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
        // check if we have an old image path
        // 检查如果我们有一个旧的图片路径
        if (isset($this-&gt;path)) {
            // store the old name to delete after the update
            $this-&gt;temp = $this-&gt;path;
            $this-&gt;path = null;
        } else {
            $this-&gt;path = 'initial';
        }
    }

    /**
     * @ORM\PrePersist()
     * @ORM\PreUpdate()
     */
    public function preUpload()
    {
        if (null !== $this-&gt;getFile()) {
            // do whatever you want to generate a unique name
            // 去生成一个唯一的名称
            $filename = sha1(uniqid(mt_rand(), true));
            $this-&gt;path = $filename.'.'.$this-&gt;getFile()-&gt;guessExtension();
        }
    }

    /**
     * @ORM\PostPersist()
     * @ORM\PostUpdate()
     */
    public function upload()
    {
        if (null === $this-&gt;getFile()) {
            return;
        }

        // if there is an error when moving the file, an exception will
        // be automatically thrown by move(). This will properly prevent
        // the entity from being persisted to the database on error
        //当移动文件发生错误，一个异常move（）会自动抛出异常。
        //这将阻止实体持久化数据库发生错误。
        $this-&gt;getFile()-&gt;move($this-&gt;getUploadRootDir(), $this-&gt;path);

        // check if we have an old image
        if (isset($this-&gt;temp)) {
            // delete the old image
            unlink($this-&gt;getUploadRootDir().'/'.$this-&gt;temp);
            // clear the temp image path
            $this-&gt;temp = null;
        }
        $this-&gt;file = null;
    }

    /**
     * @ORM\PostRemove()
     */
    public function removeUpload()
    {
        $file = $this-&gt;getAbsolutePath();
        if ($file) {
            unlink($file);
        }
    }
}</pre><p></p>
<blockquote><p> 如果更改你的entity是由Doctrine event listener 或event subscriber处理，这个 <tt class="docutils literal"><code>preUpdate()回调函数必须通知Doctrine关于正在做的改变。有关preUpdate事件限制的完整参考请查看 http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate</code></tt></p></blockquote>
<p>现在这个类做了你需要的一切：他会在entity持久化之前生成一个唯一的文件名，持久化之后，移动文件，删除文件。</p>
<p>现在移动文件是entity自动完成，这个$document-&gt;upload()就应该从controller中移除了：</p><pre class="crayon-plain-tag">if ($form-&gt;isValid()) {
    $em = $this-&gt;getDoctrine()-&gt;getManager();

    $em-&gt;persist($document);
    $em-&gt;flush();

    return $this-&gt;redirect(...);
}</pre><p></p>
<blockquote><p> 这个@ORM\PrePersist()和@ORM\PostPersist()事件回调:一个是在entity持久化到数据库之前触发,一个是在entity持久化到数据库之后触发。另一方面， <tt class="docutils literal"><code>@ORM\PreUpdate()</code></tt> 和 <tt class="docutils literal"><code>@ORM\PostUpdate()事件回调时当实体更新时触发。</code></tt></p>
<p>当改变entity字段后进行持久化操作时，<tt class="docutils literal"><code>PreUpdate</code></tt>和<tt class="docutils literal"><code>PostUpdate回调才会被触发。这意味着，默认情况下，你只改变了$file属性，这些事件不会被触发，因为这个属性它自己不会持久化到Doctrine。有一个解决方法，就是创建一个updated字段把它持久化到Doctrine，并当文件改变时手动调整它。</code></tt></p></blockquote>
<h2></h2>
<h2>使用ID作为文件名</h2>
<p>如果要使用ID作为文件名，实现略有不同，您需要保存path属性为文件扩展名,而不是实际的文件名:</p><pre class="crayon-plain-tag">use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Document
{
    private $temp;

    /**
     * Sets file.
     *
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
        // check if we have an old image path
        if (is_file($this-&gt;getAbsolutePath())) {
            // store the old name to delete after the update
            $this-&gt;temp = $this-&gt;getAbsolutePath();
        } else {
            $this-&gt;path = 'initial';
        }
    }

    /**
     * @ORM\PrePersist()
     * @ORM\PreUpdate()
     */
    public function preUpload()
    {
        if (null !== $this-&gt;getFile()) {
            $this-&gt;path = $this-&gt;getFile()-&gt;guessExtension();
        }
    }

    /**
     * @ORM\PostPersist()
     * @ORM\PostUpdate()
     */
    public function upload()
    {
        if (null === $this-&gt;getFile()) {
            return;
        }

        // check if we have an old image
        if (isset($this-&gt;temp)) {
            // delete the old image
            unlink($this-&gt;temp);
            // clear the temp image path
            $this-&gt;temp = null;
        }

        // you must throw an exception here if the file cannot be moved
        // so that the entity is not persisted to the database
        // which the UploadedFile move() method does
        $this-&gt;getFile()-&gt;move(
            $this-&gt;getUploadRootDir(),
            $this-&gt;id.'.'.$this-&gt;getFile()-&gt;guessExtension()
        );

        $this-&gt;setFile(null);
    }

    /**
     * @ORM\PreRemove()
     */
    public function storeFilenameForRemove()
    {
        $this-&gt;temp = $this-&gt;getAbsolutePath();
    }

    /**
     * @ORM\PostRemove()
     */
    public function removeUpload()
    {
        if (isset($this-&gt;temp)) {
            unlink($this-&gt;temp);
        }
    }

    public function getAbsolutePath()
    {
        return null === $this-&gt;path
            ? null
            : $this-&gt;getUploadRootDir().'/'.$this-&gt;id.'.'.$this-&gt;path;
    }
}</pre><p>你会注意到，在这种情况下,你需要做一点工作,以删除该文件。在数据删除之前，你必须保存文件路径（因为它依赖于ID）。然后，一旦对象已经完全从数据库中删除，你就可以安全的删除文件（在数据删除之后）。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/199">(Doctrine)如何处理Doctrine文件上传</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/199/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
