configuring the fastcgi extension for iis 6.0
转载: http://www.iis.net/learn/application-frameworks/install-and-configure-php-on-iis/configuring-the-fastcgi-extension-for-iis-60IntroductionFrom its first version, Internet Information Services (IIS) has supported Common Gateway Interface (CGI), which is a standards-based protocol that allows information servers, such as IIS, to interface with external applications. FastCGI was introduced to address the scalability shortcomings of CGI.This article describes the shortcomings of earlier versions of CGI and defines the need for the FastCGI extension for IIS 6.0. This article also describes the configuration settings for the FastCGI extension.CGI, ISAPI, and FastCGI in IIS 6.0 and IIS 5.1CGICGI is a protocol that allows information servers to interface with external applications. Because HTTP is stateless, any requests that are made over HTTP create a new instance of the external application in a new operating system process.Within the new process, the stdin handle is remapped so that it receives request data from the client, the stdout handle is remapped so that it writes response data to the client, and the command line and operating system environment variables are set to provide other server and request information to the CGI process.The disadvantage with CGI on IIS is the relatively expensive process creation on Windows operating systems. Every HTTP request creates a new process, performs the work inside the CGI application, and shuts down the process. On operating systems with light-weight process creation, performance is bound by the work that is completed inside the CGI application. On operating systems where process creation is expensive, such as Windows, performance of the CGI application is bound by spinning up the new process. This is the reason why CGI has performed well on a Unix-based platform, but has not been recommended for IIS.ISAPIDespite the disadvantage of CGI on Windows, IIS is capable of keeping up with, and often surpassing, the performance of other Web servers. The reason for this is Internet Server Application Programming Interface (ISAPI). Unlike CGI, ISAPI is completely internal to the Web server process. When a new request is made for an ISAPI application, a new process is not created. Instead, the Web server calls an entry point in a DLL that is loaded into the Web server process. If the ISAPI application is written with an understanding of how the operating system threading model works, the performance is extremely fast.For many years, PHP has run on IIS through both ISAPI and CGI implementations. However, both implementations have disadvantages when running on IIS. As with all CGI applications, the CGI implementation of PHP has a disadvantage due to the performance characteristics of process creation on the Windows OS. The ISAPI implementation has a disadvantage due to threading issues.When PHP runs as an ISAPI, it runs inside the Web server process in a highly multi-threaded environment. While the PHP implementation is thread-safe, many popular extensions to PHP are not thread-safe. If you use a non-thread-safe extension to PHP with ISAPI, the server can become unstable. Hence, many applications cannot run in the ISAPI PHP implementation, while other applications can run well in this environment.FASTCGIFastCGI offers a solution that delivers both performance and stability. FastCGI allows the host CGI process to remain alive after one request finishes so that the process can be reused for another request. Since the process can be reused many times, the cost of process creation on the Windows OS is no longer an issue.The technical difference between normal CGI and FastCGI is that FastCGI has a layer in the process that maps the FastCGI protocol into the stdin, stdout and other resources that CGI uses. Many third-party libraries can be linked into existing CGI source code with minor modifications to make them work with FastCGI.FastCGI on IIS runs on top of ISAPI and can be broken down into the following parts: applications, the application manager, and the FastCGI protocol support code.Because Web servers handle multiple, concurrent requests, a pool of processes must be available and ready to handle incoming requests. In the FastCGI handler, this pool of processes is called an application (to avoid confusion with IIS applications, this article uses the term "process pool"). There are a number of properties of a process pool that you can manage. For example, you can specify the number of processes in the pool, or the number of requests that a process is allowed to accept before it is shut down and recycled.The FastCGI handler supports multiple process pools so that you can run more than one kind of FastCGI on a single server. For example, you can configure your server to support both PHP and Ruby on Rails. If you have multiple sites on your server and do not want requests for those sites to share the same processes, you can have the site processes run as different users. The part of the server that handles multiple process pools is called the application manager.Configuring FastCGISCRIPT MAPSTo route requests to the FastCGI handler, you must associate the FastCGI handler with IIS. In IIS 6.0 and IIS 5.1, you can do this by using an IIS configuration setting called a "script map." Script maps are used to associate file extensions with the ISAPI handler that executes when that file type is requested. The script map also has an optional setting that verifies that the physical file associated with the request exists before allowing the request to be processed. For security reasons, this is the default setting.However, you might want to allow a request to be processed that is not associated with a physical file. The ISAPI extension that contains the FastCGI handler is called fcgiext.dll. To configure the FastCGI handler to accept PHP requests, you must create a new script map that associates the ".php" extension with fcgiext.dll. For Ruby on Rails, you must map the ".rb" extension to fcgiext.dll and so on. In IIS 6.0 it is also possible to use a wildcard character in the script map. In this situation, all requests are routed to a single ISAPI extension. If you create a wildcard script map for fcgiext.dll, all requests go to FastCGI, regardless of the file extension that is requested.To create a script map for the FastCGI handler on IIS 6.0 and IIS 5.1:Open IIS Manager.Double-click the machine icon for the local computer.Right-click Web Sites and then click Properties.Click the Home Directory tab.Click the Configuration… button.Click the Add… button.Browse to %WINDIR%\system32\inetsrv\ and select fcgiext.dll as the executable file.Note: If you are using a 64-bit platform in WOW mode, you must use the fcgiext.dll file that is located in the %WINDIR%\SysWOW64\inetsrv path.In the Extension text box, enter .php (or another extension that is specific to your FastCGI application).Under Verbs, in the Limit to text box, enter GET,HEAD,POST.Select the Script engine and Verify that file exists check boxes. Click OK.FCGIEXT.INIThe configuration for FastCGI is a collection of one or more applications. A FastCGI application consists of a pool of processes that each handle HTTP requests. Each process in the pool handles one request at a time. After each request, the process is returned to the pool to await another request. Each application is required to have its own section in the configuration file to specify its behavior.The main section of the fcgiext.ini file is the [types] section. This section associates file extensions from the URL with FastCGI applications.The general syntax is as follows:[types] abc=Application 1 def:/lm/w3svc/1701187997/root/app1=Application 2 *:/lm/w3svc/1701187997/root/app1=Application 3 def:1701187997=Application 4 *:1701187997=Application 5 def=Application 6 *=Application 7The above example consists of seven mappings that are mapped as follows:The file extension "abc" is associated with the FastCGI application named "Application 1".The file extension "def" is associated with the FastCGI application named "Application 2", but only for requests that are made to the application /app1 under the site with the numeric identifier of "1701187997". Note that application-specific mappings override site, extension-specific mappings.Requests to the application /app1 under the Web site with the identifier of "1701187997" and with a file extension other than "def" are associated with the FastCGI application named "Application 3".The file extension "def" is associated with the FastCGI application named "Application 4", but only for requests that are made to the applications other than /app1 under the Web site with the numeric identifier of "1701187997". Note that site-specific mappings override non-site-specific mappings.Requests to the applications other than /app1 under the Web site with the identifier of "1701187997" and with file extensions other than "def" are associated with the FastCGI application named "Application 5".The file extension "def" is associated with the FastCGI application named "Application 6" for requests that are not for sites with the numeric identifier of "1701187997".Requests with a file extension that does not have a specific mapping are associated with the FastCGI application named "Application 7".Note: Application names are ASCII and should generally contain only alphanumeric characters. Space characters are allowed. Application names are not case sensitive.Note: Multiple mappings can be associated with the same FastCGI application.FASTCGI APPLICATION SETTINGSThe FastCGI extension has a set of configuration settings that controls the behavior of FastCGI processes that are associated with the FastCGI process pool. This section lists all the settings that are supported by FastCGI and their format:ExePath - The physical path to the process executable to use in the pool.Arguments - Arguments to pass to each process in the pool at start time. This setting is optional.EnvironmentVars – Environment variables that are set for the process executable associated with this pool. This setting uses the following format:EnvironmentVars=Name:Value,Name:Value,…,Name:ValueExample:EnvironmentVars=VARIABLE1:10000,VARIABLE2:20000If the environment variable value contains a space character, then enclose the value in quotes. If the environment variable value contains a comma character, then this character is escaped with "/". Similarly, if the environment variable value contains "/", then this character should be escaped as well.Example:EnvironmentVars=VARIABLE1:"C://PHP",VARIABLE2="1/,2/,3"Protocol – This setting specifies the protocol to use to communicate with the FastCGI process. The allowed values are 'NamedPipe' and 'Tcp'. If not specified, the default value is 'NamedPipe'.QueueLength – This setting specifies the maximum number of requests to this application's process pool that are queued before the FastCGI handler starts returning errors to clients, indicating that the application is too busy. If not specified, the default value is 1000.MaxInstances – This is the highest number of process instances allowed in the process pool. Note that the FastCGI handler will not create this number of processes unless they are needed. If your application never receives more than two concurrent requests, your application only creates two processes. The default is 0, which means that the number of process instances will be adjusted automatically based on available memory and current CPU usage .InstanceMaxRequests – This is the number of requests that are sent to a process in the pool before it is shut down and recycled. The default value is 1000.IdleTimeout – This is the number of seconds that a process can remain idle without working on a request before it shuts down. The default is 300 seconds.ActivityTimeout – This is the number of seconds that the FastCGI handler waits for I/O activity from a process before it is terminated. The default is 30 seconds.RequestTimeout – This is the maximum amount of time that a FastCGI process is allowed to handle a request before it is terminated. The default value is 90 seconds.ResponseBufferLimit – Data from FastCGI processes is buffered before being returned to the client as responses. This property specifies the amount of response data, in bytes, that is buffered for requests to this application. This buffer is flushed to the client once it is full, or when the response is complete, whichever occurs first. If not specified, the default value is 4194304 (4MB).FlushNamedPipe – There are some cases where a FastCGI application might not read all of the data from the named pipe that communicates with the Web server. If this happens, the Web server waits for a read that is not coming, causing a deadlock on that member of the process pool. This most often happens in the case where the FastCGI process abnormally exits. For instance, the process may have an internal notion of the maximum number of requests that it can handle that is less than the InstanceMaxRequests setting. Setting FlushNamedPipe to 1 will cause FastCGI to flush data that might lead to this condition. The default value is 0.UnhealthyOnQueueFull – If the value is 1, the worker process that is hosting is flagged to IIS as unhealthy any time that the application's request queue is filled. IIS checks health whenever it does a ping to the worker process. If that worker process has been flagged as unhealthy, it (along with everything it is hosting) will be recycled. If not specified, the default value is 0.MonitorChangesTo - This property specifies the path to a file, changes to which will trigger a recycle of FastCGI executables that are running for this FastCGI process pool. If the value of this property is blank, file change monitoring is disabled. The path to a file can be absolute or relative to the folder in which the FastCGI process (as specified by ExePath) is present. If not specified, the default value is blank.StderrMode - This setting specifies how content that is received on stderr is handled. The allowed values are:'ReturnStderrIn500' - The FastCGI extension will set the response status code to 500 and send whatever was received on the stderr stream as a response. This is the same behavior as in the FastCGI Extension v1.0.'ReturnGeneric500' - The FastCGI extension will set the response status code to 500, but will return a generic 500 response.'IgnoreAndReturn200' - Data on stderr is completely ignored and the FastCGI extension will send what was received on stdout as a response, with the status code 200.'TerminateProcess' - The FastCGI extension will terminate the FastCGI process as soon as it returns anything on stderr. A generic response with the status code 500 will be sent to the HTTP client.If this setting is not specified, the default value is 'ReturnStderrIn500'.MaxInstances - This setting dictates the maximum number of FastCGI processes that can be launched for each application pool. This value is also equal to the maximum number of requests that can be processed simultaneously, since one process handles only one request at a time. This setting existed in the FastCGI Extension v1.0; however, with FastCGI Extension v1.5, the value can be set to 0, which will turn on automatic adjustment of the maximum number of instances. When the value is set to 0, the FastCGI extension will constantly analyze current CPU load and memory availability and, based on that, will increase or decrease the number of FastCGI process instances that are running at the same time.SignalBeforeTerminateSeconds - This setting specifies the number of seconds to elapse after setting the shutdown event and before calling TerminateProcess, thereby forcibly terminating the process. The default value is 0, which means that the event is not set and the FastCGI processes can be terminated abruptly at any time. If this value is greater than 0, the FastCGI process will create an event that is inherited by the child process. The value of this event's handle is set as the environment variable _FCGI_SHUTDOWN_EVENT_. The name of the named pipe, which is used to communicate with the process, is stored in the environment variable _FCGI_X_PIPE_.ActivityTimeout - This is the number of seconds that the FastCGI handler waits for I/O activity from a process before it is terminated. This setting existed in the FastCGI Extension v1.0, but in v1.5 its default value has been increased from 30 seconds to 70 seconds.USING UTF-8 ENCODING FOR SERVER VARIABLESBy default, the FastCGI extension uses ASCII encoding when setting server variables that are used by PHP. When the requested URL contains non-ASCII characters, server variables that derive their values from the requested URL string may be set incorrectly. PHP applications that rely on those server variables may not work as a result.To prevent this, the FastCGI extension can be configured to use UTF-8 encoding when setting server variables. To configure FastCGI to use UTF-8 encoding for a particular set of server variables, use the REG_MULTI_SZ registry key FastCGIUtf8ServerVariables and set its value to a list of server variable names. For example:reg add HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\w3svc\Parameters /v FastCGIUtf8ServerVariables /t REG_MULTI_SZ /d REQUEST_URI\0PATH_INFOThe above example configures the FastCGI extension to use UTF-8 encoding when setting the REQUEST_URI and PATH_INFO server variables.After setting the registry key, restart IIS by using the iisreset command.Warning: Using UTF-8 encoding for server variables may affect how PHP core and PHP applications work. Make sure to verify that applications work as expected after the registry key has been changed.Using the FastCGI Configuration ScriptTo simplify and automate the configuration steps that are described in the previous sections, a configuration script (fcgiconfig.js) is provided with the installation of the FastCGI extension. The script is located in %WINDIR%\system32\inetsrv. This script adds and removes script maps and modifies application pool settings in the fcgiext.ini file.ADDING A NEW FASTCGI MAPPINGTo add a new FastCGI mapping, run the script fcgiconfig.js with the –add switch. When using this switch, you must provide the following parameters:-section:<Section name>. This parameter specifies the name of the section that will be added to the fcgiext.ini file in the [Types] block.-extension:<file extension>. This parameter specifies which file extensions are associated with this section.-path:<file path to CGI executable>. This parameter specifies the absolute file path to the CGI executable that processes requests for files with the extension that is specified in the –extension parameter.-site:<site id>. This optional parameter specifies to which site the section should be added. If not specified, the section is added to all sites on your Web server.-application:"/w3svc/<siteid>/root/<appname>"-norecycle. By default, the configuration script recycles all application pools on IIS 6.0 for configuration changes to take effect. This optional parameter prevents this recycling.Examples:>cscript fcgiconfig.js -add -section:"PHP" -extension:php -path:"C:\PHP\php-cgi.exe"The above example adds a script map for the .php extension and updates the fcgiext.ini file.As an option, you can specify to which site the script map is applied:>cscript fcgiconfig.js -add -section:"PHP" -extension:php -path:"C:\PHP\php-cgi.exe" –site:1This example adds a script map to the "Default Web Site" only.Also, you can specify to which application the script map is applied:>cscript fcgiconfig.js -add -section:"PHP" -extension:php -path:"C:\PHP\php-cgi.exe" -application:"/w3svc/1/root/app1"REMOVING AN EXISTING FASTCGI MAPPINGTo remove an existing FastCGI mapping, run the script fcgiconfig.js with the –remove switch. When using this switch, you must provide the following parameters:-section:<Section name>. This parameter specifies the name of the section that will be removed from the fcgiext.ini file in the [Types] block.-norecycle. By default, the configuration script recycles all application pools on IIS 6.0 for configuration changes to take effect. This optional parameter prevents this recycling.This example removes the FastCGI mapping for PHP applications:>cscript fcgiconfig.js -remove -section:"PHP"CONFIGURING AN EXISTING FASTCGI MAPPINGYou can use the configuration script to set all the FastCGI configuration properties that are described earlier in this article. To set the configuration properties, use the –set switch. When using this switch, you must provide the following parameters:-section:<Section name>. This parameter specifies the name of the section to which the configuration change will be applied.-<Parameter Name>:<Parameter Value>.This parameter specifies the name of the parameter to change and its new value. The complete list of possible parameters is found in the section "Application Pool Settings".-norecycle. By default, the configuration script recycles all application pools on IIS 6.0 for configuration changes to take effect. This optional parameter prevents this recycling.This example sets the FastCGI process pool configuration property InstanceMaxRequests for the "PHP" section:>cscript fcgiconfig.js -set -section:"PHP" –InstanceMaxRequests:10000
相信很多PHP开发人员都碰到开发过程中需要共用多个php类库的问题，传统的做法是把这些类库放到项目中的独立文件夹，创建新项目就复制一份共用类库到此文件夹以做到代码重用，但这么做容易造成多个项目的共用类库版本不同步.而且每次复制也很麻烦,在团队多人开发或公司多团队配合时维护类库工作往往很繁琐，在java中这个问题是通过maven解决.maven中的依赖仓库设置可以让当前项目自动依赖其他项目类库代码.并且同步更新公用类库. php中现在有composer同样可以做到这点.composer可以在php项目中实现类似Maven功能.composer并不是一个完整的PHP项目管理工具,它没有自动化任务功能,但其解决了代码依赖和自动加载.本文分几个部分讲解如何在NetBeans中集成Composer并且管理PHP代码依赖.其也适用命令行Composer. Composer的官网地址是 https://getcomposer.org/在NetBeans中集成Composer构建Composer依赖管理项目设置自动加载导入依赖库导入私有依赖库构建可以被Composer导入的类库一. 在NetBeans中集成Composer准备工作: 我使用的是NetBeans IDE 7.4版和PHP 5.4,操作系统是Windows7 64位. 当然你使用最新的NetBeans 8.0或其他IDE也可以. 步骤都差不多. 注意在NetBeans执行Composer命令时在IDE底部输出窗口可以看到具体被执行的Composer命令,其可以作为你在命令行中使用Composer的参考. 这里就不讲解如何安装NetBeans和PHP了. 不过要注意的是NetBeans最好不要比我的版本低,而且php也要求php5.3.2以上. 这是使用Composer的最低要求.Composer集成NetBeans步骤:确保PHP安装目录已经被加载到操作系统PATH环境变量中.从 https://getcomposer.org/composer.phar 下载Composer.phar文件,将其放到PHP安装目录(就是php.exe所在目录).可选(命令行调用): 在PHP安装目录创建 composer.bat 文件,内容为@php "%~dp0composer.phar" %*注意:在linux中你需要用bash命令作为替代在NetBeans的tools - options - php - general 界面中设置PHP解释器.类似以下效果: 在NetBeans的Tools - options - php - composer 界面中设置composer路径,类似以下效果:注意:其中的联系信息和供应商改为你自己的信息.在composer使用过程中可能会提示需要svn,git,curl,openssl等支持,这个可以在具体使用时根据提示安装对应软件或php库二. 构建Composer依赖管理项目这里只介绍使用netbeans创建composer依赖管理php项目的方法. 完全手工命令行创建过程也差不多，具体需要的命令可以查看netbeans执行时的output窗口.使用netbeans创建php项目.在项目中应用composer初始化composer.其会出现一个composer.json文件，此文件就是composer的配置文件（就如同maven的pom.xml，只是功能没有那么强大）这里有必要解释composer.json的各项内容:name: 这个是当前项目名称, 其格式为 “厂商名/项目名”description: 项目描述authors: 作者信息, 具体字段可以参考 authorsrequire: 这个就是依赖库设置，等同于maven的仓库依赖设置将项目设置为web站点项目(这里只是用其做示例，实际使用中根据自己的情况可以选择)a. 在项目根目录建立public文件夹，此文件夹作为web根目录，避免暴露compoer或项目其他敏感文件.b. 在public目录创建index.php文件（我们在这里将其作为网站首页示范文件）c. 在composer.json中增加"type" : "project" 配置，增加 "license" : "proprietary" 配置. 在这里我们把其配置为项目而不是库，这样在依赖加载此项目时才可以加载所有项目文件.证书设置为私有（想来web网站大部分代码都不是开源的吧!）.现在看起来是这个样子: 在项目中安装composer代码操作后根目录会出现vendor文件夹，其内包含了所有的composer代码或composer下载的依赖库.三. 设置自动加载composer支持自动加载代码，可以自动加载类(psr-0,psr-4)或指定php文件. 这在项目中非常有用，再也不必每次写代码前都要include相关文件了.在public/index.php中写入以下代码即可自动应用composer下载的依赖库或自定义设置的命名空间request "../vendor/autolaod.php"要加载自定义类库空间请参考 https://getcomposer.org/doc/04-schema.md#autoload四. 导入依赖库这里分两种情况，一种是导入公共库，即网络上公开的php库，另一种是导入公司内部私有库. 详细可以参考 https://getcomposer.org/doc/04-schema.md#require 和 https://getcomposer.org/doc/05-repositories.md#vcs 我们这里只介绍常用的简单方法.1. 导入公共依赖库假设项目依赖smarty，步骤如下:现在你可以在index.php中自由引用smarty类，就像是你自己包含了smarty文件那样在这其中注意requrue有dev和非dev的区别，dev指的是开发版，此时如果你使用update(dev)则会更新带dev标志的requre类库，这个方法在做测试的时候非常有用，例如你本地测试可能需要phpunit或带调试的类库，而生产环境就不需要它.2. 导入私有依赖库在内部开发环境时往往有很多类库或代码需要在多个项目中重用，而这些类库又不能公开（不能上传到外网代码仓库，当然也不可以在composer公共以来库去注册.）此时,你需要的是自定义composer仓库源（就如同自定义maven源那样）. 当然如果你嫌公网下载compser依赖太慢，也可以把用自定义源覆盖公网对应类库设置. 自定义总是优先的.例如项目以来公司内部git(非git服务器请参考 https://getcomposer.org/doc/05-repositories.md#vcs )服务器上的类库"zhuolin/testlib"，composer.json设置如下:{ "name": "zhuolin/my-php-project", "description": "Description of project MyPhpProject.", "type" : "project", "license" : "proprietary", "authors": [ { "name": "Shi Zhuolin", "email": "shizhuolin@hotmail.com" } ], "repositories": [ { "type": "git", "url": "http://192.168.1.254/testlib.git", "vendor-alias": "zhuolin" } ], "require": { "smarty/smarty": "v3.1.15", "zhuolin/testlib": "dev-master" } }这里注意，repositories就是自定义的依赖仓库，我这里自定义公司内部服务器，但由于内部服务器没有按供应商名称给项目分目录（毕竟都是公司内部使用）。所以给其取了一个"zhuolin"的供应商别名，这样才符合require的依赖库命名规范. 其中dev-master就是testlib.git的master分支，只要不是创建git-tag 则都会存在dev类似的名称（表示开发中，随时可能变动）. 设置好compoesr.json后执行composer update即可自动下载testlib代码到vendor目录中，并且根据类库的composer.json设置自动加载（请看后文“构建可以被Composer导入的类库”）。五.构建可以被Composer导入的类库要注意，一般情况下只有带composer.json的类库才可以被composer作为依赖管理，这里就讲解如何创建支持此规范的compoer依赖类库.创建composer依赖类库其实和创建composer网站项目区别不大，主要区别就两个地方.composer.json中的type值是library. 表示这是一个类库而不是一个全覆盖项目.必须在composer.json设置自动加载类空间，这样此库被依赖加载后，才可以自动导入其命名空间以被其他项目使用.例如zend\file类库的composer.json如下:"autoload": { "psr-0": { "Zend\\File\\": "" } },表示zend\file前缀类都在根目录开始寻找.其autoload配置节可以让此依赖被加载后自动更新composer的autoload.php,这样在你的项目中就可以自由引用zend\file命名空间类.
测试条件: I3 380M + 4G + Windows7 + iis7 + PHP5.4(fastcgi) + Zend2.3网站设置: 使用默认的模块和控制器,配置config和module缓存. 不连接任何服务和数据库.为避免带宽不足清空view测试软件: Jmeter 20并发用户.测试机和被测试机是不同的电脑，它们之间用100M5类线直连在测试结果中,平均响应时间需要0.637秒,每秒处理30个请求.这个数据比zf1低一半. 同一台机zf1可以达到每秒60多个请求我看到这个测试结果后有点想放弃zf2,可是在开发灵活和便捷性上zf2确实要比zf1要好得多. 分析发现,在zf2中路由，派遣和服务注册，模块注册拖累了整个速度. 以前做项目用到过yaf,发现yaf性能挺好的,于是计划用yaf代替zf2的mvc模块.一. 在yaf项目中加载zf2类库首先你得有一个能正常运行的yaf项目.在这个基础上改动以增加zf2支持.可以参考yaf官方教程做一个yaf的基本架构站点. 这里假设你已按照官方教程架设好yaf站点1. 在yaf项目中使用composeryaf官网示范中默认没有zf2的类加载器，考虑到今后还要加载其它的类库代码，这里使用composer加载zf2依赖. 要在yaf项目中引用composer需要对yaf项目的/public/index.php和/conf/application.ini做少量改动.<?php /** * This makes our life easier when dealing with paths. Everything is relative * to the application root now. */ chdir(dirname(__DIR__)); // Decline static file requests back to the PHP built-in webserver if (php_sapi_name() === 'cli-server' && is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))) { return false; } // Setup autoloading require 'init_autoloader.php'; // Define path to application directory define("APP_PATH", dirname(__DIR__)); // Create application, bootstrap, and run $app = new Yaf_Application(APP_PATH . "/conf/application.ini"); $app->bootstrap() ->run();改动后的文件和yaf默认文件主要的区别:要增加chdir(dirname(__DIR__)). 目的是让zf2能正常读取配置文件require 'init_autoloader.php'; 这个文件是在ZendSkeletonApplication.zip中复制出来的，目的是引用composer, zf2类库就要通过这个文件加载进来.$app->bootstrap() 必须要有引导器，因为后面要在bootstrap里面加载zf2配置和服务因为yaf默认会禁用spl加载器，这会造成无法加载非yaf前缀命名的类.所以我们还需要更改yaf运行配置.在/conf/application.ini中设置 application.system.use_spl_autoload = true[product] application.directory = APP_PATH "/application/" application.dispatcher.catchException = true application.system.use_spl_autoload = true总结一下，你需要更改 index.php,application.ini增加init_autoloader.php文件，哦!别忘记了编辑composer.json在其中增加zf2依赖配置. 我这里用的是zf2.3. 然后更新composer依赖. 等待composer依赖更新完成后测试项目，看看在yaf项目中是否可以用zf2的类库,这里应该可以引用，如果还是不行就请下载本文后面提供的完整代码.<?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/ZendSkeletonApplication for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * This autoloading setup is really more complicated than it needs to be for most * applications. The added complexity is simply to reduce the time it takes for * new developers to be productive with a fresh skeleton. It allows autoloading * to be correctly configured, regardless of the installation method and keeps * the use of composer completely optional. This setup should work fine for * most users, however, feel free to configure autoloading however you'd like. */ // Composer autoloading if (file_exists('vendor/autoload.php')) { $loader = include 'vendor/autoload.php'; } $zf2Path = false; if (is_dir('vendor/ZF2/library')) { $zf2Path = 'vendor/ZF2/library'; } elseif (getenv('ZF2_PATH')) { // Support for ZF2_PATH environment variable or git submodule $zf2Path = getenv('ZF2_PATH'); } elseif (get_cfg_var('zf2_path')) { // Support for zf2_path directive value $zf2Path = get_cfg_var('zf2_path'); } if ($zf2Path) { if (isset($loader)) { $loader->add('Zend', $zf2Path); } else { include $zf2Path . '/Zend/Loader/AutoloaderFactory.php'; Zend\Loader\AutoloaderFactory::factory(array( 'Zend\Loader\StandardAutoloader' => array( 'autoregister_zf' => true ) )); } } if (!class_exists('Zend\Loader\AutoloaderFactory')) { throw new RuntimeException('Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.'); } "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.3.*" }2. 让Yaf项目中的zf2配置文件起作用上面的改动完成后在yaf中可以使用zf2的类库了，这还不够，我们还要在项目中使用zf2的框架配置和服务管理器. 在改动前需要先了解yaf的限制.yaf是c语言写的mvc框架，配置文件默认是applicatin.ini. yaf并不像zf2那样包含各种功能模块,特别是yaf不能直接使用zf2的配置文件.也就是说zf2的配置在yaf上是没有效果的.为了让zf2配置起作用，我们需要在yaf中调用zf2的服务管理模块以读取配置, 然后把配置好的服务模块注册到yaf_register中.zf2如果除开mvc模块（这里我们用yaf代替了zf2-mvc），最核心的就是servermanager。所有的配置项，所有的服务类，包括事件等都是由servermanager管理，如需在yaf中使用zf2配置，就要把servermanager移植到yaf中. 在zf2中servermanager是最先被执行的，并且把实例作为参数由各种后续对象传递，在yaf中我们使用yaf_regisger传递servermanager实例. 由于我们用yaf代替了zf2的mvc模块，所以在我们移植后的项目中zf2配置中的mvc部分是没有作用的我们先把zf2的配置文件复制到/conf中，注意zf2配置文件默认在/config目录，而yaf配置是在/conf中，为了让配置文件能正常工作，请把zf2应用配置文件application.config.php中的config_glob_paths配置文件路径由/config改为/conf. 除此以外其它的配置项不需要做任何更改.接下来需要改动yaf的Bootstrap.php文件,还记得index.php中的bootstrap()么?,那就是为了运行这个文件准备的. 在这个文件中所有_init开头的方法都会被自动执行,我们把注册servermanager和读取zf2配置的方法写在这里,这样yaf项目启动的时候配置文件就起作用了. 然后在这里把通过配置生成的servermanager对象注册给yaf_regisger.这样在整个yaf中就可以像zf2那样使用服务管理器调用各种功能(不能调用mvc相关模块).<?php use Zend\ServiceManager\ServiceManager; use Zend\Mvc\Service\ServiceManagerConfig; use Zend\ModuleManager\Listener\ConfigListener; use Zend\ModuleManager\Listener\ListenerOptions; use Zend\ModuleManager\ModuleEvent; class Bootstrap extends Yaf_Bootstrap_Abstract { public function _initConfig() { $config = Yaf_Application::app()->getConfig(); Yaf_Registry::set("config", $config); } public function _initServiceManager() { $configuration = require APP_PATH . '/conf/application.config.php'; $smConfig = isset($configuration['service_manager']) ? $configuration['service_manager'] : array(); $serviceManager = new ServiceManager(new ServiceManagerConfig($smConfig)); $serviceManager->setService('ApplicationConfig', $configuration); $configListener = new ConfigListener(new ListenerOptions($configuration['module_listener_options'])); // If not found cache, merge config if (!$configListener->getMergedConfig(false)) $configListener->onMergeConfig(new ModuleEvent); // If enabled, update the config cache if ($configListener->getOptions()->getConfigCacheEnabled() && !file_exists($configListener->getOptions()->getConfigCacheFile())) { $configFile = $configListener->getOptions()->getConfigCacheFile(); $content = "<?php\nreturn " . var_export($configListener->getMergedConfig(false), 1) . ';'; file_put_contents($configFile, $content); } $serviceManager->setService('config', $configListener->getMergedConfig(false)); Yaf_Registry::set('ServiceManager', $serviceManager); } public function _initSessionManager() { Yaf_Registry::get('ServiceManager')->get('Zend\Session\SessionManager'); } public function _initPlugin(Yaf_Dispatcher $dispatcher) { $user = new UserPlugin(); $dispatcher->registerPlugin($user); } }二. 在yaf项目使用zf2服务管理器调用数据库,cache,日志调用各种zf2的服务功能时,需要先确保你的zf2配置中有对应的设置,具体设置参考zf2官方手册,本文后面也有示例代码下载.<?php use Zend\Session\Container as SessionContainer; use Zend\Db\TableGateway\TableGateway; class IndexController extends Yaf_Controller_Abstract { public function indexAction() { if (!isset($session->n)) { $session->n = 0; } $session->n++; echo $session->n; $adapter = $this->getDbAdapter(); $table = new TableGateway('admin', $adapter); $entities = $table->select(); foreach ($entities as $entity) { var_dump($entity->username); } $cache = $this->getStorage(); $cache->setItem('asdfas', 'asdfasdf'); $this->getLogger()->alert('asdfasd'); $this->getView()->assign("content", "Hello World"); } /** * db adapter * @return \Zend\Db\Adapter\Adapter */ public function getDbAdapter() { return Yaf_Registry::get('ServiceManager')->get('Zend\Db\Adapter\Adapter'); } /** * storage * @return \Zend\Cache\Storage\StorageInterface */ protected function getStorage() { return Yaf_Registry::get('ServiceManager')->get('Zend\Cache\Storage\StorageInterface'); } /** * logger * @return \Zend\Log\Zend\Log\Logger */ protected function getLogger() { return Yaf_Registry::get('ServiceManager')->get('Zend\Log\Logger'); } }三. 测试yaf集成zf2后的性能我们使用jmeter测试改动后的yaf项目性能,测试条件和参数与上次测试zf2相同,结果如下:现在测试数据好看多了,平均响应时间提高到81毫秒, 每秒响应请求数提高到241个. 其实我还测试过没有集成zf2服务管理器的yaf性能,每秒请求数可以达到1200(这是11年的I3移动版CPU的老笔记本电脑,每秒1200已经很强了),但是不提供服务的项目性能再好都没有意义.这是一个完整的yaf集成zf2的项目示例.下载后请把把其中的dist配置文件复制并去掉.dist扩展名. yaf-sample-d771c9a.tar
IIS配置php提供web服务. 短期访问正常,长时间高压力访问容易崩溃.表现为IIS提示500错误，php-cgi.exe崩溃. 此时在php日志中看不到任何异常，但windows事件日志记录下了php-cgi.exe应用程序错误. 其原因和php内存管理有关.有以下改善方法1. 服务器配置足够物理内存.根据任务管理器界面的物理内存数据，保留更多的空闲物理内存给php-cgi.exe使用. 避免总内存占用超过物理内存.2. 给php-cgi.exe配置足够内存.php内存回收机制决定了达到一半的内存占用时就会运行gc. 频繁的gc造成php崩溃(windows版php的bug);根据实际情况可以改大些. 最好超过实际占用值的两倍以上(任务管理器可以查看php-cgi进程得知实际占用值) memory_limit = 128M程序中如下代码，效果和更改php.ini相同.ini_set('memory_limit','128M');3. fastcgi配置请求超过一定数量（例如1000） 就重启进程. MaxInstances=64 #根据内存和CPU情况配置最大php-cgi.exe进程数. 不要服务器压力过大. InstanceMaxRequests=1000 #每处理1000个请求则重启实例. EnvironmentVars=PHP_FCGI_MAX_REQUESTS:1000 #同上如果64/1000还不行，可以配置更少4.减少请求处理内存占用和处理时长.优化PHP代码，提高程序性能和减少内存占用。比如使用php完成采集任务时避免一次采集过多内容（那样会占用大量的内存和单次cpu处理时间）.5. 可以考虑在php处理过程中关闭gc.虽然关闭gc会造成php内存占用增大，但可以避免频繁运行gc造成php-cgi.exe崩溃.gc_collect_cycles(); gc_disable(); ....php业务代码. gc_enable();以上方法可以极大的改善windows版php的运行稳定性. 总结就是以下几点:不要让PHP服务器满负荷运行.尽可能减少PHP引擎的内存回收次数（通过优化代码降低内存占用或关闭gc实现）
zend2数据层配置和代码示范(带一对多关系)表 admin, log,数据模型代码和工厂类,配置,控制器在模块Admin的调用示范db.sqlCREATE TABLE `admin` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号', `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(40) NOT NULL COMMENT '登陆密码', `disabled` tinyint(1) NOT NULL COMMENT '禁用状态', `name` varchar(64) NOT NULL COMMENT '姓名', `title` varchar(64) NOT NULL COMMENT '职位', `phone` varchar(64) NOT NULL COMMENT '联系电话', `email` varchar(320) NOT NULL COMMENT '邮件地址', `address` varchar(255) NOT NULL COMMENT '联系地址', `description` text NOT NULL COMMENT '其他记录', `addtime` bigint(20) NOT NULL COMMENT '增加时间', `uptime` bigint(20) NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `username_UNIQUE` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='管理员数据表'; CREATE TABLE `adminlog` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号', `admin` int(11) NOT NULL COMMENT '管理员', `content` text NOT NULL COMMENT '内容', `time` bigint(20) NOT NULL COMMENT '时间', PRIMARY KEY (`id`), KEY `fk_adminlog_admin_idx` (`admin`), CONSTRAINT `fk_adminlog_admin` FOREIGN KEY (`admin`) REFERENCES `admin` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=220 DEFAULT CHARSET=utf8 COMMENT='管理日志';return array( 'service_manager' => array( 'factories' => array( 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory', ), ), );编辑 "/config/application.config.php" 注册数据库工厂服务return array( 'db' => array( 'driver' => 'pdo', 'platform' => 'Mysql', 'pdodriver' => 'mysql', 'options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8\'') ), );编辑 "/config/autoload/global.php" 配置数据库参数return array( 'db' => array( 'hostname' => '服务器', 'dbname' => '数据库名', 'username' => '账号', 'password' => '密码', ), );编辑 "/config/autoload/local.php" 配置数据库连接信息<?php namespace Admin; use Zend\Db\ResultSet\ResultSet; use Zend\Db\TableGateway\TableGateway; use Zend\ModuleManager\Feature\ServiceProviderInterface; class Module implements ServiceProviderInterface { public function getServiceConfig() { return array("factories" => array( 'Admin\Model\AdminTable' => function($serviceManager) { $resultset = new Model\Admin(); $resultset->setServiceLocator($serviceManager) $resultSetPrototype = new ResultSet(); $resultSetPrototype->setArrayObjectPrototype($resultset); return new Model\AdminTable(new TableGateway('admin', $serviceManager->get('Zend\Db\Adapter\Adapter'), null, $resultSetPrototype)); }, 'Admin\Model\LogTable' => function($serviceManager) { $resultset = new Model\Log(); $resultset->setServiceLocator($serviceManager) $resultSetPrototype = new ResultSet(); $resultSetPrototype->setArrayObjectPrototype($resultset); return new Model\LogTable(new TableGateway('adminlog', $serviceManager->get('Zend\Db\Adapter\Adapter'), null, $resultSetPrototype)); }, )); } }编辑 "/module/Admin/Module.php" 将XXXTable作为服务注册. 并且注册时帮定数据实例(Model\XXX)类，$resultset->setServiceLocator($serviceManager) 是可选调用，用于给实例中的服务设置调用，如果数据行中用不上则可以不调用此行<?php namespace Admin\Model; use Zend\ServiceManager\ServiceLocatorAwareInterface; /** * entity of Admin * * @author ShiZhuolin */ class Admin implements ServiceLocatorAwareInterface { /** * 服务 * @var \Zend\ServiceManager\ServiceLocatorInterface */ protected $servicelocator; /** * 自动编号 * @var int */ public $id; /** * 用户名 * @var string */ public $username; /** * 登陆密码 * @var string */ public $password; /** * 禁用状态 * @var bool */ public $disabled; /** * 姓名 * @var string */ public $name; /** * 职位 * @var string */ public $title; /** * 联系电话 * @var string */ public $phone; /** * 邮件 * @var string */ public $email; /** * 联系地址 * @var string */ public $address; /** * 描述 * @var string */ public $description; /** * 增加时间 * @var int */ public $addtime; /** * 更新时间 * @var int */ public $uptime; /** * 交换数组数据 * @param array $data * @return void */ public function exchangeArray($data) { $this->id = isset($data['id']) ? $data['id'] : null; $this->username = isset($data['username']) ? $data['username'] : null; $this->password = isset($data['password']) ? $data['password'] : null; $this->disabled = isset($data['disabled']) ? $data['disabled'] : null; $this->name = isset($data['name']) ? $data['name'] : null; $this->title = isset($data['title']) ? $data['title'] : null; $this->phone = isset($data['phone']) ? $data['phone'] : null; $this->email = isset($data['email']) ? $data['email'] : null; $this->address = isset($data['address']) ? $data['address'] : null; $this->description = isset($data['description']) ? $data['description'] : null; $this->addtime = isset($data['addtime']) ? $data['addtime'] : null; $this->uptime = isset($data['uptime']) ? $data['uptime'] : null; } /** * 输出数组 * @return array */ public function getArrayCopy() { return array( 'id' => $this->id, 'username' => $this->username, 'password' => $this->password, 'disabled' => $this->disabled, 'name' => $this->name, 'title' => $this->title, 'phone' => $this->phone, 'email' => $this->email, 'address' => $this->address, 'description' => $this->description, 'addtime' => $this->addtime, 'uptime' => $this->uptime, ); } /** * 记录日志 * @param string $message */ public function log($message) { $log = new \Admin\Model\Log; $log->admin = $this->id; $log->content = $message; $log->time = time(); $this->getServiceLocator()->get('Admin\Model\LogTable')->insert($log); } public function getServiceLocator() { return $this->servicelocator; } public function setServiceLocator(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator) { $this->servicelocator = $serviceLocator; } }编辑 "\module\Admin\src\Admin\Model\Admin.php" 数据实体<?php namespace Admin\Model; use Zend\Db\ResultSet\ResultSet; use Zend\Db\TableGateway\TableGateway; /** * table of AdminTable * * @author ShiZhuolin */ class AdminTable { /** * TableGateway * @var TableGateway */ protected $tableGateway; /** * construct * @param TableGateway $tableGateway */ public function __construct(TableGateway $tableGateway) { $this->tableGateway = $tableGateway; } /** * TableGateway * @return TableGateway */ public function getTableGateway() { return $this->tableGateway; } /** * fetch data * @param Where|\Closure|string|array $where * @return ResultSet|Admin */ public function fetch($where = null) { return $this->tableGateway->select($where); } /** * find by id * @param int $id * @return Admin */ public function find($id) { return $this->tableGateway->select(array('id' => (int) $id))->current(); } /** * insert data * @param Admin $admin * @return int */ public function insert(Admin $admin) { $data = $admin->getArrayCopy(); unset($data['id']); return $this->tableGateway->insert($data); } /** * update data * @param Admin $admin * @return int */ public function update(Admin $admin) { $data = $admin->getArrayCopy(); unset($data['id']); return $this->tableGateway->update($data, array('id' => (int) $admin->id)); } /** * delete by id * @param int $id * @return int */ public function delete($id) { return $this->tableGateway->delete(array('id' => (int) $id)); } }编辑 "module\Admin\src\Admin\Model\AdminTable.php" 数据操作工厂.$this->getServiceLocator()->get('Admin\Model\AdminTable')在控制器中调用 AdminTable实例. 通过Tables实例操作数据库表
<?php /** * HSL色彩空间描述 * HSL.php * @author shizhuolin */ class HSL { /** * 色相 0-360 * @var float */ protected $_hue; /** * 饱和度 0-1 * @var float */ protected $_saturation; /** * 亮度 0-1 * @var float */ protected $_lightness; /** * 构造HSL色彩空间描述 * @param float $hue * @param float $saturation * @param float $lightness */ public function __construct($hue=0, $saturation=0, $lightness=0) { $this->_hue = $hue; $this->_saturation = $saturation; $this->_lightness = $lightness; } /** * 获取色相 * @return float */ public function getHue() { return $this->_hue; } /** * 获取饱和度 * @return float */ public function getSaturation() { return $this->_saturation; } /** * 获取亮度 * @return float */ public function getLightness() { return $this->_lightness; } /** * 获取RGB形式色彩空间描述 * @return RGB */ public function toRGB() { $h = $this->getHue(); $s = $this->getSaturation(); $l = $this->getLightness(); if ($s == 0) { require_once 'RGB.php'; return new RGB($l, $l, $l); } $q = $l < 0.5 ? $l * (1 + $s) : $l + $s - ($l * $s); $p = 2 * $l - $q; $hk = $h / 360; $tR = $hk + (1 / 3); $tG = $hk; $tB = $hk - (1 / 3); $tR = $this->getTC($tR); $tG = $this->getTC($tG); $tB = $this->getTC($tB); $tR = $this->getColorC($tR, $p, $q); $tG = $this->getColorC($tG, $p, $q); $tB = $this->getColorC($tB, $p, $q); require_once 'RGB.php'; return new RGB($tR, $tG, $tB); } private function getColorC($tc, $p, $q) { if ($tc < (1 / 6)) { return $p + (($q - $p) * 6 * $tc ); } else if ((1 / 6) <= $tc && $tc < 0.5) { return $q; } else if (0.5 <= $tc && $tc < (2 / 3)) { return $p + (($q - $p) * 6 * (2 / 3 - $tc) ); } else { return $p; } } private function getTC($c) { if ($c < 0) $c++; if ($c > 1) $c--; return $c; } /** * 获取 array形式HSL色彩描述 * @return array */ public function toArray() { return array( 'hue' => $this->getHue(), 'saturation' => $this->getSaturation(), 'lightness' => $this->getLightness() ); } }<?php /** * HSV色彩空间描述 * HSV.php * @author shizhuolin */ class HSV { /** * 色相 0-260 * @var float */ protected $_hue; /** * 饱和度 0-1 * @var float */ protected $_saturation; /** * 色调 0-1 * @var float */ protected $_value; /** * 构造 * @param float $hue 色相 * @param float $saturation 饱和度 * @param float $value 色调 */ public function __construct($hue=0, $saturation=0, $value=0) { $this->_hue = $hue; $this->_saturation = $saturation; $this->_value = $value; } /** * 获取色相 0-360 * @return float */ public function getHue() { return $this->_hue; } /** * 获取饱和度 0-1 * @return float */ public function getSaturation() { return $this->_saturation; } /** * 获取色调 0-1 * @return float */ public function getValue() { return $this->_value; } /** * 返回该色彩在RGB色彩空间的描述 * @return RGB */ public function toRGB() { $hue = $this->getHue(); $saturation = $this->getSaturation(); $value = $this->getValue(); $hi = floor($hue / 60) % 6; $f = $hue / 60 - $hi; $p = $value * (1 - $saturation); $q = $value * (1 - $f * $saturation); $t = $value * (1 - (1 - $f) * $saturation); switch ($hi) { case 0: $red = $value; $green = $t; $blue = $p; break; case 1: $red = $q; $green = $value; $blue = $p; break; case 2: $red = $p; $green = $value; $blue = $t; break; case 3: $red = $p; $green = $q; $blue = $value; break; case 4: $red = $t; $green = $p; $blue = $value; break; case 5: $red = $value; $green = $p; $blue = $q; break; default: throw new ErrorException('HSV Conversion RGB failure!'); break; }; require_once 'RGB.php'; return new RGB($red, $green, $blue); } /** * 返回数组形式表达 * @return array */ public function toArray() { return array( 'hue' => $this->getHue(), 'saturation' => $this->getSaturation(), 'value' => $this->getValue() ); } }<?php /** * RGB色彩空间描述 * RGB.php * @author shizhuolin */ class RGB { /** * 红色 0-1 * @var float */ protected $_red; /** * 绿色 0-1 * @var float */ protected $_green; /** * 蓝色 0-1 * @var float */ protected $_blue; /** * 以初始值构造 * @param float $red 红色0-1 * @param float $green 绿色0-1 * @param float $blue 蓝色0-1 */ public function __construct($red = 0, $green = 0, $blue = 0) { $this->_red = $red; $this->_green = $green; $this->_blue = $blue; } /** * 获取红色分量 * @return float */ public function getRed() { return $this->_red; } /** * 获取绿色分量 * @return float */ public function getGreen() { return $this->_green; } /** * 获取蓝色分量 * @return float */ public function getBlue() { return $this->_blue; } /** * 返回该色彩的HSL空间描述 * @return HSL */ public function toHSL() { $r = $this->getRed(); $g = $this->getGreen(); $b = $this->getBlue(); $rgb = array($r, $g, $b); $max = max($rgb); $min = min($rgb); $diff = $max - $min; if ($max == $min) { $h = 0; } else if ($max == $r && $g >= $b) { $h = 60 * (($g - $b) / $diff); } else if ($max == $r && $g < $b) { $h = 60 * (($g - $b) / $diff) + 360; } else if ($max == $g) { $h = 60 * (($b - $r) / $diff) + 120; } else if ($max == $b) { $h = 60 * (($r - $g) / $diff) + 240; } else { throw new ErrorException("RGB conversion HSL failure!"); } $l = ($max + $min) / 2; if ($l == 0 || $max == $min) { $s = 0; } else if (0 < $l && $l <= 0.5) { $s = $diff / (2 * $l); } else if ($l > 0.5) { $s = $diff / (2 - 2 * $l); } else { throw new ErrorException("RGB conversion HSL failure!"); } require_once 'HSL.php'; return new HSL($h, $s, $l); } /** * 返回此色彩的HSV空间描述 * @return HSV */ public function toHSV() { $red = $this->getRed(); $green = $this->getGreen(); $blue = $this->getBlue(); $rgb = array($red, $green, $blue); $max = max($rgb); $min = min($rgb); $diff = $max - $min; /* 计算色相 */ if ($max == $min) { $hue = 0; } else if ($max == $red && $green >= $blue) { $hue = 60 * (($green - $blue) / $diff); } else if ($max == $red && $green < $blue) { $hue = 60 * (($green - $blue) / $diff) + 360; } else if ($max == $green) { $hue = 60 * (($blue - $red) / $diff) + 120; } else if ($max == $blue) { $hue = 60 * (($red - $green) / $diff) + 240; } else { throw new ErrorException("compute hue failure!"); } /* 计算饱和度 */ if ($max == 0) { $saturation = 0; } else { $saturation = 1 - $min / $max; } /* 计算色调 */ $value = $max; require_once 'HSV.php'; return new HSV($hue, $saturation, $value); } /** * 返回该色彩的数组表现形式 */ public function toArray() { return array( 'red' => $this->getRed(), 'green' => $this->getGreen(), 'blue' => $this->getBlue() ); } }效果测试(需要phpunit支持)<?php require_once dirname(__FILE__) . '/../../color/RGB.php'; require_once dirname(__FILE__) . '/../../color/HSL.php'; require_once dirname(__FILE__) . '/../../color/HSV.php'; /** * Test class for HSL. * HSLTest.php * Generated by PHPUnit on 2011-11-29 at 16:56:17. */ class HSLTest extends PHPUnit_Framework_TestCase { /** * @var HSL */ protected $object; /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. */ protected function setUp() { $this->object = new HSL(120, 1, 0.75); } /** * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ protected function tearDown() { } /** * @todo Implement testGetHue(). */ public function testGetHue() { $this->assertEquals(120, $this->object->getHue()); } /** * @todo Implement testGetSaturation(). */ public function testGetSaturation() { $this->assertEquals(1, $this->object->getSaturation()); } /** * @todo Implement testGetLightness(). */ public function testGetLightness() { $this->assertEquals(0.75, $this->object->getLightness()); } /** * @todo Implement testToRGB(). */ public function testToRGB() { $this->assertEquals(new RGB(0.5, 1, 0.5), $this->object->toRGB()); } /** * @todo Implement testToArray(). */ public function testToArray() { $this->assertEquals(array( 'hue' => 120, 'saturation' => 1, 'lightness' => 0.75 ), $this->object->toArray()); } }<?php require_once dirname(__FILE__) . '/../../color/RGB.php'; require_once dirname(__FILE__) . '/../../color/HSL.php'; require_once dirname(__FILE__) . '/../../color/HSV.php'; /** * Test class for HSV. * HSVTest.php * Generated by PHPUnit on 2011-11-29 at 16:49:00. */ class HSVTest extends PHPUnit_Framework_TestCase { /** * @var HSV */ protected $object; /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. */ protected function setUp() { $this->object = new HSV(120, 0.5, 1); } /** * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ protected function tearDown() { } /** * @todo Implement testGetHue(). */ public function testGetHue() { $this->assertEquals(120, $this->object->getHue()); } /** * @todo Implement testGetSaturation(). */ public function testGetSaturation() { $this->assertEquals(0.5, $this->object->getSaturation()); } /** * @todo Implement testGetValue(). */ public function testGetValue() { $this->assertEquals(1, $this->object->getValue()); } /** * @todo Implement testToRGB(). */ public function testToRGB() { $this->assertEquals(new RGB(0.5, 1, 0.5), $this->object->toRGB()); } /** * @todo Implement testToArray(). */ public function testToArray() { $this->assertEquals(array( 'hue' => 120, 'saturation' => 0.5, 'value' => 1 ), $this->object->toArray()); } }<?php require_once dirname(__FILE__) . '/../../color/RGB.php'; require_once dirname(__FILE__) . '/../../color/HSL.php'; require_once dirname(__FILE__) . '/../../color/HSV.php'; /** * Test class for RGB. * RGBTest.php * Generated by PHPUnit on 2011-11-29 at 16:38:54. */ class RGBTest extends PHPUnit_Framework_TestCase { /** * @var RGB */ protected $object; /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. */ protected function setUp() { $this->object = new RGB(0.5, 1, 0.5); } /** * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ protected function tearDown() { } /** * @todo Implement testGetRed(). */ public function testGetRed() { $this->assertEquals(0.5, $this->object->getRed()); } /** * @todo Implement testGetGreen(). */ public function testGetGreen() { $this->assertEquals(1, $this->object->getGreen()); } /** * @todo Implement testGetBlue(). */ public function testGetBlue() { $this->assertEquals(0.5, $this->object->getBlue()); } /** * @todo Implement testToHSL(). */ public function testToHSL() { $this->assertEquals(new HSL(120, 1, 0.75), $this->object->toHSL()); } /** * @todo Implement testToHSV(). */ public function testToHSV() { $this->assertEquals(new HSV(120, 0.5, 1), $this->object->toHSV()); } /** * @todo Implement testToArray(). */ public function testToArray() { $this->assertEquals(array( 'red' => 0.5, 'green' => 1, 'blue' => 0.5 ), $this->object->toArray()); } }
很久以前写的代码了，现在才想起贴上来. 稍微更改并结合人脸识别可以实现自动模糊人物的脸部.<?php require_once 'RGB.php'; //这里请看另一个 "PHP实现色彩空间转换" /** * 图像信息处理 * @author shizhuolin */ class Image { /** * 位图资源 * @var resource */ protected $_image; /** * 使用指定文件构造图像操作实例 * @param string $filename */ public function __construct($filename=null) { if ($filename) $this->open($filename); } /** * 读取指定文件明的图像 * @param string $filename * @return Image */ public function open($filename) { $handle = fopen($filename, "rb"); $contents = stream_get_contents($handle); fclose($handle); $image = imagecreatefromstring($contents); $width = imagesx($image); $height = imagesy($image); $truecolorimage = imagecreatetruecolor($width, $height); imagefilledrectangle($truecolorimage, 0, 0, $width, $height, 0xFFFFFF); imagecopyresampled($truecolorimage, $image, 0, 0, 0, 0, $width, $height, $width, $height); imagedestroy($image); $this->setImage($truecolorimage); return $this; } /** * 读取指定资源的图像 * @param resource $image * @return Image */ public function load($image) { $width = imagesx($image); $height = imagesy($image); $truecolorimage = imagecreatetruecolor($width, $height); imagefilledrectangle($truecolorimage, 0, 0, $width, $height, 0xFFFFFF); imagecopyresampled($truecolorimage, $image, 0, 0, 0, 0, $width, $height, $width, $height); if ($this->getImage()) { imagedestroy($this->getImage()); } $this->setImage($truecolorimage); return $this; } /** * 设置内部图像资源描述符 * @param resource $image */ protected function setImage($image) { $this->_image = $image; } /** * 获取内部操作图像资源 * @return resource */ public function getImage() { return $this->_image; } /** * 获取图像宽度 * @return int */ public function getWidth() { return imagesx($this->getImage()); } /** * 获取图像高度 * @return int */ public function getHeight() { return imagesy($this->getImage()); } /** * 获取指定坐标像素颜色 * @param type $x * @param type $y * @return type */ public function getColorAt($x, $y) { $rgb = imageColorAt($this->getImage(), $x, $y); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; return array('red' => $r, 'green' => $g, 'blue' => $b); } /** * 高斯模糊(单纬度二次模糊) * @param float $radius 半径 */ public function gaussianBlur($radius=1.0) { $width = $this->getWidth(); $height = $this->getHeight(); $sigma = $radius / 3; $sigma2 = 2 * pow($sigma, 2); $matrix = array(); $newimage = imagecreatetruecolor($width, $height); /* 生成高斯矩阵(单纬度矩阵) */ for ($x = -$radius; $x <= $radius; $x++) { $x2 = pow($x, 2) + pow($x, 2); $matrix[] = exp(-$x2 / $sigma2) / ($sigma2 * M_PI); } /* 构建模糊图像 */ for ($y = 0; $y < $height; $y++) { for ($x = 0; $x < $width; $x++) { $bright = $red = $green = $blue = 0; /* 垂直模糊 */ for ($yy = -$radius; $yy <= $radius; $yy++) { $yyy = $y + $yy; if ($yyy >= 0 && $yyy < $height) { $weight = $matrix[$yy + $radius]; $bright += $weight; $color = $this->getColorAt($x, $yyy); $red += ($color['red'] * $weight); $green += ($color['green'] * $weight); $blue += ($color['blue'] * $weight); } } /* 水平模糊 */ for ($xx = -$radius; $xx <= $radius; $xx++) { $xxx = $x + $xx; if ($xxx >= 0 && $xxx < $width && $xx != 0) { $weight = $matrix[$xx + $radius]; $bright+=$weight; $color = $this->getColorAt($xxx, $y); $red += ($color['red'] * $weight); $green += ($color['green'] * $weight); $blue += ($color['blue'] * $weight); } } $z = 1 / $bright; imagesetpixel($newimage, $x, $y, ($red * $z << 16) | ($green * $z << 8) | $blue * $z); } } imagedestroy($this->getImage()); $this->setImage($newimage); } /** * 析构 销毁图像占用内存 */ public function __destruct() { if ($this->getImage()) { imagedestroy($this->getImage()); } } }调用代码<?php require_once 'Image.php'; $image = new Image("http://www.u-cola.com/images/200809/1220676617582974754.jpg"); $image->gaussianBlur(10); header("Content-type: image/png"); imagepng($image->getImage());