WordPress让PHP更加流行

TIOBE 7月编程语言排行榜的榜单明星为PHP,文中曾提及PHP流行背后似乎得益于最新发布的PHP Zend Framework,但并未做具体的缘由阐述。然而,促使PHP份额上升背后真正的推动者却是WordPress而并非PHP Frameworks。作者Manuel Lemos执笔详细阐述了WordPress推动PHP更加流行的七大理由。译文如下:
1.  WordPress是目前最流行的PHP应用
大多数人会选择使用几大流行的搜索网站来对比衡量目前相对流行的技术、语言、应用或者产品。然而这种方式似乎并不靠谱。因为当某个搜索网站改变其算法后,搜索结果也会随之而改变。
因此,最为靠谱的方法就是采用Google Trends,Google会根据关键字进行检索显示相关结果。如下图所示,与其他的PHP应用相比,包括Joomla、 Drupal、Magento WordPress,WordPress无疑最流行的一款。
2.  WordPress比其他PHP框架更流行
利用Google Trends,可以将不同的PHP框架进行对比。如下图所示,WordPress 与Zend Framework、Symfony PHP及CakePHP的对比情况,WordPress流行趋势则遥遥领先。
3.  WordPress不使用第三方PHP框架
此前,WordPress也使用第三方组件,但并非全栈框架都在使用。这就意味着WordPress从不需要创建全栈框架,而像这样的框架也很难流行起来。
4.  应用让技术变得更加流行,而非组件
在上面的图表中,你可能会注意到Magento要比Zend Framework更受欢迎。Magento是基于Zend Framework创建而来。这似乎暗示着,促使该技术流行的背后是能否解决用户问题,而不是基于此搭建而来的组件。
同样地,WordPress也很受欢迎,这是因为对于很多用户来说它的用处很大。当用户需要托管安装WordPress,需要寻求PHP主机服务。因此,在此基础上用户至少要知道什么是PHP以及所需要的PHP主机。
也就是说,是WordPress促使PHP更加流行。越多人安装WordPress,PHP就越流行。
5.  可扩展的WordPress插件系统
促使WordPress流行的最重要的原因之一就是各种插件支持额外功能。有了WordPress插件,你几乎能创建任何你想要的东西,包括嵌入到其他应用中,比如,论坛、电子商务网站、故障通知单系统等,除了博客系统。
倘若WordPress能够在博客应用中普及,那么其势必将成为更受欢迎的一个平台。
6.  非程序员开发PHP,只因WordPress
如果你需要创建某个网站,而这些特性却是WordPress所没有的,那么你可以通过开发一款新的插件来扩展WordPress从而满足你的需求。
许多WordPress用户并非是程序员,他们开始接触编程只是因为他们需要的某些功能在WordPress 中没有合适的插件。
也就是说,许多非程序员必须去学习PHP,才能够开发出新的WordPress 插件。这也是促使PHP更为流行的一大原因。
7.  实用优于虚构
用一句话来说,WordPress和PHP流行背后主要是因为它解决了众多用户的实际需求。无论是WordPress还是PHP的软件项目案例都是值得称赞的,它们始终遵循良好的项目计划采取超赞的设计模式。
原文出自:PHPClasses

MYSQL–事务处理

事务处理在各种管理系统中都有着广泛的应用,比如人员管理系统,很多同步数据库操作大都需要用到事务处理。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!
     删除的SQL语句
delete from userinfo where ~~~
delete from mail where ~~
delete from article where~~
~~
   如果没有事务处理,在你删除的过程中,假设出错了,只执行了第一句,那么其后果是难以想象的!
但用事务处理。如果删除出错,你只要rollback就可以取消删除操作(其实是只要你没有commit你就没有确实的执行该删除操作)

   一般来说,在商务级的应用中,都必须考虑事务处理的!

 

查看inodb信息
      shell> /usr/local/mysql -u root -p
      mysql> show variables like “have_%”
系统会提示:
+——————+——-+
| Variable_name     | Value |
+——————+——-+
| have_bdb          | YES    |
| have_crypt        | YES    |
| have_innodb       | YES    |
| have_isam         | YES    |
| have_raid         | YES    |
| have_symlink      | YES    |
| have_openssl      | NO     |
| have_query_cache | YES    |
+——————+——-+
8 rows in set (0.05 sec)
如果是这样的,那么我们就可以创建一张支持事务处理的表来试试了。

 

MYSQL的事务处理功能!

