Rusty Divine

Live, Love, Learn, Teach

CakePHP with IIS, SQL Server, SRS, LDAP, and TFS

Our development team recently completed a project with a requirement that PHP be used and hosted on IIS 7. We are a C#.Net team and have some experience with PHP and we had to work hard to get all of our tools to work together, so hopefully this will help you, too. The project turned out fantastically, thanks in part to a really good product owner on the client side. Thanks goes to Adam Costenbader for some of the notes for these steps.

  1. Install IIS 7 (add/remove programs > configure windows features)
  2. From the IIS Manager, enable FastCGI support.
  3. Download and install PHP 5.3 or later
  4. Install the URL Rewrite extension
  5. Install the PHP Manager
    1. Enable php_pdo_sqlsrv_54_nts.dll for sql server. If missing, you can download it from Microsoft, then install and enable it by adding an extension in the PHP manager.
    2. Enable php_LDAP.dll for LDAP access
  6. In IIS Manager, check that there is a handler mapping for PHP to the FastCGIModule. With the server node selected, enter the Handler Mappings and if *.php is not setup for PHP53_via_FastCGI, then add the mapping. The path is *.php, module is FastCgiModule, executable is C:\[php path]\php-cgi.exe, name is PHP53_via_FastCGI. Select the Request Restrictions and then check Invoke and select File or Folder.
  7. Create a website in IIS Manager and set index.php as the default document for the website.
    1. The security for the root folder needs to permit USERS at the basic level, and IIS_IUSRS needs full permissions to the app\tmp folder.
  8. Install the TFS Power Tools for VS2012 to get a Windows Explorer add-in for TFS
  9. For reporting, install the SQL Server Data Tools
  10. For PHP Intelli-sense, we purchased PHP Tools for Visual Studio
  11. We were never able to get debugging to work with CakePHP, although we tried PHP Tools and Xdebug.
  12. To make the solution work with TFS, we had to make sure the app\tmp directory wasn’t read-only, but the folder structure had to be there. We had trouble with the project trying to add the files back in each time, so we ended up just excluding the app\tmp from source control and then creating the folder structure manually on new checkouts.
  13. To get some SQL Server Reporting Services to serve reports as HTML into a CakePHP view:
    1. We created a local user on the report server and permissions (db_datareader) to the database.
    2. If SRS is on a different server, you need to allow basic authentication by editing the rsreportserver.config found in the Program Files:
      1. <Authentication>
           <AuthenticationTypes>
            <RSWindowsNTLM/>
            <RSWindowsBasic/>
           </AuthenticationTypes>
           <EnableAuthPersistence>true</EnableAuthPersistence>
        </Authentication>
    3. On the report server, run IE as an administrator and browse to the server/reports folder. Navigate to your reports folder and to the Properties tab > Security page. Add the local user here and make sure Browser is checked.
    4. For more examples, see this MSDN post.

We were able to get this CakePHP website running on an Azure website, too, which was a fun challenge (we did not activate the LDAP or SRS portions for this). This was our web.config for Azure:

<!-- Configuration for Windows Azure -->
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <clear/>
        <rule name="Imported Rule 0" stopProcessing="true">
          <match url="^(img|css|files|js)(.*)$"/>
          <action type="Rewrite" url="app/webroot/{R:1}{R:2}" appendQueryString="false"/>
        </rule>
        <rule name="Imported Rule 1" stopProcessing="true">
          <match url="^(.*)$" ignoreCase="false"/>
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
          </conditions>
          <action type="Rewrite" url="index.php?url={R:1}" appendQueryString="true"/>
        </rule>
        <rule name="Imported Rule 2" stopProcessing="true">
          <match url="^$" ignoreCase="false"/>
          <action type="Rewrite" url="/"/>
        </rule>
        <rule name="Imported Rule 3" stopProcessing="true">
          <match url="(.*)" ignoreCase="false"/>
          <action type="Rewrite" url="/{R:1}"/>
        </rule>
        <rule name="Imported Rule 4" stopProcessing="true">
          <match url="^(.*)$" ignoreCase="false"/>
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
          </conditions>
          <action type="Rewrite" url="index.php/{R:1}" appendQueryString="true"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>
<!-- Configuration for Windows Server 2008 --> 
<?xml version="1.0"?>
<configuration> 
  <system.webServer>
    <rewrite>
      <rules>
        <clear/>
        <rule name="Imported Rule 0" stopProcessing="true">
          <match url="^(img|css|files|js)(.*)$"/>
          <action type="Rewrite" url="app/webroot/{R:1}{R:2}" appendQueryString="false"/>
        </rule>
        <rule name="Imported Rule 1" stopProcessing="true">
          <match url="^(.*)$" ignoreCase="false"/>
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
          </conditions>
          <action type="Rewrite" url="index.php?url={R:1}" appendQueryString="true"/>
        </rule>
        <rule name="Imported Rule 2" stopProcessing="true">
          <match url="^$" ignoreCase="false"/>
          <action type="Rewrite" url="app/webroot/"/>
        </rule>
        <rule name="Imported Rule 3" stopProcessing="true">
          <match url="(.*)" ignoreCase="false"/>
          <action type="Rewrite" url="app/webroot/{R:1}"/>
        </rule>
        <rule name="Imported Rule 4" stopProcessing="true">
          <match url="^(.*)$" ignoreCase="false"/>
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
          </conditions>
          <action type="Rewrite" url="index.php?url={R:1}" appendQueryString="true"/>
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
  <system.web>
    <compilation targetFramework="4.5"/>
    <pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/>
  </system.web>

   <system.net >
    <mailSettings>
        <smtp deliveryMethod="SpecifiedPickupDirectory" from="from@me.com">
        <specifiedPickupDirectory pickupDirectoryLocation="c:\Temp\MailDrop" />
      </smtp>-
    </mailSettings>
  </system.net>
  
</configuration>