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/

jQuery Wookmark-2 jQuery动态表格插件的效果展示

Wookmark是一个为了展示动态表格元素的一款非常实用的jQuery插件。

你可以到http://www.wookmark.com/jquery-plugin查看其示例。

\

 

配置

1.jQuery版本1.4.3或更高版本(最好不是2.0及以上版本)

2.将jquery.wookmark.js引入

用法

该插件可以被jQuery以不同的方式调用

可以用默认的方式调用,这也是官方推荐的方式:

$(‘.myElements’).wookmark();

其中myElements是你做的表格的,并且想让它成为wookmark这种动态表格元素展示的 class 样式

配置项

01. view plaincopy在CODE上查看代码片派生到我的代码片
02.
03.$('.myElements').wookmark({ 
04.align: 'center'
05.autoResize: false
06.comparator: null
07.container: $('body'), 
08.direction: undefined, 
09.ignoreInactiveItems: true
10.itemWidth: 0, 
11.fillEmptySpace: false
12.flexibleWidth: 0, 
13.offset: 2, 
14.onLayoutChanged: undefined, 
15.outerOffset: 0, 
16.resizeDelay: 50, 
17.possibleFilters: [] 
18.});

其中上边这些配置项中提及到的参数,在我们之前写的博客上也有所提及,大家可以在那边看:http://blog.csdn.net/sunyingyuan/article/details/17613627

 

itemWidth 和 flexibleWidth

这两个字段的值可以设置为数字类型(这时是被当做像素值来处理的),当然也可以设置成百分比,当 flexibleWidth 设置成 itemWidth != 0 是被用作列的最小宽度(这个地方我也没怎么看懂英文,等我好好实验下,然后再做修改)。

更新 trigger

元素被隐藏了是一直不可见的一直到它被设置为可视状态的时候,如果你在一个隐藏的选项卡上设置成wookmark属性,并不会立刻得到效果,当然你也可以手动触发触发器使得你的wookmark是可见的。

1. view plaincopy在CODE上查看代码片派生到我的代码片
2.
3.$('#myContent').trigger('refreshWookmark');

 

 

fillEmptySpace

 

有时候为了创建一个合适的布局,比如说在底部创建一个占位符。你可以在官方示例中的 example-placeholder 查看其具体使用方法,占位符需要使用到的class是 wookmark-placeholder ,当然你也可以覆盖class样式来满足你的个性化需求。

ignoreInactiveItems

 

 

当你设置 ignoreInactiveItems 为false的时候,不活动的项依然会展示。这个方法可以用来淡出过滤项目的方式。你可以查看这个示例。

comparator

你可以通过这个选项提供一个普通的比较函数作为一个排序的方式,你可以查看 example-sort or example-stamp 查看如何使用它。

原文地址: http://www.php100.com/html/program/jquery/2014/0328/6728.html

为您搜集20款开源搜索引擎系统

