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>

Book review analogy

Sometimes I encounter customers who are surprised at the effort that it takes to create custom software. Some of these customers are very good at understanding systems and big pictures and are uncomfortable with details. Some people can do both, but I find that to be welcome rarity.

A recent customer really struck me as someone who I could have helped with the following analogy, had I had the foresight.

The customer explained the project in a twenty minute conversation where I asked some questions that he agreed were things he hadn’t thought about. When I explained our requirements gathering process, which we charge a flat fee for, he was fine, but within a week he responded via email to express his frustration that we would charge him to develop a design. His email was worded rather strongly, but that was the gist of it.

I don’t think there will be a middle ground for us, but I wonder what I can do to help customers new to professional custom software development to understand it better. That’s when this analogy occurred to me:

This initial meeting has helped me understand your project at the same depth as reading a decent book review on Amazon. I know I’m interested in it, I know I’d like to read it, and I know the general plot, but I don’t know how many pages it has or a single line of its dialog. I’m not sure what the cover art looks like or who all the characters are. I’d like to setup another meeting with you where we will try to brainstorm more of these details, narrow our cast and focus on what really is important for the first book in what may become a series. I’ll take these notes and spend a significant amount of time developing them further, making sure no one else has already written this book, and then come back to you with all the notes, designs, research, and a fixed price quote to write this book for you. If you decide its not worth it at that point, or that someone else will write it better or cheaper than I can, you’re free to take these designs and have someone else write your book.