在php所谓的输出缓冲,就是代码中的echo 或者其他输出命令在执行的时候是先写入到 php buffer,在脚本执行完或者强制执行输出缓存的命令后,才会把数据输出到浏览器(其中php buffer 就是php.ini中设置的output_buffering ,默认是on,表明无限制大小,可以换成数字来限制大小)。
例子:
echo 'www.111cn.net';
echo '技术';
echo '分享';
这两个echo 是按顺序插入到缓冲区的,只有脚本执行完成或者强制执行缓存输出才会把数据输出到浏览器。
如果我想要实时地输出echo的数据,见下面的代码:
ob_end_flush(); //关闭php缓存,或者在flush()前先执行ob_flush(),下面有解释
echo str_pad(" ", 256);
for ($i=5; $i>0; $i--) {
echo $i. '<br>';
flush();
sleep(1);
}
注意:
1:flush和ob_flush 区别:
乍看之下两者很像,而且很多手册的解释也不清楚,模凌两可,其实两者是有很大区别的。
当php.ini没有开启php buffer缓存时,php脚本输出的内容都会在服务端处于等待输出状态 ,不会保存到输出缓存,因为缓存都没开,此时利用flush可以将那些等待输出的内容立即输出来发到客户端(浏览器或者其他输出端)。
当php.ini开启了php buffer缓存后,php脚本输出内容的第一步是存储在输出缓存中 ,此时等到输出的内容是没有数据的,用flush的话是无效果,取不到数据的。因此要先利用ob_flush把输出缓存中的内容取出来变成等待输出的状态,接下来在利用flush把内容发到客户端。执行的顺序是先ob_flush 再 flush。
因此要实现实时地输出,要么利用ob_end_flush 先关掉php 输出缓存后直接flush,要么先 ob_flush再flush。
2:浏览器无法输出实时数据
把代码改成下面的代码,在chrome firefox ie等浏览器都是一次性输出的,很奇葩的现象:
ob_end_flush(); //关闭php缓存,或者在flush前ob_flush();
echo str_pad(" ", 256);
for ($i=5; $i>0; $i--) {
echo $i;
flush();
sleep(1);
}
找了半天的bug,终于发现了个现象,只要顺便加个html标签,即可实时输出。
原因是:只有在遇到html标签的时候才会即时输出,真是神奇,还好一般输出的内容都会带着html标签,很少纯文本。
解决办法:加个回车或者其他的html标签即可解决问题。
一:下面详细介绍out control 的相关函数
1、flush //将等待输出的内容发送带浏览器,不会对缓存区有影响。
2、ob_flush // 将缓存区的内容变成等待输出状态,数据还没有输出到客户端。
3、ob_start(callback) // 打开输出缓冲区,可以加入回调的callback函数,实现在输出之前执行想要的功能。
例子(1)
ob_start('callbackFun');
echo '1111111';
function callbackFun($string){
return md5($string);//进行md5加密
}
结果:
1
e10adc3949ba59abbe56e057f20f883e
浏览器返回的结果是加密过的字符串,而不是 1111111,说明在数据存入输出缓存之前执行了回调函数对数据进行了md5加密后才存入输出缓存的。
例子(2)
结合ob_gzhandler实现网页内容的gzip压缩,减少传输的大小,提高页面的加载速度。
ob_start('ob_gzhandler');
echo str_repeat('hlmblog', 1024);
例子(3)
可以嵌套ob_start,但是记得要有对应的闭合,两个一一对应,不然会报错,或者取不到数据。
ob_start();
var_dump(1);
ob_start();
var_dump(2);
ob_end_flush();
ob_end_flush();
结果:
int 1
int 2
4、ob_get_contents() 获取输出缓冲区的内容或者页面输出的内容
echo str_pad('', 1024);//使缓冲区溢出
ob_start();//打开缓冲区
phpinfo();
$content = ob_get_contents();//获取缓冲区内容
$f = fopen('./phpinfo.txt', 'wb');//打开文件
fwrite($f, $content);//内容写入txt文件
fclose($f);//关闭文件
ob_end_clean();//关闭缓冲区,输出数据并清空浏览器
//ob_end_flush();//发送缓冲区内容到客户端,并关闭缓冲区,不清空浏览器
注意:
这时候浏览器将什么都不输出,因为使用了ob_end_clean() 清空掉了,但是会在当前目录产生一个phpinfo.txt 的文件,里面是获取到的phpinfo信息。
如果使用ob_end_flush,不仅会生成phpinfo.txt 文件,而且还在浏览器输出信息。
5、ob_get_length() 返回输出缓冲区的内容长度
echo str_pad('', 1024);//缓冲区溢出
ob_start();//打开缓冲区
phpinfo();
$string = ob_get_contents();//获取缓冲区内容
$length = ob_get_length();//获取缓冲区内容长度
$re = fopen('./phpinfo.txt', 'wb');
fwrite($re, $string);//将内容写入文件
fclose($re);
var_dump($length); //输出长度
ob_end_flush();//输出并关闭缓冲区
6、ob_get_level() //获取输出缓冲区嵌套级别,就是所处的级别位置
ob_start();
var_dump(ob_get_level());
ob_start();
var_dump(ob_get_level());
ob_end_flush();
ob_end_flush();
浏览器会输出对应的级别:
int 2
int 3
7、ob_get_status() //获取当前缓冲区的状态,返回的是一个数组信息
ob_start('ob_gzhandler');
var_dump(ob_get_status());
ob_start();
var_dump(ob_get_status());
ob_end_flush();
ob_end_flush();
array
'level' => int 2
'type' => int 1
'status' => int 0
'name' => string 'ob_gzhandler' (length=12)
'del' => boolean true
array
'level' => int 3
'type' => int 1
'status' => int 0
'name' => string 'default output handler' (length=22)
'del' => boolean true
返回数组参数详解:
level:嵌套级别,和ob_get_level()获取到的值是一样的。
type :处理缓冲类型,0是系统内部自动处理,1是用户手动处理。
status :缓冲处理状态, 0是开始, 1是进行中, 2是结束。
name: :定义的输出处理函数名称,就是ob_start() 函数中第一个参数的回调函数。
del :是否运行了删除缓冲区操作。
8、ob_list_handlers() //获得处理程序的函数名数组,也就是ob_start函数传入的第一个参数函数名
print_r(ob_list_handlers());
ob_end_flush();
ob_start("ob_gzhandler");
print_r(ob_list_handlers());
ob_end_flush();
浏览器输出对应的处理函数信息数组:
Array ( [0] => default output handler ) Array ( [0] => ob_gzhandler )
9、ob_implicit_flush() // 打开或关闭绝对刷送模式,就是每一次输出后自动执行 flush(),不用对应地写多次的flush,节省代码,提高效率
echo str_pad('', 1024);//缓冲区溢出
ob_end_flush();
ob_implicit_flush(true);//打开绝对刷送
echo 'hlmblog</br>';
//flush();
sleep(1);
echo '分享</br>';
//flush();
sleep(1);
echo '技术</br>';
浏览器实时的输出下面:
hlmblog
分享
技术
10、ob_end_flush() //发送内部缓冲区的内容到浏览器,并且关闭输出缓冲区
11、ob_end_clean() //删除内部缓冲区的内容,并且关闭内部缓冲区
二:控制缓存输出可以用来做什么,具体的几个示例
1:生成静态页面
静态页面的加载速度就是快,这句话是家户喻晓的道理,不用请求数据库,这是多么爽的事情啊。
下面是生成静态页面的例子:
echo str_pad('', 1024);//使缓冲区溢出
ob_start();//打开缓冲区
$content = ob_get_contents();//获取页面输出的内容
$f = fopen('./index.html', 'w');
fwrite($f, $content);//内容写入txt文件
fclose($f);
ob_end_clean();//清空并关闭缓冲区
传说中的静态页面就这样简单的生成。
2:捕获输出
function test($param) {
if($param) {
ob_start();
eval($param);
$contents = ob_get_contents();
ob_end_clean();
}else {
echo '遗憾的没有输出';
exit();
}
return $contents;
}
电子邮件营销称为EDM,即Email Direct Marketing的缩写,是在用户事先许可的前提下,通过电子邮件的方式向目标用户传递价值信息的一种网络营销手段。邮件推送是跟用户互动最有效的手段之一,因此任何一个平台,需要跟用户交流,邮件推送系统是必不可少的一个环节。
本文主要讲解如何用drupal搭建一个邮件推送系统,实现用户订阅邮件的发送。
本文参考: http://www.wdtutorials.com/…
第一步,安装模块Simplenews、MailSystem、HTMLMail。
Simplenews是邮件管理发送的核心模块,启用simplenews就可以有一种内容类型,用来创建新的newsletter,MailSystem和HTMLMail,是设置邮件输出内容以及格式用的。
第二步,创建Newsletter。
A. 设置一个测试邮箱(Test Address)admin/config/services/simplenews/settings
B. 创建Newsletter node/add/simplenews
C. 创建完成之后,到newsletter的tab,点击发送测试
设置测试邮件地址截图:
这时候,我们可以到测试邮箱地址查看是否收到了此邮件(涉及本地的sendmail系统和收件箱是否屏蔽垃圾邮件)。
这样两步就完成了一个基本的邮件管理和发送,注意这里的发送用的是Sendmail,也就是系统自带的发送邮件程序,稍微我们讲解如何替换。
第三步,Simplenews高级设置。
a. 默认设置
admin/config/services/simplenews/settings
有很多设置点:默认的format,测试收件箱、发送者、确认邮件的模板、cron发件数量等
b. 创建simplenews分类,然后在具体分类里面设置邮件发送配置
admin/config/services/simplenews
第四步,系统邮件设置 ? drupal Mail System
a. admin/config/system/mailsystem
NEW SETTING 里面选择 simplenews,点击保存。
b. MAIL SYSTEM SETTINGS 里面会有一个 Simplenews module class,选择HTMLMailSystem,点击保存。
给Simplenews模块添加邮件截图:
保存之后,给Simplenews选择邮件处理模块:
第五步. 邮件输出:HTMLMail的模板和Simplenews的Formatter
a. HTML Mail基础设置
admin/config/system/htmlmail
step 1 查看tpl文件和debug属性
step 2 选择使用的theme,选择一个简单theme或者当前theme即可。
注意,这个theme决定了htmlmail模板需要放置的位置。
step 3 输出formatter设置。
推荐新建一个formatter用于newsletter,安装几个推荐filter,比如Emogrifier。
b. simplenews的输出格式(都选择HTML)
默认的formatter在: admin/config/services/simplenews/settings
具体simplenews分类的formatter在: admin/config/services/simplenews
如果分类里面设置为HTML格式,不成功,那就再把默认的formmater也设置为HTML
到这里,就可以测试发送邮件的效果了,看看邮件格式是否正确。
关于邮件的内容正文和内容类型设置有两种方式:
1. 通过Node的body,发不出来的跟邮件的内容同一个body。
2. 通过自定义的Field,然后在tpl里面拼装,不同的用于显示结果不一样。
需要注意的问题:
Simplenews的相关模板的tpl具体在simplenews下面,需要复制到theme下面的话,一般是seven(自己测试一下为什么放到当前theme下不行),所以,推荐直接修改simplenews模块下面的tpl。
HTMLMail的相关tpl主要作用是包装simplenews,把htmlmail?simplenews.tpl.php文件复制到指定的 theme或当前theme下面(这个theme可以在htmlmail模块里设置),然后可以在htmlmail-body标签外面包装自己想要的信息即可。
HTMLMail的tpl可以用于邮件的头尾信息的包装,比如邮件的头部的logo,尾部的版权信息等。
步骤六,邮件的订阅人管理地址: admin/people/simplenews
步骤七,使用Cron发送: admin/config/services/simplenews/settings/mail
设置完成上面的步骤,一个简单的邮件推送系统就搭建完成了,Drupal里面主要还是管理邮件内容和推送用户信息、订阅等信息,真正的发送邮件还是系统的sendmail,如果需要更高效的发送,可以安装postfix。考虑到邮件的认证、域名反向解析、spf以及垃圾邮件处理,可以考虑使用第三方的发送系统,这个时候,我们可以安装Drupal的SMTP模块即可。
今天赶上了123System OPenVZ VPS全场半价的机会,购入了一台512MB内存、双核3.49Ghz Xeon E3-1270 V3 CPU的套餐,这是第一次使用123system的产品,整体印象非常不错,就目前而言速度也是杠杠哒,便手动配置起了最新版的Tengine和PHP 5.6.8。在编译完成之后我决定以PHP -FPM的方式来运行PHP,下面是从各处收集到并且重新整理的一些关于FPM使用的技巧,记录一下方便自己与一些平时接触到这方面工作的朋友吧!
1、安装完之后,对php-conf的修改:
修改用户组:
user = www-data
group = www-data
如果www-data用户不存在,那么执行linux命令先添加www-data用户
groupadd www-data
useradd -g www-data www-data
部分参数解析:
pid = run/php-fpm.pid
#pid设置,默认在安装目录中的var/run/php-fpm.pid,建议开启
error_log = log/php-fpm.log
#错误日志,默认在安装目录中的var/log/php-fpm.log
log_level = notice
#错误级别. 可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice.
emergency_restart_threshold = 60
emergency_restart_interval = 60s
#表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如果超过 emergency_restart_threshold个,php-fpm就会优雅重启。这两个选项一般保持默认值。
process_control_timeout = 0
#设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0.
daemonize = yes
#后台执行fpm,默认值为yes,如果为了调试可以改为no。在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。
listen = 127.0.0.1:9000
#fpm监听端口,即nginx中php处理的地址,一般默认值即可。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置.
listen.backlog = -1
#backlog数,-1表示无限制,由操作系统决定,此行注释掉就行。backlog含义参考:http://www.3gyou.cc/?p=41
listen.allowed_clients = 127.0.0.1
#允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接
2、对Nginx.conf的fastcgi部分修改:
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
3、PHP-FPM启动相关参数
#测试php-fpm配置
/usr/local/php/sbin/php-fpm -t
/usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf -t
#启动php-fpm
/usr/local/php/sbin/php-fpm
/usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf
#关闭php-fpm
kill -INT `cat /usr/local/php/var/run/php-fpm.pid`
#重启php-fpm
kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`
4、Nginx的开启与关闭
结束进程:fuser -k 80/tcp
启动Nginx:sudo /usr/local/nginx/nginx
现在说这个,感觉有点过时了,但是感觉用namespace的人还是不多,估计还是因为不习惯吧。
class把一个一个function组织起来,namespace可以理解成把一个一个class,function等有序的组织起来。个人觉得,namespace的主要优势有
第一,可以更好的管理代码
第二,文件一多,可以避免class,function的重名
第三,代码可读性增强了
1,定义namespace
namespace userCenter; //php代码 namespace userCenter\register; //php代码 namespace userCenter\login { //php代码 }
命名空间不能嵌套或在同一代码处声明多次(只有最后一次会被识别)。但是,你能在同一个文件中定义多个命名空间化的代码,比较合适的做法是每个文件定义一个命名空间(可以是相同命名空间)。
2,调用namespace
\userCenter\register; //绝对调用
userCenter\login; //相对调用
use userCenter\register; //引用空间
use userCenter\register as reg; //引用空间并加别名
3,实例说明
login.class.php
<?php namespace userCenter; function check_username(){ echo "login OK<br>"; } class login{ public function save(){ echo "login had saved<br>"; } } ?> regist.class.php <?php namespace userCenter\regist { function check_username() { echo "regist OK<br>"; } class regist{ public function save(){ echo "regist had saved<br>"; } } } ?>
test.php
<?php require "login.class.php"; require "regist.class.php"; use userCenter\regist; //使用use调用空间 use userCenter\regist as reg; //as定义别名 echo \userCenter\check_username(); //绝对调用 $login = new \userCenter\login(); echo $login->save(); echo regist\check_username(); //相对调用 echo reg\check_username(); //别名调用 $regist = new reg\regist(); echo $regist->save();
使用use,比绝对调用要好一点,好比给class,function等加了一个前缀,这样看起来就比较清楚了
基本设置
创建一个简单的Doctrine实体类:
// 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->path ? null : $this->getUploadRootDir().'/'.$this->path; } public function getWebPath() { return null === $this->path ? null : $this->getUploadDir().'/'.$this->path; } protected function getUploadRootDir() { // the absolute directory path where uploaded // documents should be saved return __DIR__.'/../../../../web/'.$this->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'; } }
该document实体有一个名称与文件相关联。这个path属性存储一个文件的相对路径并且在数据库中存储。这个getAbsolutePath()会返回一个绝对路径,getWebPath()会返回一个web路径,用于模板加入上传文件链接。
如果你还没有这样做的话,你应该阅读http://symfony.com/doc/current/reference/forms/types/file.html首先了解基本的上传过程。
如果您使用注释来验证规则(如本例所示),请确保你启用了注释验证(见http://symfony.com/doc/current/book/validation.html#book-validation-configuration)。
在处理一个实际的文件上传时,使用一个“虚拟”的file字段。例如,如果你在controller中直接构建一个form,他可能是这样的:
public function uploadAction() { // ... $form = $this->createFormBuilder($document) ->add('name') ->add('file') ->getForm(); // ... } 下一步,创建file这个属性到你的Document类中并且添加一些验证规则: 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->file = $file; } /** * Get file. * * @return UploadedFile */ public function getFile() { return $this->file; } } annotations Annotations // 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; // ... }
当你使用File约束,symfony会自动猜测表单字段输入的是一个文件上传。这就是当你创建表单(->add(‘file’))时,为什么没有在表单明确设置为文件上传的原因。
下面的控制器,告诉您如何处理全部过程:
// ... 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->createFormBuilder($document) ->add('name') ->add('file') ->getForm(); $form->handleRequest($request); if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($document); $em->flush(); return $this->redirect($this->generateUrl(...)); } return array('form' => $form->createView()); }
以前的controller当提交name自动的存储Document实体,但是他不会做任何关于文件的事情并且path属性也将是空白。
处理文件上传一个简单的方法就是在entity持久化之前设置相应的path属性。在某一时刻处理文件上传时,要调用Document实体类一个upload()方法给path赋值。
if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $document->upload(); $em->persist($document); $em->flush(); return $this->redirect(...); } 这个upload()方法利用UploadedFile对象,是它提交后返回file字段: public function upload() { // the file property can be empty if the field is not required // 该file属性为空这个属性就不需要了 if (null === $this->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->getFile()->move( $this->getUploadRootDir(), $this->getFile()->getClientOriginalName() ); // set the path property to the filename where you've saved the file // 设置path属性为你保存文件的文件名 $this->path = $this->getFile()->getClientOriginalName(); // clean up the file property as you won't need it anymore // 清理你不需要的file属性 $this->file = null; } 使用生命周期回调 生命周期回调是一种有限的技术,他有一些缺点。如果你想移除Document::getUploadRootDir()方法里的写死的编码__DIR__,最好的方法是开始使用Doctrine listeners。在哪里你将能够注入内核参数,如kernel.root_dir来建立绝对路径。 这种原理工作,他有一个缺陷:也就是说当entity持久化时会有什么问题呢?答:该文件已经转移到了它的最终位置,实体类下的path属性不能够正确的实体化。 (如果entity有持久化问题或者文件不能够移动,什么事情也没有发生)为了避免这些问题,你应该改变这种实现方式以便数据库操作和自动删除文件: /** * @ORM\Entity * @ORM\HasLifecycleCallbacks */ class Document { }
接下来,利用这些回调函数重构Document类:
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->file = $file; // check if we have an old image path // 检查如果我们有一个旧的图片路径 if (isset($this->path)) { // store the old name to delete after the update $this->temp = $this->path; $this->path = null; } else { $this->path = 'initial'; } } /** * @ORM\PrePersist() * @ORM\PreUpdate() */ public function preUpload() { if (null !== $this->getFile()) { // do whatever you want to generate a unique name // 去生成一个唯一的名称 $filename = sha1(uniqid(mt_rand(), true)); $this->path = $filename.'.'.$this->getFile()->guessExtension(); } } /** * @ORM\PostPersist() * @ORM\PostUpdate() */ public function upload() { if (null === $this->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->getFile()->move($this->getUploadRootDir(), $this->path); // check if we have an old image if (isset($this->temp)) { // delete the old image unlink($this->getUploadRootDir().'/'.$this->temp); // clear the temp image path $this->temp = null; } $this->file = null; } /** * @ORM\PostRemove() */ public function removeUpload() { $file = $this->getAbsolutePath(); if ($file) { unlink($file); } } }
如果更改你的entity是由Doctrine event listener 或event subscriber处理,这个 preUpdate()回调函数必须通知Doctrine关于正在做的改变。有关preUpdate事件限制的完整参考请查看 http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#preupdate
现在这个类做了你需要的一切:他会在entity持久化之前生成一个唯一的文件名,持久化之后,移动文件,删除文件。
现在移动文件是entity自动完成,这个$document->upload()就应该从controller中移除了:
if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($document); $em->flush(); return $this->redirect(...); }
这个@ORM\PrePersist()和@ORM\PostPersist()事件回调:一个是在entity持久化到数据库之前触发,一个是在entity持久化到数据库之后触发。另一方面, @ORM\PreUpdate() 和 @ORM\PostUpdate()事件回调时当实体更新时触发。
当改变entity字段后进行持久化操作时,PreUpdate和PostUpdate回调才会被触发。这意味着,默认情况下,你只改变了$file属性,这些事件不会被触发,因为这个属性它自己不会持久化到Doctrine。有一个解决方法,就是创建一个updated字段把它持久化到Doctrine,并当文件改变时手动调整它。
使用ID作为文件名
如果要使用ID作为文件名,实现略有不同,您需要保存path属性为文件扩展名,而不是实际的文件名:
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->file = $file; // check if we have an old image path if (is_file($this->getAbsolutePath())) { // store the old name to delete after the update $this->temp = $this->getAbsolutePath(); } else { $this->path = 'initial'; } } /** * @ORM\PrePersist() * @ORM\PreUpdate() */ public function preUpload() { if (null !== $this->getFile()) { $this->path = $this->getFile()->guessExtension(); } } /** * @ORM\PostPersist() * @ORM\PostUpdate() */ public function upload() { if (null === $this->getFile()) { return; } // check if we have an old image if (isset($this->temp)) { // delete the old image unlink($this->temp); // clear the temp image path $this->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->getFile()->move( $this->getUploadRootDir(), $this->id.'.'.$this->getFile()->guessExtension() ); $this->setFile(null); } /** * @ORM\PreRemove() */ public function storeFilenameForRemove() { $this->temp = $this->getAbsolutePath(); } /** * @ORM\PostRemove() */ public function removeUpload() { if (isset($this->temp)) { unlink($this->temp); } } public function getAbsolutePath() { return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path; } }
你会注意到,在这种情况下,你需要做一点工作,以删除该文件。在数据删除之前,你必须保存文件路径(因为它依赖于ID)。然后,一旦对象已经完全从数据库中删除,你就可以安全的删除文件(在数据删除之后)。