Introduction
A while back I setup a LEMP server (Linux, NginX, MySQL, and PHP) using Virtual Box. This is fine for testing out how a live server might behave, or even for using as a virtual server if you don't have the dedicated hardware to host a server. However, for a server in active development it's a huge inconvenience as everytime I want to test out a change, no matter how small, I was forced to push the changes to the virtual server. To get around this, I'm going to setup a development server which can run in the Windows environment I normally use. I'm also going to setup the XDebug plugin. This will allow me to debug php code without using the "traditional" console/echo solution. I'm unsure if XDebug would work with my virtual server setup, but I do know it works with this development setup.
Required Software
- MySQL 5.6.10 server and MySQL Workbench 5.2.47 for Windows (I used the MySQL installer)
- NginX 1.3.15 development version for Windows x86
- PHP 5.4.13 Windows binaries and source (thread safe version)
- XDebug 2.2.2 for PHP 5.4 VC9 TS (32 bit)
- Eclipse IDE with PDT plugin. I am using the Juno release (4.2).
- Visual Studio 2010 with C# support or newer (I don't know if the Express editions will work or not)
In order to have the server automatically, I wrote a small C# wrapper application which runs as a service. The service is able to startup and shutdown the PHP-FPM and NginX applications as needed. This isn't a necessary step, but really is convenient.
Install MySQL Software
This step is fairly self-explainatory, just run the MySql installer and install the MySQL web server and MySQL Workbench. You can install other tools as needed, or come back later to modify your installation. I'm unsure if the MySQL server needs to be the 32-bit version or if you can use the 64-bit version. NginX and PHP are currently 32-bit only for Windows. I modified the specific configuration options to minimize memory usage. I don't really need to use the MySQL database all the time, and when I do it will be for low-demand development work. I also included the same bind-address directive to limit access (in addition to user account access limits). This time, I can limit access directly to the localhost rather than have to delegate to a different machine.
Configuring NginX and PHP
The first step is to unzip the downloads. Configuring the two is similar to configuring in Linux. Here is the nginx.conf I'm using:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type text/html; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; gzip on; gzip_static on; gzip_vary on; gzip_disable "msie6"; gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_comp_level 6; gzip_http_version 1.1; server { listen 80; listen [::]:80 default ipv6only=on; ## listen for ipv6 root "C:/Users/Andrew/Documents/Eclipse/main/"; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; index index.html index.html index.php; location / { deny all; } location /allowed_page { allow all; try_files $uri $uri/ /index.html; } # block access to svn directories location ~/\.svn { deny all; } # block access to git directories location ~/\.git { deny all; } # block eclipse project and workspace stuff location ~/\.settings { deny all; } location ~/\.buildpath { deny all; } location ~/\.project { deny all; } location ~/\.metadata { deny all; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # #error_page 500 502 503 504 /50x.html; #location = /50x.html { # root html; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } }
There are a few notable modifications from the default config file provided by NginX, notably the PHP handling has the fastcgi_param set correctly (the default setting is meaningless). I also by default denied access to my Eclipse repository, and explicitly allowed certain directories I wanted to allow the server access to. Alternatively, I could only allow 127.0.0.1 access instead of allow all.
Also note that according to the NginX release notes, NginX for Windows currently does not properly handle multiple worker processes (only 1 actually does any work). Since there will be very few people using this server, this is fine.
To configure PHP-CGI with XDebug support, go to the PHP directory and copy/rename php.ini-development to php.ini. These are the default configs which help with developing PHP code, which is close to what I want.
MySQL modifications
There are 2 modifications that need to be changed to enable the MySQL PDO extension. Do a search for the following lines and uncomment them.
; uncomment this line to ensure PHP can find the extensions extension_dir = "ext" ; uncomment this line to enable the MySQL PDO extension extension=php_pdo_mysql.dll
XDebug modifications
The first step is to move the XDebug dll to the php ext folder (actually, you can probably move it anywhere but this is the logical place to put it). Next, edit the php.ini file and add the following code:
; I don't know if this has to be in the [PHP] section or not, I put it right under the other extensions zend_extension="C:/Programs/php/php-5.4.13-Win32-VC9-x86/ext/php_xdebug-2.2.2-5.4-vc9.dll" ; note: make sure you add this so it's not in the middle of another section. ; Adding to the end is a good way to do so [xdebug] xdebug.remote_enable=on xdebug.remote_host=localhost xdebug.remote_port=9001 xdebug.remote_handler="dbgp"
A key point to note is that the default PHP-FCGI port is 9000, which is what XDebug's install instructions say to use. The easy fix to this is simply move XDebug's port to 9001.
Starting and Stopping the Server
Starting the server is pretty easy. Start NginX by running the main executable, nginx.exe. Start the PHP-FCGI server is simple, too. Run php-cgi.exe with the target IP address and port (as configured in nginx.conf).
php-cgi.exe -b 127.0.0.1:9000 -c ./php.ini
To stop the NginX server, use the nginx -s quit for a graceful shutdown. See the NginX documentation for more information.
Windows Service Wrapper
To keep the server running in the background, I'm going to use a Windows Service wrapper application. I'm basically following the MSDN tutorial. I couldn't find any easy way to create a Windows service using VC++ with Visual Studio 2010/2012 (the two versions I have available) so I settled with C#. I don't need special logging for just the service wrapper so I ignored the steps about adding an EventLog component. Here's the code for the main service class:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace NginXService { public partial class NginXService : ServiceBase { private string nginx_path; private string php_path; private string php_cfg; public NginXService() { InitializeComponent(); nginx_path = ConfigurationManager.AppSettings.Get("nginx_path"); php_path = ConfigurationManager.AppSettings.Get("php_path"); php_cfg = ConfigurationManager.AppSettings.Get("php_cfg"); } private bool nginxRunning() { Process[] processlist = Process.GetProcesses(); foreach (Process p in processlist) { if (p.ProcessName.ToLower().Equals("nginx")) { return true; } } return false; } private Process getPHPProcess() { Process[] processlist = Process.GetProcesses(); foreach (Process p in processlist) { if (p.ProcessName.ToLower().Equals("php-cgi")) { return p; } } return null; } protected override void OnStart(string[] args) { start(); } protected override void OnShutdown() { quit(); } protected override void OnStop() { quit(); } public void quit() { if (nginxRunning()) { ProcessStartInfo info = new ProcessStartInfo(nginx_path + "nginx.exe", "-s quit"); info.WorkingDirectory = nginx_path; Process.Start(info); } Process php = getPHPProcess(); if (php != null) { php.Kill(); } } public void start() { if (!nginxRunning()) { ProcessStartInfo info = new ProcessStartInfo(nginx_path + "nginx.exe"); info.WorkingDirectory = nginx_path; Process.Start(info); } Process php = getPHPProcess(); if (php == null) { ProcessStartInfo info = new ProcessStartInfo(php_path + "php-cgi.exe", "-b 127.0.0.1:9000 -c \"" + php_cfg + "\""); info.WorkingDirectory = php_path; Process.Start(info); } } } }
To help generalize the wrapper application I added 3 appSettings properties. This allows the wrapper to handle various installation paths without having to recompile the application.
<?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <appSettings> <add key="nginx_path" value="C:\\Programs\\nginx\\nginx-1.3.15\\" /> <add key="php_path" value="C:\\Programs\\php\\php-5.4.13-Win32-VC9-x86\\"/> <add key="php_cfg" value="C:\\Programs\\php\\php-5.4.13-Win32-VC9-x86\\php.ini"/> <add key="ClientSettingsProvider.ServiceUri" value="" /> </appSettings> <system.web> <membership defaultProvider="ClientAuthenticationMembershipProvider"> <providers> <add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" /> </providers> </membership> <roleManager defaultProvider="ClientRoleProvider" enabled="true"> <providers> <add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" /> </providers> </roleManager> </system.web> </configuration>
This service handles starting and stopping the PHP-FCGI and NginX processes. Note that MySQL is not handled as the MySQL installer created a separate service which handles this. The code could probably use with some more error handling and checking, but it is usable enough for my needs.
Eclipse PDT Setup
Now that we have a server setup, we need to setup the Eclipse PDT plugin. Open up the Eclipse workspace properties (Windows > Preferences), go to the PHP > PHP Servers page. Add a new web server with the appropriate parameters.
Next go to the PHP > Debug page. Change the PHP Debugger to XDebug and make sure the correct server is selected. Click Configure... and change the XDebug port to match the php.ini port set for XDebug.
Conclusion
And there we go! I now have a local development server running on my Windows system. In addition to this, I now have the ability to use Eclipse's PDT debugging capabilities, which is closer to the traditional debugger interface I'm used to and will make debugging my PHP code much easier.
Well, your blog is really good which have useful information regarding PHP development services. Beginners should read your posts because it has lots of good tips which will useful for their career.
ReplyDeletehire a ASP.NET developer
This comment has been removed by the author.
ReplyDeleteThere is also WT-NMP - Windows Nginx MySql Php stack:
ReplyDeletehttps://sourceforge.net/p/wtnmp/wiki/Home/
- TRULLY PORTABLE, you can zip it and take it with you, or you can move it to a different location on your filesystem.
- Easy to upgrade: Download / Unzip / Overwrite!
Interesting project, I'll take a look into what it can do.
ReplyDelete