作者:Feifengxlq   Email:feifengxlq@sohu.com
一直以来我都以为MYSQL不支持事务处理,所以在处理多个数据表的数据时,一直都很麻烦(我是不得不将其写入文本文件,在系统重新加载得时候才写入数据库以防出错)~今天发现MYSQL数据库从4.1就开始支持事务功能,据说5.0将引入存储过程^_^
      先简单介绍一下事务吧!事务是DBMS得执行单位。它由有限得数据库操作序列组成得。但不是任意得数据库操作序列都能成为事务。一般来说,事务是必须满足4个条件(ACID)
      原子性(Autmic):事务在执行性,要做到“要么不做,要么全做!”,就是说不允许事务部分得执行。即使因为故障而使事务不能完成,在rollback时也要消除对数据库得影响!
     一致性(Consistency):事务得操作应该使使数据库从一个一致状态转变倒另一个一致得状态!就拿网上购物来说吧,你只有即让商品出库,又让商品进入顾客得购物篮才能构成事务!
     隔离性(Isolation):如果多个事务并发执行,应象各个事务独立执行一样!
     持久性(Durability):一个成功执行得事务对数据库得作用是持久得,即使数据库应故障出错,也应该能够恢复!
   
   MYSQL的事务处理主要有两种方法。
   1、用begin,rollback,commit来实现
        begin 开始一个事务
        rollback 事务回滚
        commit  事务确认
    2、直接用set来改变mysql的自动提交模式
       MYSQL默认是自动提交的,也就是你提交一个QUERY,它就直接执行!我们可以通过
      set autocommit=0   禁止自动提交
      set autocommit=1 开启自动提交
   来实现事务的处理。
但注意当你用 set autocommit=0 的时候,你以后所有的SQL都将做为事务处理,直到你用commit确认或rollback结束,注意当你结束这个事务的同时也开启了个新的事务!按第一种方法只将当前的作为一个事务!
个人推荐使用第一种方法!
   MYSQL中只有INNODB和BDB类型的数据表才能支持事务处理!其他的类型是不支持的!(切记!)

下次有空说下MYSQL的数据表的锁定和解锁!

       MYSQL5.0 WINXP下测试通过~   ^_^

mysql> use test;
Database changed
mysql> CREATE TABLE `dbtest`(
     -> id int(4)
     -> ) TYPE=INNODB;
Query OK, 0 rows affected, 1 warning (0.05 sec)

mysql> select * from dbtest
     -> ;
