Documentation Snippets

Script inclusions in the context of web applications

Phalanger distinguishes between static inclusion and dynamic inclusion. If the compiler compiles an inclusion construct (i.e. include, include_once, require, or require_once) it decides about whether it is static or dynamic (the decision is based on rules described further). In the case of static inclusion, the content of the included file is used during compilation of the including file. In the dynamic case, the content of the included file is unknown to the compiler and won't be included until run-time. The inclusion is also said to be deferred to run-time.

Apparently, deferred inclusions lead to slower execution of the script. Let's assume, for example, that the including script declares a class extending another one which is declared in the dynamically included script. As the compiler does know nothing about the super-class it cannot declare the sub-class. In such a case, the compiler defers the sub-class declaration evaluation into runtime. The declaration statement class SubClass extends SuperClass { ... } is replaced with eval("class SubClass extends SuperClass { ... }"); statement. This eval invokes a compilation at run-time during which the class is declared. That's one of the reason why it is more efficient to use static inclusions.

Dynamic inclusions

Let's discuss dynamic inclusions mechanism now. If an inclusion is dynamic it is treated exactly in the same way as it is treated by the original PHP interpreter. The algorithm works as follows.

First of all, let's refine some keywords:

In PHP, you can use either physical paths or URIs to specify a target file of an inclusion. URI has to start with schema specification (http://, ftp://, file://, etc.) which sets it apart from physical path. In the current version, Phalanger doesn't support URIs in inclusions. URIs are only supported in file-system functions like fopen().

We also need to consider script dependent configuration option called "include_path". The option can be changed anytime during the script execution via e.g. ini_set() function. The include_path option contains a list of paths to directories relative to the current working directory separated by semicolon character. If the path is an empty string it is ignored (e.g. "path1;;path2" contains three items: the first and the last ones are considered, the second one is ignored). The path can be made up by a single dot character. The path "." refers to the current working directory itself.

Following steps are taken when the Phalanger is searching for a dynamically included file:

  1. The value passed to inclusion construct is converted to a string, say S.
  2. If S is an absolute path the target file is checked and the search is over. If the path is incomplete it is prefixed with the root of the current working directory. The search has succeeded iff the target file defined by the path exists.
  3. Items of include_path are combined with S one by one starting from the first one on the left. If the item is an empty string it is skipped. The combination means that the directory separator character (slash) is added if the path doesn't end with it. The result of combination is a path to a file that is relative to current working directory (as already mentioned) and checked for existence. If it does exist the search is over and has succeeded. Otherwise we continue with the next item of include_path or with the next step if there is no item left.
  4. The value of S is combined with the script directory and the resulting file path is checked for existence. If the file does exist the search has succeeded, otherwise it has failed.

If the search fails an error or warning is reported depending on the particular inclusion construct. If include or include_once constructs are processed the warning is reported. If require or require_once constructs are processed the error is reported.

Static inclusions

Using static inclusions, you can improve your application's execution performance. Static inclusions are disabled for web applications by default to make all existing PHP web applications working without additional effort. They come into consideration if you want to fine-tune your application. The following paragraphs describe how to do so.

Inclusions can be configured using ASP.NET hierarchical configuration stored in Machine.config and Web.config files in the application's root directory or above. Recall, only those Web.config files that are located on the physical path to the current request target are considered by ASP.NET (and so by Phalanger). Web.config's located along included scripts are not loaded unless they lay on the request virtual path.

First of all, you should enable static inclusions by setting the following configuration option in an appropriate configuration file.

<compiler>
  <set name="EnableStaticInclusions" value="true" />
</compiler>

Then the compiler will try to determine a value of each inclusion expression (the expression stated as a parameter of include, include_once, require, require_once construct). The value is treated as a path to the included file. The compiler checks whether the target file exists. The search algorithm is the same to dynamic inclusion one except for that include_path is not considered. That's because the include_path is not known at compile time. The value of configuration option StaticIncludePaths is used instead.

<compiler>
  <set name="StaticIncludePaths" value="." />
</compiler>

The option's value is a list of paths separated by semicolon (similarly to include_path) and relative to the application source root directory.

If the search succeeds the inclusion is considered static and dynamic otherwise. A warning is reported on failure so the user is notified that the inclusion has been deferred. This warning can be disabled by configuration option

<compiler>
  <set name="DisableWarnings" />
    <add value="DeferredToRuntime" />
  </set>
</compiler>

This mechanism will work fine for inclusions whose arguments can be avaluated at compile-time (string literals). However, many applications uses user-defined constants, file-system functions such as basename(), or variables like $_SERVER["DOCUMENT_ROOT"] in the expressions. To tackle this very usual problem, Phalanger enables to define regular expression pattern mappings that converts compile-time unevaluable expressions to string literals.

You can define a mapping by a regular expression which the compiler applies on a source code of each inclusion expression. If the regular pattern matches the compiler replaces the expression with a result defined by the mapping. The result is then proceeded as described above, i.e. the search for the target file is performed. The mapping takes place before the compiler tries to evaluate the inclusion expression. So if the pattern doesn't match the expression the compiler tries to evaluate the expression anyway.

The mappings are defined by InclusionMappings configuration option as follows:

<compiler>
  <!-- In the pattern, \s means whitespace, &quot; is a double quote, parenthesis marks the group indexed from 1. 
       In the value, $1 refers to the first group. -->
  <set name="InclusionMappings">
    <add pattern="A_PATH[\s.]*['&quot;]([^'&quot;$]+)['&quot;]" value="LibraryA/$1" />
    <add pattern="B_PATH[\s.]*['&quot;]([^'&quot;$]+)['&quot;]" value="LibraryB/$1" />
  </set>
</compiler>

Patterns use .NET Framework regular expressions with options RegexOptions.IgnoreCase. The expression source is trimmed before the match.

Examples:

include(A_PATH . "file1.php");  // result is 'LibraryA/file1.php' relatively to the web application root
include(   A_PATH."file2.php"); // result is 'LibraryA/file2.php' ditto
include(a_path."file3.php");    // result is 'LibraryA/file3.php' ditto
include(B_PATH."file4.php");    // result is 'LibraryB/file4.php' ditto
$i = 1;
include(A_PATH."file$i.php");   // doesn't match ($i is not evaluated because mapping is
                                // applied on the source code not its value!); inclusion is dynamic
include('file5.php');           // doesn't match; the expression is evaluable => inclusion is static if there is file 'file5.php' in the app. root
include($x.'file3.php');        // doesn't match; inclusion is dynamic
			

Note that static inclusions cannot generate a cycle. In such a case the inclusion which completes the cycle will be deferred to run-time. This drawback will be fixed in some future version.