一些开源搜索引擎系统介绍,包含开源Web搜索引擎和开源桌面搜索引擎。
Sphider
Sphider是一个轻量级,采用PHP开发的web spider和搜索引擎,使用mysql来存储数据。可以利用它来为自己的网站添加搜索功能。Sphider非常小,易于安装和修改,已经有数千网站在使用它。
RiSearch PHP
RiSearch PHP是一个高效,功能强大的搜索引擎,特别适用于中小型网站。RiSearch PHP非常快,它能够在不到1秒钟内搜索5000-10000个页面。RiSearch是一个索引搜索引擎,这就意味着它先将你的网站做索引并建立一个数据库来存储你网站所有页面的关键词以便快速搜索。Risearch是全文搜索引擎脚本,它把所有的关键词都编成一个文档索引除了配置文件里面的定义排除的关键词。 RiSearch使用经典的反向索引算法(与大型的搜索引擎相同),这就是为什么它会比其它搜索引擎快的原因。
PhpDig
PhpDig是一个采用PHP开发的Web爬虫和搜索引擎。通过对动态和静态页面进行索引建立一个词汇表。当搜索查询时,它将按一定的排序规则显示包含关键字的搜索结果页面。PhpDig包含一个模板系统并能够索引PDF,Word,Excel,和PowerPoint文档。PHPdig适用于专业化更强、层次更深的个性化搜索引擎,利用它打造针对某一领域的垂直搜索引擎是最好的选择。
OpenWebSpider
OpenWebSpider是一个开源多线程Web Spider(robot:机器人,crawler:爬虫)和包含许多有趣功能的搜索引擎。
Egothor
Egothor是一个用Java编写的开源而高效的全文本搜索引擎。借助Java的跨平台特性,Egothor能应用于任何环境的应用,既可配置为单独的搜索引擎,又能用于你的应用作为全文检索之用。
Nutch
Nutch 是一个开源Java 实现的搜索引擎。它提供了我们运行自己的搜索引擎所需的全部工具。包括全文搜索和Web爬虫。
Lucene
Apache Lucene是一个基于Java全文搜索引擎,利用它可以轻易地为Java软件加入全文搜寻功能。Lucene的最主要工作是替文件的每一个字作索引,索引让搜寻的效率比传统的逐字比较大大提高,Lucen提供一组解读,过滤,分析文件,编排和使用索引的API,它的强大之处除了高效和简单外,是最重要的是使使用者可以随时应自已需要自订其功能。
Oxyus
是一个纯java写的web搜索引擎。
BDDBot
BDDBot是一个简单的易于理解和使用的搜索引擎。它目前在一个文本文件(urls.txt)列出的URL中爬行,将结果保存在一个数据库中。它也支持一个简单的Web服务器,这个服务器接受来自浏览器的查询并返回响应结果。它可以方便地集成到你的Web站点中。
Zilverline
Zilverline是一个搜索引擎,它通过web方式搜索本地硬盘或intranet上的内容。Zilverline可以从PDF, Word, Excel, Powerpoint, RTF, txt, java, CHM,zip, rar等文档中抓取它们的内容来建立摘要和索引。从本地硬盘或intranet中查找到的结果可重新再进行检索。Zilverline支持多种语言其中包括中文。
XQEngine
XQEngine用于XML文档的全文本搜索引擎。利用XQuery做为它的前端查询语言。它能够让你查询XML文档集合通过使用关键字的逻辑组合。有点类似于Google与其它搜索引擎搜索HTML文档一样。XQEngine只是一个用Java开发的很紧凑的可嵌入的组件。
MG4J
MG4J可以让你为大量的文档集合构建一个被压缩的全文本索引,通过使内插编码(interpolative coding)技术。
JXTA Search
JXTA Search是一个分布式的搜索系统。设计用在点对点的网络与网站上。
YaCy
YaCy基于p2p的分布式Web搜索引擎。同时也是一个Http缓存代理服务器。这个项目是构建基于p2p Web索引网络的一个新方法。它可以搜索你自己的或全局的索引,也可以Crawl自己的网页或启动分布式Crawling等。
Red-Piranha
Red-Piranha是一个开源搜索系统,它能够真正”学习”你所要查找的是什么。Red-Piranha可作为你桌面系统(Windows,Linux与Mac)的个人搜索引擎,或企业内部网搜索引擎,或为你的网站提供搜索功能,或作为一个P2P搜索引擎,或与wiki结合作为一个知识/文档管理解决方案,或搜索你要的RSS聚合信息,或搜索你公司的系统(包括SAP,Oracle或其它任何Database/Data source),或用于管理PDF,Word和其它文档,或作为一个提供搜索信息的WebService或为你的应用程序(Web,Swing,SWT,Flash,Mozilla-XUL,PHP, Perl或c#/.Net)提供搜索后台等等。
LIUS
LIUS是一个基于Jakarta Lucene项目的索引框架。LIUS为Lucene添加了对许多文件格式的进行索引功能如:Ms Word,Ms Excel,Ms PowerPoint,RTF,PDF,XML,HTML,TXT,Open Office序列和JavaBeans.针对JavaBeans的索引特别有用当我们要对数据库进行索引或刚好用户使用持久层ORM技术如:Hibernate,JDO,Torque,TopLink进行开发时。
Apache Solr
Solr是一个高性能,采用Java5开发,基于Lucene的全文搜索服务器。文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个XML/JSON响应来实现。它的主要特性包括:高效、灵活的缓存功能,垂直搜索功能,高亮显示搜索结果,通过索引复制来提高可用性,提供一套强大Data Schema来定义字段,类型和设置文本分析,提供基于Web的管理界面等。
Paoding
Paoding中文分词是一个使用Java开发的,可结合到Lucene应用中的,为互联网、企业内部网使用的中文搜索引擎分词组件。 Paoding填补了国内中文分词方面开源组件的空白,致力于此并希翼成为互联网网站首选的中文分词开源组件。 Paoding中文分词追求分词的高效率和用户良好体验。
Carrot2
Carrot2是一个开源搜索结果分类引擎。它能够自动把搜索结果组织成一些专题分类。Carrot2提供的一个架构能够从各种搜索引擎(YahooAPI、GoogleAPI、MSN Search API、eTools Meta Search、Alexa Web Search、PubMed、OpenSearch、Lucene index、SOLR)获取搜索结果。
Regain
regain是一款与Web搜索引擎类似的桌面搜索引擎系统,其不同之处在于regain不是对Internet内容的搜索,而是针对自己的文档或文件的搜索,使用regain可以轻松地在几秒内完成大量数据(许多个G)的搜索。Regain采用了Lucene的搜索语法,因此支持多种查询方式,支持多索引的搜索及基于文件类型的高级搜索,并且能实现URL重写及文件到HTTP的桥接,并且对中文也提供了较好的支持。
Regain提供了两种版本:桌面搜索及服务器搜索。桌面搜索提供了对普通桌面计算机的文档与局域网环境下的网页的快速搜索。服务器版本主要安装在Web服务器上,为网站及局域网环境下的文件服务器进行搜索。

原文地址: http://www.php100.com/html/it/focus/2014/0404/6750.html

OpenERP API 接口

API 接口?

使用Web服务(Working with Web Services)?

鉴于OpenERP的架构,它不适合直接通过PostgreSQL客户端或者 ODBC 这样的连接方法访问数据库, 幸运的是,OpenERP提供了一个非常全面的web服务集,允许你通过标准协议做任何事情。

Note

虽然直接访问数据库,在技术上是可行的,你必须意识到这可能对您的数据造成灾难性的后果,除非你知道你是在做 什么。当你直接访问数据库的时候,建议您关闭OpenERP服务器,以避免缓存和并发问题。

支持的网络服务协议(Supported Web Services Protocols)?

目前支持的协议是XML-RPC和Net-RPC。XML-RPC是用于Web服务的第一批标准之一, 几乎可以再任何语言中使用。这是一个非常详细的协议,而且在需要的时候可以引入潜在位。 另一方面,Net-RPC是一个优化的协议,特别用在Python编写的应用程序之间。.

对于REST风格的web服务的支持在将来的OpenErp发布包中支持。

对于SOAP协议,目前的OPenErp已经不再支持,但是如果在社区有足够的爱好者的话将来可能恢复。

可用的Web服务(Available Web Services)?

OpenERP 为你提供了以下的Web服务.

Note

你能在服务的源码(/bin/service/web_services.py)的对应类里面找到每种服务的细节 .

db:
提供函数创建、删除、备份、恢复数据库. 请谨慎使用!

Technorati 标签: ,,,

common:

让你登录和退出 OpenERP, 并且提供各种实用功能。你只有登录后才能使用其他的网络服务.

object:
这是最有用的网络服务,因为通过它可以访问 OpenERP 对象. 值得注意的是, 函数 “execute” 让你调用对象的方法, 比如可以搜索的大部分的ORM方法,读写记录。它也可以用来调用价格计算等对象的其他方法.

Note

主要的 ORM 方法一览:

create({‘field’:’value’})
  • 创建一个具有指定值的新纪录
  • Returns: 新纪录的ID
search([(‘arg1’,’=’,’value1’)…], offset=0, limit=1000)
  • arg1, arg2, .. ,argN: 指定列表的搜索条件
  • offset: 跳过的可选的记录
  • limit: 返回的最大数量的记录
  • Returns: 匹配给定条件的记录
read([IDS], [‘field1’,’field2’,…])
  • fields: 返回的字段名字(默认全部返回) (default: all fields)
  • Returns: 每条记录的ID和请求字段的值
write([IDS], {‘field1’:’value1’,’field2’:3})
  • values: 更新的字段的值
  • Updates 对给定的记录按照给定的值进行更新
  • Returns: True
unlink([IDS])
  • 按照给定的IDS删除记录
  • Returns: True

通过 Web 服务不能使用 Browse() 函数.

另一个有用的功能是 “exec_workflow”, 它可以让你通过工作流制定记录的进展.

向导:

提供对旧式的向导。新风格的向导是基于ORM的,因此他们可以通过 “object” web 服务来进行访问.

报告:

让你生成和检索报告.

例子:通过Web服务写入数据(Example:writing data through the Web Service)?

下面是一个写数据的例子程序。在下一章你会发现关于多种编程语言 XML-RPC的更详尽的例子.

  1. login: 在Web服务 “common” 中调用 “login” 函数,使用下面的参数:
    • database
    • user name
    • password
  2. 创建一个新的合作者: 在Web服务 “object” 中调用 “execute” 函数,使用下面的参数:
    • database
    • user id provided by “login” in step 1.
    • the object name : ‘res.partner’
    • the name of the ORM method : “create”
    • some data to be recorded

上面提到的数据都是键值对, 比如:

  • name: Fabien Pinckaers
  • lang: fr_FR

但是更复杂的数据结构也可以发送。比如你可以在一个单一的Web服务调用中创建一个合作者 和他的地址。在那种情况下,所有的数据在服务的相同的数据库事务中来处理。这意味. 着你一定要保存好你的数据一致性的状态。这是对所有ERP应用的关键要求.

XML-RPC Web服务(XML-RPC Web Services)?

XML-RPC 是一个著名的Web服务. Web 服务是一个工具,它可以再现有的网络基础设施上面设置分布式的应用程序。这些应用程序使用一种传输层的网络但是并不提供直接通过浏览器的人机界面。可扩展标记语言(XML)提供了描述远程过程调用(RPC)的词汇表,RPC是使用超文本传输协议(HTTP) 在计算机之间传输。实际上,RPC让各开发者自行定义网络调用中的接口。这些接口可以是很简单的一个函数调用也可以像大型API那样复杂.

XML-RPC 允许在两台或者更多运行不同操作系统和不同语言程序的计算机之间协同处理。比如,一个JAVA应用可以和一个Perl应用会谈,一个Perl应用可以同一个同ASP会谈的PYTHON应用会谈,等等。系统集成商往往在不同系统之间建立自己的连接,创建它们自己定义的格式的协议来进行通信,但是这造成了大量的不常使用的协议。RPC方法的程序员无需了解底层的协议、网络以及各种实施细则.

XML-RPC 可以同 Python, Java, Perl, PHP, C, C++, Ruby, Microsoft’s .NET 以及许多其他的编程语言来一起使用。它的实现被广泛用于 Unix, Linux, Windows 和 Macintosh 的平台.

一个 XML-RPC 调用实在双方之间进行的,客户端(调用程序)和服务器(被调用过程)。服务时提供在一个特定的URL上的,比如 (such as http://example.org:8080/rpcserv/).

上面我们只是接触了 XML-RPC 的表面. 我推荐 O’Reilly’s “Programming Web Service with XML-RPC” 进行进一步的学习。还可以阅读以下几个环节:

接口(InterFaces)?
XML-RPC?
XML-RPC 架构?

OpenERP 基于C/S体系结构。服务器和客户端之间的通信使用XML-RPC协议。XML-RPC是一个非常简单的协议,它允许客户端进行远程过程调用。被调用的函数,它的参数,调用结果通过XML编码并且使用HTTP进行传输。欲了解更多的关于XML-RPC的详尽信息,请参阅: http://www.xml-rpc.com.

架构(Architecture)?

下面的图标综合了OpenERP的客户端和服务器结构。OpenERP的服务器和客户端通信使用 XML-RPC.

../_images/tech_arch.png

客户端

OpenERP 的逻辑是在服务器端配置的。客户端是很简单的,它是仅用于POST的数据(forms, lists, trees)并且把结果发回服务器。新功能的更新和加入并不需要客户端的升级,这使得OpenERP更容易维护.

客户端并不明白POST的内容。即使像点击打印图标的行动时发送到服务器并且询问如何作出反应.

客户端的操作时很简单的,当客户发出一个动作(保存一个表格、打开一个目录、打印…)它发送动作到服务器。然后服务器执行客户端的请求并将结果发送回来.

下面是三种行为;

  • Open a window (form or tree)
  • Print a document
  • Execute a wizard
Python?
通过 xml-rpc 获取数据?
代码示例?
  • 创建一个合作伙伴和他的地址
import xmlrpclib

username = 'admin' # OpenERP 登陆用户
pwd = 'admin'      # 登陆密码
dbname = 'terp'    # OpenERP 帐套

# Get the uid
sock_common = xmlrpclib.ServerProxy ('http://localhost:8069/xmlrpc/common')
uid = sock_common.login(dbname, username, pwd)

#replace localhost with the address of the server
sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/object')

partner = {
   'name': 'Fabien Pinckaers',
   'lang': 'fr_FR',
}

partner_id = sock.execute(dbname, uid, pwd, 'res.partner', 'create', partner)

address = {
   'partner_id': partner_id,
   'type' : 'default',
   'street': 'Chaussée de Namur 40',
   'zip': '1367',
   'city': 'Grand-Rosière',
   'phone': '+3281813700',
   'fax': '+3281733501',
}

address_id = sock.execute(dbname, uid, pwd, 'res.partner.address', 'create', address)
  • 查询业务伙伴
    args = [('vat', '=', 'ZZZZZZ')] # 查询过滤条件
    ids = sock.execute(dbname, uid, pwd, 'res.partner', 'search', args)
  • 读取业务伙伴数据
    fields = ['name', 'active', 'vat', 'ref'] # 需要读取的数据字段
    data = sock.execute(dbname, uid, pwd, 'res.partner', 'read', ids, fields) #ids is a list of id
  • 更新业务伙伴数据
    values = {'vat': 'ZZ1ZZZ'} # 待更新数据Dictionary
    result = sock.execute(dbname, uid, pwd, 'res.partner', 'write', ids, values)
  • 删除业务伙伴
    # ids : 待删除业务伙伴id列表
    result = sock.execute(dbname, uid, pwd, 'res.partner', 'unlink', ids)
PHP?
通过 xml-rpc 获取数据?

下载 XML-RPC PHP Library

windows / linux: 从 http://phpxmlrpc.sourceforge.net/ 上下载xmlrpc框架,目前最新的正式版本是2007年2月25日发行的2.2版本

配置 PHP XML-RPC Library

从xmlrpc-2.2.tar.gz 解压出xmlrpc.inc 文件,把这个文件放到php函数库文件夹中,重启apache/iis 服务器

代码示例

  • 登陆OpenERP
function connect() {
   var user = 'admin';
   varpassword = 'admin';
   var dbname = 'db_name';
   varserver_url = 'http://localhost:8069/xmlrpc/';

   if(isset(_COOKIE["user_id"]) == true)  {
       if(_COOKIE["user_id"]>0) {
       return _COOKIE["user_id"];
       }
   }sock = new xmlrpc_client(server_url.'common');msg = new xmlrpcmsg('login');
   msg->addParam(new xmlrpcval(dbname, "string"));
   msg->addParam(new xmlrpcval(user, "string"));
   msg->addParam(new xmlrpcval(password, "string"));
   resp =sock->send(msg);val = resp->value();id = val->scalarval();
   setcookie("user_id",id,time()+3600);
   if(id>0) {
       returnid;
   }else{
       return -1;
   }
 }
  • 查询业务伙伴
/**
 * client = xml-rpc handler
 *relation = name of the relation ex: res.partner
 * attribute = name of the attribute ex:code
 *operator = search term operator ex: ilike, =, !=
 * key=search for
 */

function search(client,relation,attribute,operator,keys) {
     var user = 'admin';
     varpassword = 'admin';
     var userId = -1;
     vardbname = 'db_name';
     var server_url = 'http://localhost:8069/xmlrpc/';key = array(new xmlrpcval(array(new xmlrpcval(attribute , "string"),
              new xmlrpcval(operator,"string"),
              new xmlrpcval(keys,"string")),"array"),
        );

     if(userId<=0) {
     connect();
     }

     msg = new xmlrpcmsg('execute');msg->addParam(new xmlrpcval(dbname, "string"));msg->addParam(new xmlrpcval(userId, "int"));msg->addParam(new xmlrpcval(password, "string"));msg->addParam(new xmlrpcval(relation, "string"));msg->addParam(new xmlrpcval("search", "string"));
     msg->addParam(new xmlrpcval(key, "array"));

     resp =client->send(msg);val = resp->value();ids = val->scalarval();

     returnids;
}
  • 创建业务伙伴
<?

include('xmlrpc.inc');

arrayVal = array(
'name'=>new xmlrpcval('Fabien Pinckaers', "string") ,
'vat'=>new xmlrpcval('BE477472701' , "string")
);client = new xmlrpc_client("http://localhost:8069/xmlrpc/object");

msg = new xmlrpcmsg('execute');msg->addParam(new xmlrpcval("dbname", "string"));
msg->addParam(new xmlrpcval("3", "int"));msg->addParam(new xmlrpcval("demo", "string"));
msg->addParam(new xmlrpcval("res.partner", "string"));msg->addParam(new xmlrpcval("create", "string"));
msg->addParam(new xmlrpcval(arrayVal, "struct"));

resp =client->send(msg);

if (resp->faultCode())

    echo 'Error: '.resp->faultString();

else

    echo 'Partner '.resp->value()->scalarval().' created !';

?>
  • 更新业务伙伴数据
/**
 * client = xml-rpc handler
 *relation = name of the relation ex: res.partner
 * attribute = name of the attribute ex:code
 *operator = search term operator ex: ilike, =, !=
 * id = id of the record to be updated
 *data = data to be updated
 */

function write(client,relation,attribute,operator,data,id) {
     var user = 'admin';
     varpassword = 'admin';
     var userId = -1;
     vardbname = 'db_name';
     var server_url = 'http://localhost:8069/xmlrpc/';id_val = array();
     id_val[0] = new xmlrpcval(id, "int");

     if(userId<=0) {
     connect();
     }msg = new xmlrpcmsg('execute');
     msg->addParam(new xmlrpcval(dbname, "string"));
     msg->addParam(new xmlrpcval(userId, "int"));
     msg->addParam(new xmlrpcval(password, "string"));
     msg->addParam(new xmlrpcval(relation, "string"));
     msg->addParam(new xmlrpcval("write", "string"));msg->addParam(new xmlrpcval(id, "array"));msg->addParam(new xmlrpcval(data, "struct"));resp = client->send(msg);
     val =resp->value();
     record =val->scalarval();

     return $record;

}
JAVA?
通过 xml-rpc 获取数据?

下载 JAVA XML-RPC Library

从 http://ws.apache.org/xmlrpc/ 上下载java xmlrpc框架,目前最新版本是2007年8月发布的3.1版本. All OpenERP errors throw exceptions because the framework allows only an int as the error code where OpenERP returns a string.

代码示例

  • 获取OpenERP帐套列表
import java.net.URL;
import java.util.Vector;

import org.apache.commons.lang.StringUtils;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

public Vector<String> getDatabaseList(String host, int port)
{
  XmlRpcClient xmlrpcDb = new XmlRpcClient();

  XmlRpcClientConfigImpl xmlrpcConfigDb = new XmlRpcClientConfigImpl();
  xmlrpcConfigDb.setEnabledForExtensions(true);
  xmlrpcConfigDb.setServerURL(new URL("http",host,port,"/xmlrpc/db"));

  xmlrpcDb.setConfig(xmlrpcConfigDb);

  try {
    //Retrieve databases
    Vector<Object> params = new Vector<Object>();
    Object result = xmlrpcDb.execute("list", params);
    Object[] a = (Object[]) result;

    Vector<String> res = new Vector<String>();
    for (int i = 0; i < a.length; i++) {
    if (a[i] instanceof String)
    {
      res.addElement((String)a[i]);
    }
  }
  catch (XmlRpcException e) {
    logger.warn("XmlException Error while retrieving OpenERP Databases: ",e);
    return -2;
  }
  catch (Exception e)
  {
    logger.warn("Error while retrieving OpenERP Databases: ",e);
    return -3;
  }
}
  • 登陆
import java.net.URL;

import org.apache.commons.lang.StringUtils;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

public int Connect(String host, int port, String tinydb, String login, String password)
{
  XmlRpcClient xmlrpcLogin = new XmlRpcClient();

  XmlRpcClientConfigImpl xmlrpcConfigLogin = new XmlRpcClientConfigImpl();
  xmlrpcConfigLogin.setEnabledForExtensions(true);
  xmlrpcConfigLogin.setServerURL(new URL("http",host,port,"/xmlrpc/common"));

  xmlrpcLogin.setConfig(xmlrpcConfigLogin);

  try {
    //Connect
    params = new Object[] {tinydb,login,password};
    Object id = xmlrpcLogin.execute("login", params);
    if (id instanceof Integer)
      return (Integer)id;
    return -1;
  }
  catch (XmlRpcException e) {
    logger.warn("XmlException Error while logging to OpenERP: ",e);
    return -2;
  }
  catch (Exception e)
  {
    logger.warn("Error while logging to OpenERP: ",e);
    return -3;
  }
}
  • 查询业务伙伴
    TODO
  • 创建业务伙伴
    TODO
  • 更新业务伙伴
    TODO
Python 代码示例?

创建合作伙伴和他们的地址的例子.

import xmlrpclib

sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/object')
uid = 1
pwd = 'demo'

partner = {
    'title': 'Monsieur',
    'name': 'Fabien Pinckaers',
    'lang': 'fr',
    'active': True,
}

partner_id = sock.execute(dbname, uid, pwd, 'res.partner', 'create', partner)

address = {
    'partner_id': partner_id,
    'type': 'default',
    'street': 'Rue du vieux chateau, 21',
    'zip': '1457',
    'city': 'Walhain',
    'phone': '(+32)10.68.94.39',
    'fax': '(+32)10.68.94.39',
}

sock.execute(dbname, uid, pwd, 'res.partner.address', 'create', address)

用下面的脚本来获得用户的 UID :

sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/common')
 UID = sock.login('terp3', 'admin', 'admin')

CRUD(创建/读取/更新/删除)代码示例:

"""
:The login function is under
::    http://localhost:8069/xmlrpc/common
:For object retrieval use:
::    http://localhost:8069/xmlrpc/object
"""
import xmlrpclib

user = 'admin'
pwd = 'admin'
dbname = 'terp3'
model = 'res.partner'

sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/common')
uid = sock.login(dbname ,user ,pwd)

sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/object')

# CREATE A PARTNER
partner_data = {'name'.. code-block:: php:'Tiny', 'active':True, 'vat':'ZZZZZ'}
partner_id = sock.execute(dbname, uid, pwd, model, 'create', partner_data)

# The relation between res.partner and res.partner.category is of type many2many
# To add  categories to a partner use the following format:
partner_data = {'name':'Provider2', 'category_id': [(6,0,[3, 2, 1])]}
# Where [3, 2, 1] are id fields of lines in res.partner.category

# SEARCH PARTNERS
args = [('vat', '=', 'ZZZZZ'),]
ids = sock.execute(dbname, uid, pwd, model, 'search', args)

# READ PARTNER DATA
fields = ['name', 'active', 'vat', 'ref']
results = sock.execute(dbname, uid, pwd, model, 'read', ids, fields)
print results

# EDIT PARTNER DATA
values = {'vat':'ZZ1ZZ'}
results = sock.execute(dbname, uid, pwd, model, 'write', ids, values)

# DELETE PARTNER DATA
results = sock.execute(dbname, uid, pwd, model, 'unlink', ids)

PRINT(打印) 示例代码:

  1. 打印发票
  2. IDS is the invoice ID, as returned by:
  3. ids = sock.execute(dbname, uid, pwd, ‘account.invoice’, ‘search’, [(‘number’, ‘ilike’, invoicenumber), (‘type’, ‘=’, ‘out_invoice’)])
import time
import base64
printsock = xmlrpclib.ServerProxy('http://server:8069/xmlrpc/report')
model = 'account.invoice'
id_report = printsock.report(dbname, uid, pwd, model, ids, {'model': model, 'id': ids[0], 'report_type':'pdf'})
time.sleep(5)
state = False
attempt = 0
while not state:
    report = printsock.report_get(dbname, uid, pwd, id_report)
    state = report['state']
    if not state:
    time.sleep(1)
    attempt += 1
    if attempt>200:
    print 'Printing aborted, too long delay !'

    string_pdf = base64.decodestring(report['result'])
    file_pdf = open('/tmp/file.pdf','w')
    file_pdf.write(string_pdf)
    file_pdf.close()
PHP 代码示例?

下面的例子是如何使用 PHP 创建一个合作伙伴. 这里使用 phpxmlrpc 库, 在 sourceforge 上有效.

<?

    include('xmlrpc.inc');

    arrayVal = array(
    'name'=>new xmlrpcval('Fabien Pinckaers', "string") ,
    'vat'=>new xmlrpcval('BE477472701' , "string")
    );client = new xmlrpc_client("http://localhost:8069/xmlrpc/object");

    msg = new xmlrpcmsg('execute');msg->addParam(new xmlrpcval("dbname", "string"));
    msg->addParam(new xmlrpcval("3", "int"));msg->addParam(new xmlrpcval("demo", "string"));
    msg->addParam(new xmlrpcval("res.partner", "string"));msg->addParam(new xmlrpcval("create", "string"));
    msg->addParam(new xmlrpcval(arrayVal, "struct"));

    resp =client->send(msg);

    if (resp->faultCode())

        echo 'Error: '.resp->faultString();

    else

        echo 'Partner '.resp->value()->scalarval().' created !';

    ?>
Perl 代码示例?

下面的例子是使用 Perl 创建、查找、删除一个合作伙伴.

#!c:/perl/bin/perl
# 17-02-2010
# OpenERP XML RPC communication example
# Todor Todorov <todorov@hp.com> <tttodorov@yahoo.com>

use strict;
use Frontier::Client;
use Data::Dumper;

my(user) = 'admin';
my(pw) = 'admin';
my(db) = 'put_your_dbname_here';
my(model) = 'res.partner';

#登录
my server_url = 'http://localhost:8069/xmlrpc/common';
myserver = Frontier::Client->new('url' => server_url);
myuid = server->call('login',db,user,pw);

print Dumper(uid);

myserver_url = 'http://localhost:8069/xmlrpc/object';
my server = Frontier::Client->new('url' =>server_url);

print Dumper(server);

#
# 创建合作伙伴
#
mypartner_data = {'name'=>'MyNewPartnerName',
                  'active'=> 'True',
          'vat'=>'ZZZZZ'};
my partner_id =server->call('execute',db,uid, pw,model, 'create', partner_data);

print Dumper(partner_id);

#
# 搜索合作伙伴
#
my query = [['vat', '=', 'ZZZZZ']];

print Dumper(query);

my ids =server->call('execute',db,uid, pw,model, 'search', query);

print Dumper(ids);

#这里等待用户输入
#OpenERP interface my be checked if partner is shown there

print /."Check OpenERP if partner is inserted. Press ENTER"./;
<STDIN>;

#
# 删除合作伙伴
#
my results =server->call('execute',db,uid, pw,model, 'unlink', ids);

print Dumper(results);

在 OpenERP 的 GTK 或 web 客户端的一切活动都是通过 XML/RPC webservices. 启动 openERP GTK 客户端 使用 ./openerp-client.py -l debug_rpc (or debug_rpc_answer) 然后你可以在 GTK 客户端操作,查看客户端操作日志, 你将看到webservice的标签。通过在日志中创建缩进将帮助您 找出它的web服务.

http://tomora.blog.51cto.com/2624569/1305859 原文

每个Web开发者必备的9个软技能

 

对于一份工作,你可能专注于修炼自己的内功,会在不自觉中忽视软技能。硬技能决定你是否能得到工作,而软技能能够表明你是否适合这份工作和适应工作环境等。所有的公司都有属于自己的文化,并努力将这些文化传承下去,所以他们必须确保每一位新员工能够适应这个环境。

许多硬技能很容易获得并可很快的提高,而软技能由于和你性格等联系紧密,可想要改变他们是一件很困难和缓慢的事情。这里有七个核心的软技能,适用于大多数的企业,它们不分行业,但作为Web开发者不应该忽视其中的任何一个。但是,有两个软技能不仅有利于Web开发者,还适用于所有的开发者,那就是往往被企业和员工忽视的:批判性思维和自负管理。

1. 交流

现代科技发展得很快,移动通信的发达,你必须确保自己能够熟练运用这些。

  • 电话或手机
  • 电子邮件
  • 在线聊天
  • 社交
  • 各种即时消息应用

而这些也只是简单的工作,你真正需要做到的是必须提高沟通技巧。作为一名Web开发者更需要自信和有效地进行相关的远程交流,对象包括:上司、同事、用户和其它开发者。

与用户沟通是一件非常困难的事情,因为你不得不向非技术人员解释技术性很强的概念。你在平时与同事交流中可以习惯性的使用一些行业术语,但不要指望用户能够理解这些术语。

另外,沟通的关键是要把握好细节,俗话说:“伟大的产品微末。”那么,你与人沟通时会使用正式的还是轻松的言语呢?不同人之间都有很大的差异。也许最关键的是,你得知道如何接受他们的批评并在批评中学习。

2. 倾听

倾听和交流是紧密相连的,但对于Web开发者需要单独拿出来讨论。

通常你会被很多杂事包围,谁也逃避不了。若是你有电话过来,请打开手机的扬声器,然后继续回到屏幕做你该做的事情,你只需要听对方说的什么,并甄选出极其重要的信息即可。如果有不明白的地方,直接说出来。例如,对于一些代码的修改或者功能的讨论,用扬声器是一个非常好的方式。

3. 适应能力

适应能力是Web开发者的一个重要技能,作为一名Web开发者,你应该学会适应各种不同的环境,看看互联网在近八年的惊人变化你就应该知道是为什么了。你至少需要适应以下几种情况:

  • 不得不学习新的技能
  • 不得不承担额外的责任
  • 你负责的项目会被突然取消
  • 工作时间会有很大的变化
  • 办公室的变化或公司搬迁到新的城市
  • 公司被合并或被收购

随着社会的发展,行业的竞争,你可能会同时面临好几个上述的问题,如果你出生在1980年~2000年,更需要以一个开放、积极去的心态来处理各种艰难的困境。

4. 合作

你永远不可能一个人完成繁杂的事情,你必须要有一个团队,而谁也不愿意不被团队成员所接纳。你需要关心每一位同事,了解他们的优缺点,有则改之无则加勉。同时,你若是遇到一个很难找到和解决的Bug,你至少可以明确地知道求助谁。如果你突然成为一个新项目组的Leader,需要按照每一个团队成员的具体情况进行有效地分配任务。

5. 态度

前面讨论了团队合作的重要性,如果没人愿意花时间和你一起愉快的玩耍,说明你真的不擅长团队合作。可以通过以下几个方法来提升这个技能:

  • 主动问候对方
  • 少抱怨
  • 提供建议,而不仅是批评
  • 不要偏激

有一个积极的态度不仅可以让你更轻松的面对挑战,也会让你的同事更乐于帮助你。

6. 职业道德

你是一个非常有原则的人吗?你是一个诚实和真诚的人吗?你是一个有道德的人吗?具备良好的职业道德,关乎着你的诚信。

你需要保持自己不被卷入企业之间的间谍活动或内幕交易,以及不要经常抱怨领导和同事、表现出负面情绪,这只能凸显出你的不诚信。如果你能够做到这些,那么你一定会被公司永远地评为“本月最佳员工”。

7. 判断/辨别

这是一个概念性的总称,包含了技术到常识的决策能力。对于上级和同事采取什么样的态度呢?在和外界沟通时,你得知道可以谈的内容尺度,什么是机密、什么是个人隐私等。具备一个良好的判断力,能够为你的职业生涯保驾护航。例如,你在发布Facebook或Twitter时需要有分寸。

8. 批判性思维

批判性思维有很多学术定义,这里只来探讨应用到工作中方法。

问题:我经常上班迟到怎么办?最简单的解决方案就是:早点去公司。

如果针对这个问题进行批判性思维,涉及到评估和分析。你需要寻找所有上班迟到可能的原因,然后评估和分析出每个原因的解决方案,每个解决方案可能会导致新的问题,需要接着进行综合的分析,最后再总结出能得到一个积极结果的方案。

作为一名Web开发者,你有时候会面临一个问题的多个解决途径,都能实现相同效果,批判性思维可以让你快速、有效地分析出最优的方法。

9. 自负管理

自负会对你的职业发展带来不可预知的积极或消极的影响。作为一名Web开发者,不管你写的代码多么出色,你也不是神、明星,充其量只能算是一个D-list 上的一员。如果你不知道如何管理自负,不妨看下以下几个方法:

  • 向他人学习。总有值得你学习人,并积极接受别人的建议。
  • 愿意分享和教别人。不要以居高临下的姿态,学会谦和地分享自己的心得和体会。
  • 提供具有建设性的批评意见而非单纯的批评。最好别说:你的代码很吓人、有点烂,而应该说:如果你这样做,你的代码会更好。建设性的批评目的是帮助别人,而不是体现出你多么的伟大。

你在团队里是为了和他人合作并一同进步,而不鼓励孤立自己或试图掌控团队。
原文地址: http://www.php100.com/html/it/focus/2014/0402/6740.html

Thinkphp实现站点静态化的方法

thinkphp提供了一个有效的生成静态页的方法,(在tp2.0的手册上有说明,3.0的手册上没有说明了,不过3.0方法还是存在的。)
$this->buildHtml('静态文件', '静态路径','模板文件');
稍微说明下参数,有些朋友问我这个参数和具体如何使用。
参数一:静态文件,是指生成后的静态文件名,文件保存路径完整的就是:静态路径/静态文件。例如静态文件设置a/index.那么保存的路径就是项
目路径/Html/a/index.html(默认的静态路径在项目路径的Html文件夹下,没有可以自己创建)
参数二:静态路径,上面已经说明了默认的路径,3.0中可以在入口文件里加入参数来更改静态路径。
define(‘HTML_PATH’, ‘./’);(将静态路径义为网站根目录)
参数三:模板文件,我感觉官方这个描述是不正确的,准确的来说应该是目标模块,就是需要生成静态文件的模块。格式:模块名:操作。比如要生成Index下的a方法为静态文件,那就是Index:a。如果为空则默认生成当前操作的静态文件。
例子:
class IndexAction extends Action {
    public function index(){
        this->buildHtml("index",'',"");this -> display();
    }
}
其实
$this->buildHtml("index",'',"");
$this->buildHtml("index",'',"Index:index");
$this->buildHtml("index",'',"index");
这样三个格式等效的
下面是静态化的实现,就两种方法
可以在当前模块下加如上述语句,那么只要运行该模块就会产生指定的目录下的”模块.html”文件,通常的做法就是站点建设好以后,专门写一个方法,然后让其执行,使整个站点一次性生成静态文件,注意:如果站点有编辑或调整那么必须清理缓存一次,就是项目下的Runtime文件夹必须清空

MySQL动态添删改列字段

动态增加列字段
alter table TableName drop column field_id;

动态删除列字段
alert TABLE table_name change old_field_name new_field_name field_type;

动态修改列字段
alert TABLE table_name change old_field_name new_field_name field_type;

thinkphp模板中对时间戳的转换

在thinkphp中前台静态html文件需要解析一个时间戳。则需要转换成时间段。

{$vo.create_time|date=’Y-m-d H:i:s’,###}

这样就可以把一个时间戳转换成我们熟悉的时间段了。