Empty set (0.01 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into dbtest value(5);
Query OK, 1 row affected (0.00 sec)

mysql> insert into dbtest value(6);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from dbtest;
+——+
| id    |
+——+
|     5 |
|     6 |
+——+
2 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into dbtest values(7);
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from dbtest;
+——+
| id    |
+——+
|     5 |
|     6 |
+——+
2 rows in set (0.00 sec)

mysql>

*******************************************************************************************************************

[PHP]
function Tran( $sql ) {
         $judge = 1;
         mysql_query(‘begin’);
         foreach (sql asv) {
                 if ( !mysql_query($v) ) {
                         $judge = 0;
                 }
         }
         if ($judge == 0) {
                 mysql_query(‘rollback’);
                 return false;
         }
         elseif ($judge == 1) {
                 mysql_query(‘commit’);
                 return true;
         }
}
[/PHP]

************************************************

<?php
$handler=mysql_connect(“localhost”,”root”,””);
mysql_select_db(“task”);
mysql_query(“SET AUTOCOMMIT=0”);//设置为不自动提交,因为MYSQL默认立即执行
mysql_query(“BEGIN”);//开始事务定义
if(!mysql_query(“insert into trans (id) values(‘2’)”))
{
mysql_query(“ROOLBACK”);//判断当执行失败时回滚
}
if(!mysql_query(“insert into trans (id) values(‘4’)”))
{
mysql_query(“ROOLBACK”);//判断执行失败回滚
}
mysql_query(“COMMIT”);//执行事务
mysql_close($handler);
?>

如何学习php框架

关于PHP框架的问题,是我们很多的一个方面。推荐一篇文章,详细介绍使用PHP框架的原因,供大家参考。

如今的PHP框架层出不穷,其中有些比其他的更加惹人耳目。我不是这方面的专家,甚至不能熟练地使用其中的一种,所以我不做推荐,也不想讨论哪些算是框架哪些不算框架(我本文的前提是所有自称为PHP框架的皆是框架)。这里我要讨论的是如何才能更快地开始使用某个新的框架。

首先你当然必须选择一个框架,比如ZF、Cake、Symfony、atk、Yii、CodeIgniter、Solar、PRADO等。选择哪个呢?有些框架提供了非常好“step-by-step”入门教程,另一些则比较难入门了。

我很喜欢这篇文章:《学习一种新的编程语言所需做的练习》,但我因为已经很熟悉PHP了,所以这个方法不能用在学习框架上。所以我想列举一些类似的练习用以学习PHP框架。

如果你还不知道怎么使用框架,何不按照本文的练习来学呢?

1、你好世界(hello world)

这里练习的必要性我就不多作说明了。在一个新的框架上构建一个应用,在页面中显示“‘hello world”。当然,做起来不会那么简单。

2、计算器(calculator)  

写一个简单的计算器程序,结合表单,对两个操作数进行加减乘除等运算,并把结果输出到页面上。你需要为每一种运算设置单独的动作,或者说方法(每个框架的叫法不一样),而且还要用到框架中的表单验证功能(如果框架提供了这一功能的话)。

3、留言板(guestbook)

让我们回到1998年——建立一个留言本程序,在页面上显示一个表单让用户填写,然后提交到这个应用程序里,继而存储到数据库或文本文件中(视你自己的情况而定),并把用户留言的内容显示到页面上。如果框架提供了创建HTML表单、对象模型或层等功能,就用上它们吧。

4、解析和分页(parse and paginate)  

选一个RSS源或其他XML源,解析其中的条目并显示出来。同时添加一个分页的功能,让用户可以五条五条地浏览,并在页面的顶部或底部添加页码。如果框架里提供了这样的功能,就使用它吧。

完成以上的练习后,你应该就可以清楚地知道如何使用这个框架了,并能用它完成更复杂的工作,或是继续探索这个框架的其他功能。如果你还是不会用,或者花了很多时间来完成以上练习,那么我觉得这个框架不适合你,还是找些其他的来用吧。

记住,如果在使用框架的过程中遇到困难,记得去该框架的社区里寻求帮助。一个有这强大社区支持的框架会为你未来的工作带来很大帮助,同时也表明这个框架是个不错的框架。没有人希望在使用某框架后的六个月,框架的开发者就宣布停止开发,这就太气人了。

希望通过本文的介绍,能给你带来帮助。

PHP开发:从程序化到面向对象编程

首先要明白此文较长,阅读时间基本在60分钟以上,请先做好准备,这是一篇不错的文章

这份教程的诞生源自一年多之前Robert C.Martin在演讲中带给我的启发。当时他的演讲主题在于讨论创造“终极编程语言”的可能性。在过程中,他提出了这样几个问题:为什么会存在“终极编程语言”?这样的语言应具备哪些特性?但随着他的讲解,我从中发现另一种有趣的思路:每种编程范式都在无形中给程序员带来诸多无法避免的局限性。为了正本溯源,我打算在正式进入PHP由程序化向面向对象转变这一话题之前,先与大家分享一些理论知识。

范式局限

每种编程范式都限制了我们将想象转化为现实的能力。这些范式去掉了一部分可行方案,却纳入另一些方案作为替代,但这一切都是为了实现同样的表示效果。模块化编程令程序规模受到制约,强迫程序员只能在对应模块范畴之内施展拳脚,且每个模块结尾都要以“go-to”来指向其它模块。这种设定直接影响了程序成品的规模。另外,结构化编程与程序化编程方式去掉了“go-to”声明,从而限制了程序员对序列、选择以及迭代语句的调整能力。序列属于变量赋值,选择属于if-else判断,而迭代则属于do-while循环。这些已经成为当下编程语言与范式的构建基石。

面向对象编程方式去掉了函数指针,同时引入多态特性。PHP使用指针的方式与C语言有所不同,但我们仍能从变量函数库中找到这些函数指针的变体形式。这使得程序员能够将某个变量的值当成函数名称,从而实现以下内容:


  1. 		function foo() {
  2.     echo “This is foo”;
  3. }
  4. function bar($param) {
  5.     echo “This is bar saying: $param”;
  6. }
  7. $function = ‘foo’;
  8. $function();        // Goes into foo()
  9. $function = ‘bar’;
  10. $function(‘test’);  // Goes into bar()

初看起来,这种特性似乎无关紧要。但仔细想想,大家一定会发现其中蕴含着极为强大的潜力。我们可以将一条变量作为参数发往某函数,然后让该函数根据参数数值调用其它函数。这绝对非同小可。它使我们能够在不了解函数功能的前提下对其进行调用,而且函数自身根本不会体现出任何差异。

这项技术也正是我们实现多态性调用的关键所在。

现在,我们姑且不谈函数指针的作用,先来看看其工作机制。函数指针中其实已经隐藏着“go-to”声明,或者至少以间接方式实现了与“go-to”相近的执行效果。这可不是什么好消息。事实上,PHP通过一种非常巧妙的方式在不直接使用的前提下实现“go-to”声明。如前例所示,我需要首先在PHP中做出声明。虽然这看起来不难理解,但在大型项目以及函数种类繁多且彼此关联的情况下,我们还是很难准确做出判断。而在C语言这边,这种关系就变得更加晦涩且极难理解。

然而仅仅消除函数指针还远远不够。面向对象的编程机制必然带来替代方案,事实也确实如此,它包含着多态特性与一套简单语法。重点来了,多态性正是面向对象编程的核心价值,即:控制流与源代码在依赖关系上正好相反。

\

在上面的图片中,我们描绘了一个简单的例子:多态性如何在两个不同范式之间发挥作用。在程序化或者结构化编程领域,控制流与源代码在依赖关系上非常相似——二者都指向更具体的输出行为。

而在面向对象编程方面,我们可以逆转源代码的依赖关系,使其指向抽象执行结果,并保持控制流仍旧指向具体执行结果。这一点至关重要,因为我们希望控制机制能尽可能触及具体层面与代码中的不稳定部分,这样我们才能真正让执行结果与预期相符。但在源代码这边,我们的要求却恰好相反。对于源代码,我们希望将具体结果与不稳定因素排除在外,从而简化修改流程、让改动尽量不影响其它代码。这样不稳定部分可以经常修正,但抽象部分则仍然有效。大家可以点击此处阅读由Robert C.Martin所撰写的依赖倒置原则研究论文。

试手任务

在本章中,我们将创建一款简单应用,旨在列出谷歌日程表及其事件提醒内容。首先,我们尝试利用程序化方式进行开发,只涉及简单功能、避免以任何形式使用类或对象。开发工作结束之后,我们更进一步、在不改动程序化代码的前提下通过行为进行代码整理。最后,尝试将其转化为面向对象版本。

谷歌PHP API客户端

谷歌专门针对PHP提供一套API客户端,我们将利用它与自己的谷歌账户进行对接,从而对日程表服务加以操作。要想让代码正确起效,大家需要通过设定让自己的谷歌账户接受来自日程表的查询。

虽然这是本篇指南文章的重要前提,但并不能算主要内容。为了避免在这方面浪费太多篇幅,请大家直接参考官方说明文档。各位不必担心,整个设置过程非常简单,而且只要五分钟左右即可搞定。

本教程附带的示例代码中包含谷歌PHP API客户端代码,建议大家就使用这一套以确保整个学习过程与文章说明保持一致。另外,如果大家想尝试自行安装,请点击此处查看官方说明文档。

接下来按照指示向apiAccess.php文件中填写信息。该文件在程序化与面向对象两套实例中都会用到,因此大家不必在新版本中重复填写。我在文件中留下了自己填写的内容,这样大家就能更轻松地找到对应位置并将其按自己的资料进行修改。

如果大家碰巧用的是NetBeans,我把各个项目文件保存在了包含有不同范例的文件夹当中。这样大家可以轻松打开该项目,并点选Run——>Run Project在本地PHP服务器(要求使用PHP 5.4)上直接加以运行。

与谷歌API对接的客户端库为面向对象型。为了示例的正常运行,我编写了一套小小的函数合集,其中囊括了本教程所需要的所有函数。通过这种方式,我们可以利用程序化层在面向对象客户端库之上进行软件编写,且代码不会涉及任何对象。

如果大家打算快速测试自己的代码与指向谷歌API的连接是否正常起效,则可以直接使用位于index.php文件中的代码。它会列出账户中所有日程表信息,且应该至少有一套具备summary字段的日程表中包含您的姓名。如果日程表中存在联系人生日信息,那么谷歌API将无法与之正常协作。不过大家不用惊慌,另选一套即可。


  1. 		require_once './google-api-php-client/src/Google_Client.php';
  2. require_once ‘./google-api-php-client/src/contrib/Google_CalendarService.php’;
  3. require_once __DIR__ . ‘/../apiAccess.php’;
  4. require_once ‘./functins_google_api.php’;
  5. require_once ‘./functions.php’;
  6. session_start();
  7. $client = createClient();
  8. if(!authenticate($client)) return;
  9. listAllCalendars($client);

这个index.php文件将成为我们应用程序的入口点。我们不会使用任何Web框架或者其它复杂的机制。我们要做的只是简单输出一些HTML代码而已。

程序化开发方案

现在我们已经了解了所需创建的目标以及所能使用的资源,接下来就是下载附件中的源代码。我会提供代码中的有用片段,但为了进一步了解全局情况,大家可能希望访问其初始来源。

在这套方案中,我们只求成果能按预期生效。我们的代码可能会显得有些粗糙,而且其中只涉及以下几个文件:

• index.php – 这是惟一一个我们需要通过浏览器直接访问并转向其GET参数的文件。

• functions_google_api.php – 囊括所有前面提到的谷歌API。

• functions.php – 一切奇迹在此发生。

functions.php将容纳应用程序的所有执行过程。包括路由逻辑、表现以及一切值与行为全部发生于此。这款应用非常简单,其主逻辑如下图所示:

\

这里有一项名为doUserAction()的函数,它的生效与否取决于一条很长的if-else声明;其它方法则根据GET变量中的参数决定调用情况。这些方法随后利用API与谷歌日程表对接,并在屏幕上显示出我们需要的任何结果。


  1. 		function printCalendarContents($client) {
  2.    putTitle(‘These are you events for ‘ . getCalendar(client, _GET[‘showThisCalendar’])[‘summary’] . ‘ calendar:’);
  3.    foreach (retrieveEvents(client, _GET[‘showThisCalendar’]) as $event) {
  4.       print(‘<div style=”font-size:10px;color:grey;”>’ . date(‘Y-m-d H:m’, strtotime($event[‘created’])));
  5.       putLink(‘?showThisEvent=’ . htmlentities($event[‘id’]) .
  6.             ‘&calendarId=’ . htmlentities(_GET[‘showThisCalendar’]), event[‘summary’]);
  7.       print(‘</div>’);
  8.       print(‘<br>’);
  9.    }
  10. }

这个例子恐怕要算我们此次编写的代码中最为复杂的函数。它所调用的是名为putTitle()的辅助函数,其作用是将某些经过格式调整的HTML输出以充当标题。标题中将包含我们日程表的实际名称,这是通过调用来自functions_google_api.php文件中的getCalendar()函数来实现的。返回的日历信息是一个数组,其中包含一个summary字段,而这正是我们要找的内容。

$client变量被传递到我们的所有函数当中。它需要与谷歌API相连,不过这方面内容我们稍后再谈。

接下来,我们整理一下日程表中的全部现有事件。这份数组列表由封装在retrieveEvents()函数中的API请求运行得来。对于每个事件,我们都会显示出其创建日期及标题。

\

其余部分代码与我们之前讨论过的内容相近,甚至更容易理解。大家可以抱着轻松的心情随便看看,然后抖擞精神进军下一章。

组织程序化代码

我们当前的代码完全没问题,但我想我们可以通过调整使其以更合适的方式组织起来。大家可能已经从附带的源代码中发现,该项目所有已经组织完成的代码都被命名为“GoogleCalProceduralOrganized”。

使用全局客户端变量

在代码组织工作中,第一件让人心烦的事在于,我们把client变量作为参数推广到全局以及嵌套函数的深层当中。程序化编程方案对这类情况提供了一种巧妙的解决办法,即全局变量。由于client是由index.php所定义,而从全局观点来看,我们需要改变的只是函数对该变量的具体使用方式。因此我们不必改变$client参数,而只需进行如下处理:


  1. 		function printCalendars() {
  2.    global $client;
  3.    putTitle(‘These are your calendars:’);
  4.    foreach (getCalendarList(client)[‘items’] as calendar) {
  5.       putLink(‘?showThisCalendar=’ . htmlentities(calendar[‘id’]), calendar[‘summary’]);
  6.       print(‘<br>’);
  7.    }
  8. }

大家不妨将现有代码与附件中的代码成品进行比较,看看二者有何不同之处。没错,我们并没有将client作为参数传递,而是在所有函数中使用globalclient并将其作为只传递向谷歌API函数的参数。从技术角度看,即使是谷歌API函数也能够使用来自全局的$client变量,但我认为最好还是尽量保持API的独立性。

从逻辑中分离表示

某些函数的作用非常明确——只用于在屏幕上输出信息,但有些函数则用于判断触发条件,更有些函数身兼两种作用。面对这种情况,我们往往最好把这些存在特殊用途的函数放在属于自己的文件当中。我们首先整理只用于屏幕信息输出的函数,并将其转移到functions_display.php文件当中。具体做法如下所示:


  1. 		function printHome() {
  2.    print(‘Welcome to Google Calendar over NetTuts Example’);
  3. }
  4. function printMenu() {
  5.    putLink(‘?home’, ‘Home’);
  6.    putLink(‘?showCalendars’, ‘Show Calendars’);
  7.    putLink(‘?logout’, ‘Log Out’);
  8.    print(‘<br><br>’);
  9. }
  10. function putLink(href, text) {
  11.    print(sprintf(‘<a href=”%s” style=”font-size:12px;margin-left:10px;”>%s</a> | ‘, href, text));
  12. }
  13. function putTitle($text) {
  14.    print(sprintf(‘<h3 style=”font-size:16px;color:green;”>%s</h3>’, $text));
  15. }
  16. function putBlock($text) {
  17.    print(‘<div display=”block”>’.$text.'</div>’);
  18. }

要完成剩余的表示分离工作,我们需要从方法中提取出表示部分。下面我们就以单一方法为例演示这一过程:


  1. 		function printEventDetails() {
  2.    global $client;
  3.    foreach (retrieveEvents(_GET[‘calendarId’]) as event)
  4.       if (event[‘id’] == _GET[‘showThisEvent’]) {
  5.          putTitle(‘Details for event: ‘. $event[‘summary’]);
  6.          putBlock(‘This event has status ‘ . $event[‘status’]);
  7.          putBlock(‘It was created at ‘ .
  8.                date(‘Y-m-d H:m’, strtotime($event[‘created’])) .
  9.                ‘ and last updated at ‘ .
  10.                date(‘Y-m-d H:m’, strtotime($event[‘updated’])) . ‘.’);
  11.          putBlock(‘For this event you have to <strong>’ . $event[‘summary’] . ‘</strong>.’);
  12.       }
  13. }

我们可以明显看到,无论if声明中的内容如何、其代码都属于表示代码,而余下的部分则属于业务逻辑。与其利用一个庞大的函数处理所有事务,我们更倾向于将其拆分为多个不同函数:


  1. 		function printEventDetails() {
  2.    global $client;
  3.    foreach (retrieveEvents(_GET[‘calendarId’]) as event)
  4.       if (isCurrentEvent($event))
  5.          putEvent($event);
  6. }
  7. function isCurrentEvent($event) {
  8.    return event[‘id’] == _GET[‘showThisEvent’];
  9. }

分离工作完成后,业务逻辑就变得简单易懂了。我们甚至提取了一个小型方法来检测该事件是否就是当前事件。所有表示代码现在都由名为putEvent($event)函数负责,且被保存在functions_display.php文件当中:


  1. 		function putEvent($event) {
  2.    putTitle(‘Details for event: ‘ . $event[‘summary’]);
  3.    putBlock(‘This event has status ‘ . $event[‘status’]);
  4.    putBlock(‘It was created at ‘ .
  5.          date(‘Y-m-d H:m’, strtotime($event[‘created’])) .
  6.          ‘ and last updated at ‘ .
  7.          date(‘Y-m-d H:m’, strtotime($event[‘updated’])) . ‘.’);
  8.    putBlock(‘For this event you have to <strong>’ . $event[‘summary’] . ‘</strong>.’);
  9. }

尽管该方法只负责显示信息,但其功能仍需在对event结构非常了解的前提下方能实现。不过对于我们的简单实例来说,这已经足够了。对于其余方法,大家可以通过类似的方式进行分离。

清除过长的if-else声明

目前代码整理工作还剩下最后一步,也就是存在于doUserAction()函数中的过长if-else声明,其作用是决定每项行为的实际处理方式。在元编程方面(通过引用来调用函数),PHP具备相当出色的灵活性。这种特性使我们能够将_GET变量的值与函数名称关联起来。如此一来,我们可以在$_GET变量中引入单独的action参数,并将该值作为函数名称。


  1. 		function doUserAction() {
  2.    putMenu();
  3.    if (!isset($_GET[‘action’])) return;
  4.       $_GET[‘action’]();
  5. }

基于这种方式,我们生成的菜单将如下所示:


  1. 		function putMenu() {
  2.    putLink(‘?action=putHome’, ‘Home’);
  3.    putLink(‘?action=printCalendars’, ‘Show Calendars’);
  4.    putLink(‘?logout’, ‘Log Out’);
  5.    print(‘<br><br>’);
  6. }

如大家所见,经过重新整理之后,代码已经呈现出面向对象式设计的特性。虽然目前我们还不清楚其面向的是何种对象、会执行哪些确切行为,但其特征已经初露端倪。

我们已经让来自业务逻辑的数据类型成为表示的决定性因素,其效果与我们在文首介绍环节中谈到的依赖倒置机制比较类似。控制流的方向仍然是从业务逻辑指向表示,但源代码依赖性则与之相反。从这一点上看,我认为整套机制更像是一种双向依赖体系。

设计倾向上的面向对象化还体现在另一个方面,即我们几乎没有涉及到元编程。我们可以调用一个方法,但却对其一无所知。该方法可以拥有任何内容,且过程与处理低级多态性非常相近。

依赖性分析

对于当前代码我们可以绘制出一份关系图,内容如下所示。通过这幅关系图,我们可以看到应用程序运行流程的前几个步骤。当然,把整套流程都画下来就太过复杂了。

\

蓝色线条代表程序调用。如大家所见,这些线条与始终指向同一个方向。图中的绿色线条则表示间接调用,可以看到所有间接调用都要经过doUserAction()函数。这两种线条代表控制流,显然控制流的走向基本不变。

红色线条则引入了完全不同的概念,它们代表着最初的源代码依赖关系。之所以说“最初”,是因为随着应用的运行其指向将变得愈发复杂、难以把握。putMenu()方法中包含着被特定关系所调用的函数的名称。这是一种依赖关系,同时也是适用于所有其它关系创建方法的基本规则。它们的具体关系取决于其它函数的行为。

上图中我们还能看到另一种依赖关系,即对数据的依赖。我前面曾经提到过calendar与event,输出函数需要清楚了解这些数组的内部结构才能实现既定功能。

完成了以上内容之后,我们已经做好充分准备、可以迎来本篇教程中的最后一项挑战。

面向对象解决方案

无论采用哪种范式,我们都不可能为问题找到完美的解决方案。因此以下代码组织方式仅仅属于我的个人建议。

从直觉出发

我们已经完成了业务逻辑与表现的分离工作,甚至将doUserAction()方法作为一个独立单元。那么我的直觉是先创建三个类,Presenter、Logic与Router。三者以后可能都需要进行调整,但我们不妨先从这里着手,对吧?

Router中将只包含一个方法,且实现方式与之前提到的方法非常相似。


  1. 		class Router {
  2.    function doUserAction() {
  3.       (new Presenter())->putMenu();
  4.       if (!isset($_GET[‘action’]))
  5.          return;
  6.       (new Logic())->$_GET[‘action’]();
  7.    }
  8. }

现在我们要做的是利用刚刚创建的Presenter对象调用putMenu()方法,其它行为则利用Logic对象加以调用。不过这样会马上产生问题——我们的一项行为并不包含在Logic类当中。putHome()存在于Presenter类中,我们需要在Logic中引入一项行为,借以在Presenter中作为putHome()方法的委托。请记住,目前我们要做的只是将现有代码整理到三个类当中,并将三者作为面向对象设计的备选对象。现在所做的一切只是为了让设计方案能够正常运作,待代码编写完成后、我们将进一步加以调试。

在将putHome()方法引入Logic类后,我们又遇上新的难题。怎样才能从Presenter中调用方法?我们可以创建一个Presenter对象,并将其传递至Logic当中。下面我们从Router类入手。


  1. 		class Router {
  2.    function doUserAction() {
  3.       (new Presenter())->putMenu();
  4.       if (!isset($_GET[‘action’]))
  5.          return;
  6.       (new Logic(new Presenter))->$_GET[‘action’]();
  7.    }
  8. }

现在我们可以向Logic添加一个构造函数,并将其添加到Presenter内指向putHome()的委托当中。


  1. 		class Logic {
  2.    private $presenter;
  3.    function __construct(Presenter $presenter) {
  4.       this->presenter = presenter;
  5.    }
  6.    function putHome() {
  7.       $this->presenter->putHome();
  8.    }
  9. […]
  10. }

通过对index.php的一些小小调整、让Presenter包含原有display方法、Logic包含原有业务逻辑函数、Router包含原有行为选择符,我们已经可以让自己的代码正常运行并具备“Home”菜单元素。


  1. 		require_once './google-api-php-client/src/Google_Client.php';
  2. require_once ‘./google-api-php-client/src/contrib/Google_CalendarService.php’;
  3. require_once __DIR__ . ‘/../apiAccess.php’;
  4. require_once ‘./functins_google_api.php’;
  5. require_once ‘./Presenter.php’;
  6. require_once ‘./Logic.php’;
  7. require_once ‘./Router.php’;
  8. session_start();
  9. $client = createClient();
  10. if(!authenticate($client)) return;
  11. (new Router())->doUserAction();

下面就是其执行效果。

\

接下来,我们需要在Logic类中适当变更指向display逻辑的调用指令,从而与this->presenter相符。现在我们有两个方法——isCurrentEvent()与retrieveEvents()——二者只被用于Logic类内部。我们将其作为专用方法,并据此变更调用关系。

下面我们对Presenter类进行同样处理,并将所有指向方法的调用都变更为指向this->something。由于putTitle()、putLink()与putBlock()都只由Presenter使用,因此需要将其变为专用。如果感到上述变更过程难于理解及操作,请大家查看附件源代码内GoogleCalObjectOrientedInitial文件夹中的已完成代码。

现在我们的应用程序已经能够正常运行,这些按面向对象语法整理过的程序化代码仍然使用$client全局变量,且拥有大量其它非面向对象式特性——但仍然能够正常运行。

如果要为目前的代码绘制依赖关系类图,则应如下所示:

\

控制流与源代码的依赖关系都通过Router、然后是Logic、最后通过表示层。最后一步变更削弱了我们在之前步骤中所观察到的依赖倒置特性,但大家千万不要因此受到迷惑——原理依然如故,我们要做的是使其更加清晰。

恢复源代码依赖关系

很难界定基础性原则之间哪一条更重要,但我认为依赖倒置原则对我们的应用设计影响最大也最直接。该原则规定:

A:高层模块不应依赖于低级模块,二者都应依赖于抽象。

B:抽象不应依赖于细节,细节应依赖于抽象。

简单来说,这意味着具体实施应依赖于抽象类。类越趋近抽象,它们就越不容易发生改变。因此我们可以这样理解:变更频繁的类应依赖于其它更为稳定的类。所以任何应用中最不稳定的部分很可能是用户界面,这在我们的应用示例中通过Presenter类来实现。让我们再来明确一下依赖倒置流程。

首先,我们让Router仅使用Presenter,并打破其对Logic的依赖关系。


  1. 		class Router {
  2.    function doUserAction() {
  3.       (new Presenter())->putMenu();
  4.       if (!isset($_GET[‘action’]))
  5.          return;
  6.       (new Presenter())->$_GET[‘action’]();
  7.    }
  8. }

然后我们变更Presenter,使其使用Logic实例并由此获取需要的信息。在我们的例子中,我认为由Presenter来建立该Logic实例也可以接受,但在生产系统当中、大家可能通常会利用Factories来创建与对象相关的业务逻辑,并将其注入表示层当中。

现在,原本同时存在于Logic与Presenter两个类中的putHome()函数将从Logic中消失。这一现象说明我们已经开始进行重复数据清除工作。指向Presenter的构造函数与引用也从Logic中消失了。另一方面,由构造函数所创建的Logic对象则必须被写入Presenter。


  1. 		class Presenter {
  2.    private $businessLogic;
  3.    function __construct() {
  4.       $this->businessLogic = new Logic();
  5.    }
  6.    function putHome() {
  7.       print(‘Welcome to Google Calendar over NetTuts Example’);
  8.    }
  9. […]
  10. }

以上变更完成之后,点击Show Calendars,屏幕上会出现错误提示。由于我们链接内部的所有行为都指向Logic类中的函数名称,因此必须通过更多一致性调整来恢复二者之间的依赖关系。下面我们对方法进行一一修改,先来看第一条错误信息:


  1. 		Fatal error: Call to undefined method Presenter::printCalendars()
  2. in /[…]/GoogleCalObjectOrientedFinal/Router.php on line 9

我们的Router希望调用Presenter中某个并不存在的方法,也就是printCalendars()。我们在Presenter中创建这样一个方法,并检查它会对Logic造成哪些影响。在结果中大家可以看到,它输出了一条标题,并在重复循环之后再次调用putCalendars()。在Presenter类中,printCalendars()方法如下所示:


  1. 		function printCalendars() {
  2.    $this->putCalendarListTitle();
  3.    foreach (this->businessLogic->getCalendars() as calendar) {
  4.       this->putCalendarListElement(calendar);
  5.    }
  6. }

在Logic方面,该方法则非常单纯——直接调用谷歌API库。


  1. 		function getCalendars() {
  2.    global $client;
  3.    return getCalendarList($client)[‘items’];
  4. }

这可能让大家心中出现两个问题,“我们真的需要Logic类吗?”以及“我们的应用程序是否存在任何逻辑?”好吧,目前我们还不知道答案,现在能做的只是继续上述过程,直到所有代码都能正常工作且Logic不再依赖于Presenter。

接下来,我们将使用Presenter中的printCalendarContents()方法,如下所示:


  1. 		function printCalendarContents() {
  2.    $this->putCalendarTitle();
  3.    foreach (this->businessLogic->getEventsForCalendar() as event) {
  4.       this->putEventListElement(event);
  5.    }
  6. }

这将反过来允许我们简化Logic中的getEventsForCalendar(),并将其转化为如下形式:


  1. 		function getEventsForCalendar() {
  2.    global $client;
  3.    return getEventList(client, htmlspecialchars(_GET[‘showThisCalendar’]))[‘items’];
  4. }

现在应用已经不再报错,但我却又发现了新的问题。_GET变量同时被Logic与Presenter类所使用——_GET应该只被Presenter类使用才对。我的意思是,由于需要创建用于填充_GET变量的链接,Presenter是肯定需要感知_GET的。这就意味着$_GET与HTTP密切相关。现在,我们希望自己的代码能与命令行或者桌面图形用户界面协同运作。

因此我们需要保证只有Presenter感知到这一情况,即将以上两个方法变换为下列内容:


  1. 		function getEventsForCalendar($calendarId) {
  2.    global $client;
  3.    return getEventList(client, calendarId)[‘items’];
  4. }

  1. 		function printCalendarContents() {
  2.    $this->putCalendarTitle();
  3.    eventsForCalendar = this->businessLogic->getEventsForCalendar(htmlspecialchars($_GET[‘showThisCalendar’]));
  4.    foreach (eventsForCalendar as event) {
  5.       this->putEventListElement(event);
  6.    }
  7. }

现在我们需要实现特定事件的输出功能。对于本文中的范例,我们假设自己无法直接检索任何事件,即必须亲自进行事件查找。Logic类这时候就要派上用场了,我们可以在其中操作事件列表并搜索特定ID:


  1. 		function getEventById(eventId, calendarId) {
  2.    foreach (this->getEventsForCalendar(calendarId) as $event)
  3.       if (event[‘id’] == eventId)
  4.          return $event;
  5. }

然后Presenter的对应调用会完成输出工作:


  1. 		function printEventDetails() {
  2.    $this->putEvent(
  3.       $this->businessLogic->getEventById(
  4.          $_GET[‘showThisEvent’],
  5.          $_GET[‘calendarId’]
  6.       )
  7.    );
  8. }

就是这样,我们已经成功完成了依赖倒置。

\

控制流仍然由Logic指向Presenter,所有输出内容也完全由Logic进行定义。这样如果我们打算接入其它日程表服务,则只需创建另一个Logic类并将其注入Presenter即可,Presenter本身不会感知到任何差异。再有,源代码依赖关系也被成功倒置。Presenter是惟一创建且直接依赖于Logic的类。这种依赖关系对于保证Presenter可随意变更数据显示方式而又不影响Logic内容而言至关重要。此外,这种依赖关系允许我们利用CLI Presenter或者其它任何向用户显示信息的方法来替代HTML Presenter。

摆脱全局变量

现在惟一漏网的潜在设计缺陷就只剩下client全局变量了。应用程序中的所有代码都会对其进行访问,但与之形成鲜明对比的是,真正有必要访问client的只有Logic类一个。最直观的解决办法肯定是使其变更为专用类变量,但这样一来我们就需要将client经由Router传递至Presenter处,从而使presenter能够利用client变更创建出Logic对象——这对于解决问题显然无甚作用。我们的设计初衷是在独立环境下建立类,并准确为其分配依赖关系。

对于任何大型类结构,我们都倾向于使用Factories;但在本文的小小范例中,index.php文件已经足以容纳逻辑创建了。作为应用程序的入口点,这个类似于高层体系结构中“main”的文件仍然处于业务逻辑的范畴之外。

因此我们将index.php中的代码变更为以下内容,同时保留所有内容以及session_start()指令:


  1. 		$client = createClient();
  2. if(!authenticate($client)) return;
  3. $logic = new Logic($client);
  4. $presenter = new Presenter($logic);
  5. (new Router($presenter))->doUserAction();

结语

现在工作彻底完成了。当然,我们的设计肯定还有很多改进的空间。我们可以为Logic类中的方法编写一些测试流程,也许Logic类本身也可以换个更有代表性的名称,例如GoogleCalendarGateway。我们还可以创建Event与Calendar类,从而更好地控制相关数据及行为,同时将Presenter的依赖关系根据数据类型拆分为数组。另一项改进与扩展方针则是创建多态性行为类,用于取代直接通过$_GET调用函数。总而言之,对于这一范例的改进可谓无穷无尽,有兴趣的朋友可以尝试将自己的想法转化为现实。我在附件的GoogleCalObjectOrientedFinal文件夹中保存有代码的最终版本,大家能够以此为起点进行探索。

如果大家的好奇心比较强,也可以试着将这款小应用与其它日程表服务对接,看看如何在不同平台上以不同方式实现信息输出。对于使用NetBeans的朋友,每个源代码文件夹中都包含有NetBeans项目,大家只要直接打开即可。在最终版本中,PHPUnit也已经准备就绪。不过我在其它项目中将其移除了——因为还没有经过测试。

感谢您的阅读。

附件下载地址:https://github.com/tutsplus/From-Procedural-to-Object-Oriented-PHP

原文链接:https://net.tutsplus.com/tutorials/php/from-procedural-to-object-oriented-php/