<?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; 博客</title>
	<atom:link href="http://www.newlifeclan.com/symfony/archives/category/symfony-blog/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>flush完entity后及时更新entity</title>
		<link>http://www.newlifeclan.com/symfony/archives/1469</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1469#comments</comments>
		<pubDate>Thu, 19 Jul 2018 15:09:09 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[其他]]></category>
		<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1469</guid>
		<description><![CDATA[<p>有时候我们会需要在controller中录入数据，并及时的返回实体，但有些实体字段并没有被及时更新，这该怎么办 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1469">flush完entity后及时更新entity</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>有时候我们会需要在controller中录入数据，并及时的返回实体，但有些实体字段并没有被及时更新，这该怎么办呢？<span id="more-1469"></span></p>
<p>symofony给我们提供了一个refresh方法：</p><pre class="crayon-plain-tag">$em = $this-&gt;getDoctrine()-&gt;getManager();
        $XXXX =  $request-&gt;files-&gt;get('XXXX');
        $Uploadfileentity = new UploadfileEntity();
        $Uploadfileentity-&gt;setImagefile($XXXX);
        $em-&gt;persist($Uploadfileentity);
        $em-&gt;flush();
        // 及时从数据库更新持久化
        $em-&gt;refresh($Uploadfileentity);

        $returnarar = array('success' =&gt; 1,'message' =&gt; '上传成功!','url' =&gt; $Uploadfileentity-&gt;getImagepath());

        return new JsonResponse($returnarar);</pre><p>这段代码的意思是，你获取一个上传的文件，然后将文件赋值给entity处理，entity负责上传入库。</p>
<p>如果没有填写$em-&gt;refresh($Uploadfileentity);  恐怕你永远也不能及时获取到 $Uploadfileentity-&gt;getImagepath()的路径值。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1469">flush完entity后及时更新entity</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1469/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>密码学中的“盐值 Salt”</title>
		<link>http://www.newlifeclan.com/symfony/archives/1227</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1227#comments</comments>
		<pubDate>Wed, 08 Mar 2017 15:07:27 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[其他]]></category>
		<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1227</guid>
		<description><![CDATA[<p>为什么要在密码里加点“盐” 盐（Salt） 在密码学中，是指通过在密码任意固定位置插入特定的字符串，让散列后的 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1227">密码学中的“盐值 Salt”</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>为什么要在密码里加点“盐”</p>
<hr />
<p>盐（Salt）</p>
<p>在密码学中，是指通过在密码任意固定位置插入特定的字符串，让散列后的结果和使用原始密码的散列结果不相符，这种过程称之为“加盐”。</p>
<hr />
<p><span id="more-1227"></span></p>
<p>以上这句话是维基百科上对于 Salt 的定义，但是仅凭这句话还是很难理解什么叫 Salt，以及它究竟起到什么作用。</p>
<h2>第一代密码</h2>
<p>早期的软件系统或者互联网应用，<a class="replace_word" title="MySQL知识库" href="http://lib.csdn.net/base/mysql" target="_blank">数据库</a>中设计用户表的时候，大致是这样的结构：</p><pre class="crayon-plain-tag">mysql&gt; desc User;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| UserName | varchar(50)  | NO   |     |         |       |
| PassWord | varchar(150) | NO   |     |         |       |
+----------+--------------+------+-----+---------+-------+</pre><p>数据存储形式如下：</p><pre class="crayon-plain-tag">mysql&gt; select * from User;
+----------+----------+
| UserName | PassWord |
+----------+----------+
| lichao   | 123      |
| akasuna  | 456      |
+----------+----------+</pre><p>主要的关键字段就是这么两个，一个是登陆时的用户名，对应的一个密码，而且那个时候的用户名是明文存储的，如果你登陆时用户名是 123，那么数据库里存的就是 123。这种设计思路非常简单，但是缺陷也非常明显，数据库一旦泄露，那么所有用户名和密码都会泄露，后果非常严重。参见<a href="http://tech.qq.com/a/20111221/000485.htm" target="_blank">《CSDN 详解 600 万用户密码泄露始末》</a>。</p>
<h2>第二代密码</h2>
<p>为了规避第一代密码设计的缺陷，聪明的人在数据库中不在存储明文密码，转而存储加密后的密码，典型的加密<a class="replace_word" title="算法与数据结构知识库" href="http://lib.csdn.net/base/datastructure" target="_blank">算法</a>是 MD5 和 SHA1，其数据表大致是这样设计的：</p><pre class="crayon-plain-tag">mysql&gt; desc User;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| UserName | varchar(50)  | NO   |     |         |       |
| PwdHash  | char(32)     | NO   |     |         |       |
+----------+--------------+------+-----+---------+-------+</pre><p>数据存储形式如下：</p><pre class="crayon-plain-tag">mysql&gt; select * from User;
+----------+----------------------------------+
| UserName | PwdHash                          |
+----------+----------------------------------+
| lichao   | 202cb962ac59075b964b07152d234b70 |
| akasuna  | 250cf8b51c773f3f8dc8b4be867a9a02 |
+----------+----------------------------------+</pre><p>假如你设置的密码是 123，那么数据库中存储的就是 202cb962ac59075b964b07152d234b70 或 40bd001563085fc35165329ea1ff5c5ecbdbbeef。当用户登陆的时候，会把用户输入的密码执行 MD5（或者 SHA1）后再和数据库就行对比，判断用户身份是否合法，这种加密算法称为<strong>散列</strong>。</p>
<p>严格地说，这种算法不能算是加密，因为理论上来说，它不能被解密。所以即使数据库丢失了，但是由于数据库里的密码都是密文，根本无法判断用户的原始密码，所以后果也不算太严重。</p>
<p>&nbsp;</p>
<h2>第三代密码</h2>
<p>本来第二代密码设计方法已经很不错了，只要你密码设置得稍微复杂一点，就几乎没有被破解的可能性。但是如果你的密码设置得不够复杂，被破解出来的可能性还是比较大的。</p>
<p>好事者收集常用的密码，然后对他们执行 MD5 或者 SHA1，然后做成一个数据量非常庞大的数据字典，然后对泄露的数据库中的密码就行对比，如果你的原始密码很不幸的被包含在这个数据字典中，那么花不了多长时间就能把你的原始密码匹配出来。这个数据字典很容易收集，CSDN 泄露的那 600w 个密码，就是很好的原始素材。</p>
<p>于是，第三代密码设计方法诞生，用户表中多了一个字段：</p><pre class="crayon-plain-tag">mysql&gt; desc User;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| UserName | varchar(50) | NO   |     |         |       |
| Salt     | char(50)    | NO   |     |         |       |
| PwdHash  | char(32)    | NO   |     |         |       |
+----------+-------------+------+-----+---------+-------+</pre><p>数据存储形式如下：</p><pre class="crayon-plain-tag">mysql&gt; select * from User;
+----------+----------------------------+----------------------------------+
| UserName | Salt                       | PwdHash                          |
+----------+----------------------------+----------------------------------+
| lichao   | 1ck12b13k1jmjxrg1h0129h2lj | 6c22ef52be70e11b6f3bcf0f672c96ce |
| akasuna  | 1h029kh2lj11jmjxrg13k1c12b | 7128f587d88d6686974d6ef57c193628 |
+----------+----------------------------+----------------------------------+</pre><p>Salt 可以是任意字母、数字、或是字母或数字的组合，但必须是随机产生的，每个用户的 Salt 都不一样，用户注册的时候，数据库中存入的不是明文密码，也不是简单的对明文密码进行散列，而是 MD5( 明文密码 + Salt)，也就是说：</p><pre class="crayon-plain-tag">MD5('123' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('456' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'</pre><p>当用户登陆的时候，同样用这种算法就行验证。</p>
<p>由于加了 Salt，即便数据库泄露了，但是由于密码都是加了 Salt 之后的散列，坏人们的数据字典已经无法直接匹配，明文密码被破解出来的概率也大大降低。</p>
<p>是不是加了 Salt 之后就绝对安全了呢？淡然没有！坏人们还是可以他们数据字典中的密码，加上我们泄露数据库中的 Salt，然后散列，然后再匹配。但是由于我们的 Salt 是随机产生的，假如我们的用户数据表中有 30w 条数据，数据字典中有 600w 条数据，坏人们如果想要完全覆盖的坏，他们加上 Salt 后再散列的数据字典数据量就应该是 300000* 6000000 = 1800000000000，一万八千亿啊，干坏事的成本太高了吧。但是如果只是想破解某个用户的密码的话，只需为这 600w 条数据加上 Salt，然后散列匹配。可见 Salt 虽然大大提高了安全系数，但也并非绝对安全。</p>
<p>实际项目中，Salt 不一定要加在最前面或最后面，也可以插在中间嘛，也可以分开插入，也可以倒序，程序设计时可以灵活调整，都可以使破解的难度指数级增长。</p>
<p>注，文中所谓第一、二、三代密码的称呼，是我自己 YY 的。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1227">密码学中的“盐值 Salt”</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1227/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>symfony中JsonResponse处理findAll数据集和编码</title>
		<link>http://www.newlifeclan.com/symfony/archives/1185</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1185#comments</comments>
		<pubDate>Wed, 28 Dec 2016 06:18:45 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1185</guid>
		<description><![CDATA[<p>在symfony中JsonResponse是一个生成Json响应的便捷方式。但是当我们将findAll查询出来 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1185">symfony中JsonResponse处理findAll数据集和编码</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>在symfony中JsonResponse是一个生成Json响应的便捷方式。但是当我们将findAll查询出来的结果放入到JsonResponse时，他输出的Json数据居然为空。<span id="more-1185"></span></p><pre class="crayon-plain-tag">[{},{},{}....]</pre><p>什么情况？呢</p>
<p>其实是我们的entity类的属性为私有化的问题。解决起来也很简单，就是修改我们相关的entity类：</p><pre class="crayon-plain-tag">namespace AppBundle\Entity;
....
class XXXXXXX implements JsonSerializable
{
  private $id;
  private $title;
  private $content;

  ....

  public function jsonSerialize()
    {
        return array(
            'id'=&gt; $this-&gt;id,
            'title' =&gt; $this-&gt;title,
            'content' =&gt; $this-&gt;content,
        );
    }
}</pre><p>核心就是实现JsonSerializable的jsonSerialize()方法，返回需要的属性为一个数组。</p>
<p>我们在controller中测试一下：</p><pre class="crayon-plain-tag">public function indexAction(Request $request)
    {
        $em=$this-&gt;getDoctrine()-&gt;getManager();
        $entities=$em-&gt;getRepository('AppBundle:XXXXXXXXX')-&gt;findAll();

        $response = new JsonResponse();
        $response-&gt;headers-&gt;set('Content-Type', 'application/json');
        $response-&gt;headers-&gt;set('Access-Control-Allow-Origin', '*');

        $response-&gt;setEncodingOptions(JSON_UNESCAPED_UNICODE);
        $response-&gt;setData($entities);

        return $response;
    }</pre><p>其中有一行</p>
<p>$response-&gt;setEncodingOptions(JSON_UNESCAPED_UNICODE);</p>
<p>此句话是用来处理JSON 编码，如果没有此句，你输出的Json数据很可能出现如下状况：</p>
<p>{\u0022id\u0022:1,\u0022iam\u0022:1</p>
<p>setEncodingOptions相关参数请查看：http://php.net/manual/zh/json.constants.php</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1185">symfony中JsonResponse处理findAll数据集和编码</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1185/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Twig中计算两个时间之间的天数</title>
		<link>http://www.newlifeclan.com/symfony/archives/1182</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1182#comments</comments>
		<pubDate>Tue, 29 Nov 2016 16:03:07 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[其他]]></category>
		<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1182</guid>
		<description><![CDATA[<p>PHP5.3 DateTime对象有了diff()方法返回一个DateInterval对象，这个对象可以计算开 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1182">Twig中计算两个时间之间的天数</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>PHP5.3 DateTime对象有了diff()方法返回一个DateInterval对象，这个对象可以计算开始时间和结束时间的差值。<br />
因为Twig的date函数总是返回一个DateTime对象所以可以调用diff方法。<span id="more-1182"></span></p><pre class="crayon-plain-tag">{# endDate 和 startDate 是 string or DateTime 对象 #}
{% set difference = date(endDate).diff(date(startDate)) %}
{% set leftDays = difference.days %}

  {{ leftDays }} days</pre><p>&nbsp;</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1182">Twig中计算两个时间之间的天数</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1182/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP Datetime 时间的增减方式</title>
		<link>http://www.newlifeclan.com/symfony/archives/1180</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1180#comments</comments>
		<pubDate>Tue, 29 Nov 2016 15:48:06 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1180</guid>
		<description><![CDATA[<p>PHP5 以后的 DateTime 是一个强大的事件处理对象，現在我来整理一下常用的一些时间日期增减方式。 获 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1180">PHP Datetime 时间的增减方式</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>PHP5 以后的 <code>DateTime</code> 是一个强大的事件处理对象，現在我来整理一下常用的一些时间日期增减方式。<span id="more-1180"></span></p>
<h2>获取当前时间</h2>
<p></p><pre class="crayon-plain-tag">$date = new DateTime('2000-01-01');
// OR
$date = new DateTime('now');</pre><p>&nbsp;</p>
<h2>增加10天</h2>
<h3>用 DateInterval 与 DateTime::add()</h3>
<p></p><pre class="crayon-plain-tag">$date   = new DateTime('2000-01-01');
$day    = new DateInterval('P10D'); // P开头代表日期，10D 代表 10 天
$date-&gt;add($day); 
echo $date-&gt;format('Y-m-d') ;</pre><p>用 DateTime::modify()</p><pre class="crayon-plain-tag">$date = new DateTime('2006-12-12');
$date-&gt;modify('+10 day');
echo $date-&gt;format('Y-m-d');</pre><p>&nbsp;</p>
<h2>减少一个月</h2>
<p></p><pre class="crayon-plain-tag">$date = new DateTime('2000-12-31');

$date-&gt;modify('-1 month');
echo $date-&gt;format('Y-m-d') ;</pre><p>&nbsp;</p>
<h2>用 DateInterval 增加两年四天六小时又八分</h2>
<p></p><pre class="crayon-plain-tag">$date   = new DateTime('2000-01-01');
$day    = new DateInterval('P2Y4DT6H8M'); // 两年四天六小時又八分，中间的 T 是时间的意思
$date-&gt;add($day); 
echo $date-&gt;format('Y-m-d') ;</pre><p>&nbsp;</p>
<h2>DateInterval 的写法说明</h2>
<p></p><pre class="crayon-plain-tag">new DateInterval('P1Y2M3D'); // P开头表示日期，一年二個月又三天

new DateInterval('PT4H5M6S'); // T开头表示時間，四小時五分又六秒

new DateInterval('P1Y2M3DT4H5M6S'); // 合并起來，一年二个月三天四小時五分又六秒

new DateInterval('P3M'); // 三个月

new DateInterval('PT1H'); // 一小时</pre><p>&nbsp;</p>
<h2>两个时间相减计算出天数</h2>
<p></p><pre class="crayon-plain-tag">$datetime1 = new DateTime('2013-12-23 16:13:08');
$datetime2 = new DateTime('2012-11-22 12:05:07');
$interval = $datetime1-&gt;diff($datetime2);
echo $interval-&gt;format('%d天%H小时%i分%s秒');  //输出时间字符串
echo $interval-&gt;days;   //输出两个日期相差天数</pre><p>$datetime1-&gt;diff()方法会返回DateInterval对象，具体细节可查看 php<a href="http://php.net/manual/zh/class.dateinterval.php"> DateInterval 手册</a>。</p>
<h2></h2>
<h2>注意：</h2>
<p></p><pre class="crayon-plain-tag">$date=new \DateTime();
$time1=$date;
$day=new DateInterval('P5D');
$time2=$date-&gt;add($day);</pre><p>你会发现，输出的$time1 和 $time2是一样的。这很好处理，我们只需要加上clone 即可</p><pre class="crayon-plain-tag">$date=new \DateTime();
$time1=clone $date;
$day=new DateInterval('P5D');
$time2=$date-&gt;add($day);</pre><p>这时$time1和$time2就不同步了</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1180">PHP Datetime 时间的增减方式</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1180/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FOSUserBundle重新加载用户</title>
		<link>http://www.newlifeclan.com/symfony/archives/1175</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1175#comments</comments>
		<pubDate>Sun, 27 Nov 2016 07:08:16 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1175</guid>
		<description><![CDATA[<p>有的时候，会因为某种原因，需要变换用户的角色，并实时生效。例如，用户因某些行为（变为高级会员），提高了自己的权 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1175">FOSUserBundle重新加载用户</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>有的时候，会因为某种原因，需要变换用户的角色，并实时生效。例如，用户因某些行为（变为高级会员），提高了自己的权限，程序配给它一个新的角色，这就需要程序立即更新这个用户，让用户立刻享有自己的职能。</p>
<p>或者有些会员违反了，网站的规定，网站及时将其降权，防止此会员的恶意行为。这都需要角色变换后的及时更新，也就是对用户的重新加载。</p>
<p><span id="more-1175"></span></p>
<h2>在User Entity下手</h2>
<p>很简单，只需要用到EquatableInterface接口，在这个接口中有一个函数isEqualTo，我们实现了它就可以了：</p><pre class="crayon-plain-tag">class User extends BaseUser implements EquatableInterface
{
    ........
    public function isEqualTo(UserInterface $user)
    {
        // TODO: Implement isEqualTo() method.
        return
            md5($user-&gt;getUsername()) == md5($this-&gt;getUsername()) &amp;&amp;
            md5(serialize($user-&gt;getRoles())) == md5(serialize($this-&gt;getRoles()));
    }
.......</pre><p>这个方法返回ture等于用户没有变动，如果返回false就会重新加载用户。</p>
<p>&nbsp;</p>
<h2>如何使用</h2>
<p>来到indexAction，确保你的程序已经登录。并且登录用户不存在“ROLE_ADVANT”角色</p><pre class="crayon-plain-tag">public function indexAction(Request $request)
    {
        $em=$this-&gt;getDoctrine()-&gt;getManager();
        $userentity=$this-&gt;getUser();
       // $userentity-&gt;setRoles(array('ROLE_ADVANT','ROLE_ADMIN'));
        $userentity-&gt;addRole('ROLE_ADVANT');

        $em-&gt;persist($userentity);
        $em-&gt;flush();

        return $this-&gt;render('default/index.html.twig', array(
        ));

    }</pre><p>twig模板要加入判断以便演示（当然也可以查看debug工具中的security）：</p><pre class="crayon-plain-tag">{% if is_granted('ROLE_ADVANT') %}
   已成功添加 ROLE_ADVANT
{% endif %}</pre><p>ok，你已经成功的重新加载了用户。</p>
<p>&nbsp;</p>
<blockquote><p>注意：在indexAction中不要加入$this-&gt;isGranted(&#8220;ROLE_XXX&#8221;)，来做判断，否则重新加载不会启用。</p>
<p>当然，还有别的方法。例如，重新生成Token</p><pre class="crayon-plain-tag">$em = $this-&gt;getDoctrine()-&gt;getManager();
$loggedInUser = $this-&gt;get('security.context')-&gt;getToken()-&gt;getUser();
$loggedInUser-&gt;addRole('ROLE_XYZ');

$em-&gt;persist($loggedInUser);
$em-&gt;flush();

$token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken(
  $loggedInUser,
  null,
  'main',
  $loggedInUser-&gt;getRoles()
);

$this-&gt;container-&gt;get('security.context')-&gt;setToken($token);</pre><p>这个文章可供参考：</p>
<p class="mt4 mb2 center"><a href="https://coderwall.com/p/nptn5q/how-to-reload-your-user-after-changes-in-symfony2" target="_blank">How to reload your User after changes in Symfony2</a></p>
</blockquote>
<p>&nbsp;</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1175">FOSUserBundle重新加载用户</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1175/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>密码保护：创建简单的vendor目录bunle并在项目中使用它</title>
		<link>http://www.newlifeclan.com/symfony/archives/1103</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1103#comments</comments>
		<pubDate>Thu, 10 Nov 2016 09:26:13 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1103</guid>
		<description><![CDATA[<p>无法提供摘要。这是一篇受保护的文章。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1103">密码保护：创建简单的vendor目录bunle并在项目中使用它</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<form action="http://www.newlifeclan.com/symfony/login?action=postpass" class="post-password-form" method="post">
<p>这是一篇受密码保护的文章，您需要提供访问密码：</p>
<p><label for="pwbox-1103">密码： <input name="post_password" id="pwbox-1103" type="password" size="20" /></label> <input type="submit" name="Submit" value="提交" /></p>
</form>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1103">密码保护：创建简单的vendor目录bunle并在项目中使用它</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1103/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony消息提醒Flash Message</title>
		<link>http://www.newlifeclan.com/symfony/archives/1096</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1096#comments</comments>
		<pubDate>Wed, 02 Nov 2016 15:55:05 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1096</guid>
		<description><![CDATA[<p>Symfony有一个消息条子（flash Message），它是把需要显示的临时消息放到session中，并在 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1096">Symfony消息提醒Flash Message</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>Symfony有一个消息条子（<a href="http://www.symfonychina.com/doc/current/controller.html#catalog12" target="_blank">flash Message</a>），它是把需要显示的临时消息放到session中，并在第一次访问后清除他。<span id="more-1096"></span></p>
<p>举一个简单例子：</p>
<p>在控制器中我们如何创建一个flash message:</p><pre class="crayon-plain-tag">$this-&gt;addFlash(
            'notice',
            '你想要放置的消息'
        );
 
        // $this-&gt;addFlash 就相当于 $this-&gt;get('session')-&gt;getFlashBag()-&gt;add
 
        return $this-&gt;redirectToRoute(...); //跳转页面了</pre><p>上面这段代码，就很容易的创建了消息，并跳转到其他页面。其他页面可以在twig模板中直接接收此信息并展示，代码如下：</p><pre class="crayon-plain-tag">{% for flash_message in app.session.flashBag.get('notice') %}
    &lt;div class="flash-notice"&gt;
        {{ flash_message }}
    &lt;/div&gt;
{% endfor %}</pre><p>来看看这里app.session.flashBag.get(&#8216;notice&#8217;)，并用dump在twig模板中输出一下</p><pre class="crayon-plain-tag">{{ dump(app.session.flashBag.get('notice')) }}</pre><p>会得到一个数组</p><pre class="crayon-plain-tag">array:1 [▼
  0 =&gt; "你想要放置的消息"
]</pre><p>所以我们要使用for去遍历它们，使其获得相应的消息并输出。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1096">Symfony消息提醒Flash Message</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1096/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Twig中判断变量是否存在</title>
		<link>http://www.newlifeclan.com/symfony/archives/1094</link>
		<comments>http://www.newlifeclan.com/symfony/archives/1094#comments</comments>
		<pubDate>Wed, 02 Nov 2016 15:13:46 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=1094</guid>
		<description><![CDATA[<p>twig中检查你的变量是否被定义很容易 [crayon-69f87e764a44d817041359/] 具体 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1094">Twig中判断变量是否存在</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>twig中检查你的变量是否被定义很容易<span id="more-1094"></span></p><pre class="crayon-plain-tag">{# defined works with variable names #}
{% if foo is defined %}
    ...
{% endif %}

{# and attributes on variables names #}
{% if foo.bar is defined %}
    ...
{% endif %}

{% if foo['bar'] is defined %}
    ...
{% endif %}</pre><p>具体可参见：Twig <a href="http://twig.sensiolabs.org/doc/tests/defined.html" target="_blank">defined</a>。</p>
<p>&nbsp;</p>
<p>当然，如果你想检查一个已存在变量是否为空，也是很容易的：</p><pre class="crayon-plain-tag">{% if var is null %}
    {# do something #}
{% endif %}</pre><p>&nbsp;</p>
<p>当然还有一种比较严格的比对方式：</p><pre class="crayon-plain-tag">{% if var is sameas(false) %}
    {# do something %}
{% endif %}</pre><p>具体可参见：Twig <a href="http://twig.sensiolabs.org/doc/tests/sameas.html" target="_blank">sameas</a>。</p>
<p>&nbsp;</p>
<p>如果这个变量为：array(0) { }一个空数组，那么下面的判断很有效</p><pre class="crayon-plain-tag">{% if var|length &gt; 0 %}
    {# do something #}
{% endif %}</pre><p>&nbsp;</p>
<p>&nbsp;</p>
<p>（非twig）action中的EntityRepository-&gt;find()方法会返回一个object对象，如果数据库中没有回返回null。以下方式展示如何判断：</p><pre class="crayon-plain-tag">public function showAction($productId)
{
    $product = $this-&gt;getDoctrine()
        -&gt;getRepository('AppBundle:Product')
        -&gt;find($productId);

    if (!$product) {
        throw $this-&gt;createNotFoundException(
            'No product found for id '.$productId
        );
    }

    // ... do something, like pass the $product object into a template
}</pre><p>或者</p><pre class="crayon-plain-tag">if (null !== $entity) {
}

if ($entity instanceof Representative) {
}</pre><p>&nbsp;</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/1094">Twig中判断变量是否存在</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/1094/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在symfony3.0中使用Guard很容易的完成身份验证</title>
		<link>http://www.newlifeclan.com/symfony/archives/969</link>
		<comments>http://www.newlifeclan.com/symfony/archives/969#comments</comments>
		<pubDate>Tue, 03 May 2016 08:08:50 +0000</pubDate>
		<dc:creator><![CDATA[napoleon]]></dc:creator>
				<category><![CDATA[博客]]></category>

		<guid isPermaLink="false">http://www.newlifeclan.com/symfony/?p=969</guid>
		<description><![CDATA[<p>symfony2安全系统是框架比较复杂的地方，很多人很难明白并运用到工作中。他非常的强大和灵活，但老实说他并不 [&#8230;]</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/969">在symfony3.0中使用Guard很容易的完成身份验证</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>symfony2安全系统是框架比较复杂的地方，很多人很难明白并运用到工作中。他非常的强大和灵活，但老实说他并不简单。对于自定义身份验证，symfony之前版本有一些文章。<span id="more-969"></span></p>
<p>现在symfony已经更新到了3.0，一个新的组件也被包含到了symfony框架体系：<a href="https://github.com/symfony/security-guard">Guard</a>。这个组件的目的是与安全系统集成,并提供一个更简单的方法。他公开了一个单个接口，把你从身份验证开始到结束都链起来：逻辑和所有组合都在一起。</p>
<p>在这篇文章里，我们来创建一个简单的表单验证，需要一个用户登录并给他ROLE_ADMIN角色到每一个页面。原始的方式是创建一个form authentication，他现在仍然可以使用，但我们将使用这样一个简单的步骤来举例说明Guard。你就能够了解这个概念并复用到其他的验证（token,社交媒体等）。</p>
<h2>安全配置</h2>
<p>这个安全配置将需要一个用户类（来表示用户数据）还需要UserProvider（获取用户数据）。为了让事情简单，我们直接使用InMemory用户提供器（user provider）自然而然的我们使用默认的symfony的User类。我们的security.yml就是这个样子：</p><pre class="crayon-plain-tag">security:
    providers:
        in_memory:
            memory:
                users:
                    admin:
                        password: admin
                        roles: 'ROLE_ADMIN'</pre><p>更多关于symfony安全系统的文章请阅读book。</p>
<p>我们的InMemory提供器现在有一个写死的test用户并付给他ROLE_ADMIN.</p>
<p>下面是我们定义的防火墙：</p><pre class="crayon-plain-tag">secured_area
            anonymous: ~
            logout:
                path:   /logout
                target: /
            guard:
                authenticators:
                    - form_authenticator</pre><p>上面基本上是说，匿名用户可以访问防火墙路径，还记录了用户退出路径。这个新的guard键是表明防火墙使用Guard配置的验证器：form_authenticator。他传入的是服务名称并且我们将在下一分钟内看到他的定义。</p>
<p>最后在安全配置中,我们可以指定一些访问规则:</p><pre class="crayon-plain-tag">access_control:
            - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/, roles: ROLE_ADMIN }</pre><p>在这个示例中,我们指定没有登录的用户只能访问/login路径。对于其他用户要有<code class=" language-php"><span class="token constant">ROLE_ADMIN角色。</span></code></p>
<p>&nbsp;</p>
<h2>登录控制器（Login Controller）</h2>
<p>在真正做身份验证之前，让我们看看实际的登录表单和控制器。<code class=" language-php">DefaultController的action里面：</code></p><pre class="crayon-plain-tag">/**
   * @Route("/login", name="login")
   */
  public function loginAction(Request $request)
  {
    $user = $this-&gt;getUser();
    if ($user instanceof UserInterface) {
      return $this-&gt;redirectToRoute('homepage');
    }

    /** @var AuthenticationException $exception */
    $exception = $this-&gt;get('security.authentication_utils')
      -&gt;getLastAuthenticationError();

    return $this-&gt;render('default/login.html.twig', [
      'error' =&gt; $exception ? $exception-&gt;getMessage() : NULL,
    ]);
  }</pre><p>定义了/login路由，这个action只是负责显示一个基本的登录表单，用户不登录。twig模板如下：</p><pre class="crayon-plain-tag">{{ error }}

&lt;form action="{{ path('login') }}" method="POST"&gt;
    &lt;label for="username"&gt;Username&lt;/label&gt;
    &lt;input type="text" name="username" class="form-control" id="username" placeholder="Username"&gt;
    &lt;label for="password"&gt;Password&lt;/label&gt;
    &lt;input type="password" name="password" class="form-control"
           id="password" placeholder="Password"&gt;
    &lt;button type="submit"&gt;Login&lt;/button&gt;
&lt;/form&gt;</pre><p>到目前为止，没啥特别的。只是一个简单的表单html。</p>
<p>&nbsp;</p>
<h2>Guard Authenticator 服务</h2>
<p>我们在安全配置文件中引用了我们的Guard authenticator服务。让我们确保在sservices.yml中有这个服务定义：</p><pre class="crayon-plain-tag">services:
    form_authenticator:
          class: AppBundle\Security\FormAuthenticator
          arguments: ["@router"]</pre><p>这个服务引用了<code class=" language-php">FormAuthenticator类：</code></p><pre class="crayon-plain-tag">namespace AppBundle\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class FormAuthenticator extends AbstractGuardAuthenticator
{

  /**
   * @var \Symfony\Component\Routing\RouterInterface
   */
  private $router;

  /**
   * Default message for authentication failure.
   *
   * @var string
   */
  private $failMessage = 'Invalid credentials';

  /**
   * Creates a new instance of FormAuthenticator
   */
  public function __construct(RouterInterface $router) {
    $this-&gt;router = $router;
  }

  /**
   * {@inheritdoc}
   */
  public function getCredentials(Request $request)
  {
    if ($request-&gt;getPathInfo() != '/login' || !$request-&gt;isMethod('POST')) {
      return;
    }

    return array(
      'username' =&gt; $request-&gt;request-&gt;get('username'),
      'password' =&gt; $request-&gt;request-&gt;get('password'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getUser($credentials, UserProviderInterface $userProvider)
  {
    if (!$userProvider instanceof InMemoryUserProvider) {
      return;
    }

    try {
      return $userProvider-&gt;loadUserByUsername($credentials['username']);
    }
    catch (UsernameNotFoundException $e) {
      throw new CustomUserMessageAuthenticationException($this-&gt;failMessage);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function checkCredentials($credentials, UserInterface $user)
  {
    if ($user-&gt;getPassword() === $credentials['password']) {
      return true;
    }
    throw new CustomUserMessageAuthenticationException($this-&gt;failMessage);
  }

  /**
   * {@inheritdoc}
   */
  public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
  {
    $url = $this-&gt;router-&gt;generate('homepage');
    return new RedirectResponse($url);
  }

  /**
   * {@inheritdoc}
   */
  public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
  {
    $request-&gt;getSession()-&gt;set(Security::AUTHENTICATION_ERROR, $exception);
    $url = $this-&gt;router-&gt;generate('login');
    return new RedirectResponse($url);
  }

  /**
   * {@inheritdoc}
   */
  public function start(Request $request, AuthenticationException $authException = null)
  {
    $url = $this-&gt;router-&gt;generate('login');
    return new RedirectResponse($url);
  }

  /**
   * {@inheritdoc}
   */
  public function supportsRememberMe()
  {
    return false;
  }
}</pre><p>尽管他看起来似乎很多，其实不然。让我们一步一步去了解发生了什么事情。</p>
<p>首先，这个文件就在我们bundle的Security文件夹下，这个是一个个人习惯的选择，你随意。然后，我们继承<code class=" language-php">AbstractGuardAuthenticator，因为他已经实现了GuardAuthenticatorInterface接口所有必须要实现接口的方法。如果我们需要一个特定的token类来完成我们的验证，我们仅仅需要实现这个接口极其createAuthenticatedToken方法。现在我们不需要。</code></p>
<p>实现这个接口的方法是问题的关键，验证都在这里，这个包括当一个用户访问到授权或者拒绝访问所有流程。</p>
<p>我们从这个getCredentials()方法开始，得到每个请求。这个方法的目的是从请求获取凭证数据并返回，或者返回null（会拒绝访问，或者允许其他验证器提供凭证，或者调用start()方法）。如果是这个案例：凭证是通过/login的提交post数据，我们会获得到提交的用户名和密码，并返回一个数组。</p>
<p>如果getCredentials()方法没有返回空，则顺利进入下一个方法getUser().getUser()负责加载用户，主要根据前面getCredentials()方法获得的凭证来获得用户。使用默认的用户提供器（我们案例里的InMemory提供器），基于用户名返回要加载的用户。我们也可以在这里返回null，他会触发验证失败，我们也可以选择<code class=" language-php">CustomUserMessageAuthenticationException去指定我们自定义的失败消息。</code></p>
<p>如果上面用户被获取到并返回，这个checkCredentials方法就开始生效。这个方法的目的是核查传入的凭证和找到的用户是否匹配。更上面一样的道理，我们返回null或者抛出一个异常，验证失败。</p>
<p>此时，如果凭证匹配用户就可以登陆了。在这种情况下，onAuthenticationSuccess()方法被调用，他能做我们想做的事情。例如我们的案例，重定向到主页上就死一个很好的主意。相反的，如果验证失败，onAuthenticationFailure()方法就会被调用。我们需要做的就是重定向到/login页面，我们会把最后验证的错误信息放在session中，在表单模板显示出来。</p>
<p>start方法是Guard系统（或者应用）的切入点。这个方法是当用户尝试访问一个需要验证的页面时，他没有通过getCredentials()方法返回有效凭证，就会调用这个方法。在我们案例中就是如果有人试图访问主页，getCredentials()获取不到有用的请求，返回null，没有凭证。我们就将他重定向到/login页面，让他登录后访问主页。</p>
<p>让我们想象一下其他例子：基于token的验证。在这种情况下，每个请求需要包含一个token，来验证用户。这意味着getCredentials()将总能返回凭证。如果没有，start()方法将返回一个响应，拒绝访问。</p>
<p>最后，一个方法是负责标记remember-me功能的。在我们的例子中，没有使用它，所以返回false。关于Remember-Me的更多信息请查看symfony cookbook。</p>
<p>&nbsp;</p>
<h2>总结</h2>
<p>我们现在使用Guard组件创建了一个全功能的登录系统。还有我们上面刚刚提到的token验证的例子，他与我们今天文章主要介绍的一起工作。可以存在多个身份验证器：</p><pre class="crayon-plain-tag">guard:
                authenticators:
                    - form_authenticator
                    - token_authenticator
                entry_point: form_authenticator</pre><p>如果我们配置多个验证器，我们需要去指定哪一个是entry point（当一个用户尝试访问一个资源但是没有提供凭证，start()方法将会被调用）.</p>
<p>也就是说，我们创建多个验证器，我们提交的数据符合其中的一个即可通过验证，如果都不符合，会调用entry_point指定验证器的start()方法。</p>
<p>注意：Guard并不替换任何的symfony验证，只是补充。现有的将会继续工作。例如，form_login或者simple_form，我们之前使用过的，他们都将继续工作。</p>
<p><a rel="nofollow" href="http://www.newlifeclan.com/symfony/archives/969">在symfony3.0中使用Guard很容易的完成身份验证</a>，首发于<a rel="nofollow" href="http://www.newlifeclan.com/symfony">Symfony中文教程</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.newlifeclan.com/symfony/archives/969/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
