├── .gitignore
├── 01_installation
├── 01_requirements.md
├── 02_troubleshooting.md
├── 03_web_servers.md
└── README.md
├── 02_quickstart.md
├── 03_architecture
├── 01_mvc.md
├── 02_aop.md
├── 03_objects.md
├── 04_file_structure.md
├── 05_response-lifecycle.md
├── 06_dependencies.md
└── README.md
├── 04_configuration
├── 01_bootstrapping.md
├── 02_environment.md
├── 03_third_party_libraries.md
└── README.md
├── 05_models
├── 01_connections.md
├── 02_data_mutation.md
├── 03_saving.md
├── 04_validation.md
├── 05_querying.md
├── 06_adding_functions.md
├── 07_relationships.md
├── 08_meta.md
├── 09_mongodb.md
├── 10_using_data_sources.md
├── 11_creating_data_sources.md
└── README.md
├── 06_controllers
├── 01_actions.md
├── 02_parameters.md
├── 03_routing.md
├── 04_flow_control.md
├── 05_type_rendering_detection.md
└── README.md
├── 07_views
├── 01_auto_escaping.md
├── 02_layouts.md
├── 03_elements.md
├── 04_helpers.md
├── 05_static_content.md
└── README.md
├── 08_quality_code
├── 01_security.md
├── 02_testing.md
├── 03_analysis.md
└── README.md
├── 09_common_tasks
├── 01_filters.md
├── 02_simple_authentication.md
├── 03_simple_auth_user.md
├── 04_logging.md
├── 05_caching.md
├── 06_error_handling.md
├── 07_globalization.md
├── 08_console_applications.md
├── 09_plugins.md
├── 10_etags.md
└── README.md
├── 11_appendices
├── 01_faqs.md
├── 02_using_in_other_applications.md
└── README.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── TODO.md
└── assets
└── img
├── default_app_welcome.png
└── lithium_advent_caching_flow.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/01_installation/01_requirements.md:
--------------------------------------------------------------------------------
1 | # Requirements
2 |
3 | ## Web Server
4 |
5 | You need a **web server** to run your app, preferably running on your local machine (you do
6 | all of your development locally, right, right??). As PHP, the framework itself runs just
7 | fine on most popular web servers. [Setup guides are available for Apache, IIS, Lighttpd, and
8 | NGINX](./web-servers).
9 |
10 | ## PHP
11 |
12 | Because the framework takes advantage of advanced **language** features, a recent PHP version
13 | is required. [Please review the compatibility table](http://li3.me/versions) to see if your
14 | PHP version is supported.
15 |
16 | The vanilla PHP configuration should be in general fine. However its always good to double
17 | check that certain configuration options are set correctly. Certain features are not supported
18 | as we consider those broken, very experimental or a hack.
19 |
20 | Please verify that:
21 |
22 | - Magic Quotes are disabled.
23 | - Register Globals are disabled.
24 | - Function overloading is disabled when using the `mbstring` extension.
25 | - PHP isn't compiled with curlwrappers.
26 | - Short Open Tags are disabled. Although this is not a strict requirement.
27 | - When using MongoDB you have the mongo extension >= 1.2.0 installed.
28 |
29 | While you're making PHP configuration changes, you might also consider having PHP display errors temporarily during development. Just change the relevant lines in your `php.ini`:
30 |
31 | ```ini
32 | ; Show me teh errors.
33 | display_errors = On
34 |
35 | ; Either choose to see all errors or all, but no deprecation warnings.
36 | error_reporting = E_ALL
37 | ; error_reporting = E_ALL & ~E_DEPRECATED
38 | ```
39 |
40 | ## Data Store
41 |
42 | Applications often feature some sort of **data store**. As such, you may want to track down one
43 | of the following as well:
44 |
45 | * MongoDB
46 | * MySQL or MariaDB
47 | * PostgreSQL
48 | * SQLite
49 | * CouchDB
50 |
51 | ## Version Control System
52 |
53 | While not absolutely essential, a working knowledge of the [Git **version control
54 | system**](http://git-scm.com/) is useful for most aspects of development in general, and li3
55 | specifically i.e. the workflow for contributing to projects is Git-based.
56 |
57 | ## Command-line Terminal
58 |
59 | Also not required for working with the framework, however it provides many useful tools
60 | for automating complex or repetitive tasks.
61 |
62 | ## Passion!
63 |
64 | ... for **innovation and collaboration**.
65 |
--------------------------------------------------------------------------------
/01_installation/02_troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting Installations
2 |
3 | This quick list is meant to cover common problems in installing li3.
4 |
5 | ## Internal Server Error
6 |
7 | Internal server errors are usually a result of bad .htaccess configurations. Make sure unmodified copies of the .htaccess files from the lithium repo are in these places:
8 |
9 | * `/.htaccess`
10 | * `/app/.htaccess`
11 | * `/app/webroot/.htaccess`
12 |
13 | You might also be running in a directory on your web server that is already under rewrite rules (often URLs that include your username such as http://username.example.com or http://example.com/~username/). This may cause 500 Internal server errors, or in some cases, cause a redirect loop.
14 |
15 | In this case you'll need to adjust your .htaccess files to include a RewriteBase directive:
16 |
17 | * `/.htaccess` => `RewriteBase /`
18 | * `/app/.htaccess` => `RewriteBase /app/`
19 | * `/app/webroot/.htaccess` => `RewriteBase /app/webroot/`
20 |
21 | Make sure to place the RewriteBase directive just after `RewriteEngine on`.
22 |
23 | ## Images/CSS Broken
24 |
25 | Usually this is a result of `.htaccess` files not being parsed. Make sure that the directives that cover your li3 installation include the following line:
26 |
27 | ```apache
28 | AllowOverride all
29 | ```
30 |
31 | ## Unexpected Character Input
32 |
33 | If you get an error that looks like this:
34 |
35 | ```text
36 | Warning: Unexpected character in input: '\' (ASCII=92) state=1 in /path/to/lithium/app/webroot/index.php on line 22
37 | ```
38 |
39 | This means you're not running PHP 5.3 or later. Please check your PHP version and update as appropriate.
40 |
41 | ## I'm getting a fatal error that looks like this...
42 |
43 | **Function name must be a string in .../lithium/util/collection/Filters.php on line ...**
44 |
45 | This is happening because you have [eAccelerator](http://eaccelerator.net/) installed.
46 | eAccelerator is an optimizer / opcode cache for PHP which does not fully support PHP 5.3.
47 | The solution is to disable it, and switch to a working, better-supported accelerator, like
48 | the builtin [Opcache](http://php.net/manual/en/book.opcache.php).
49 |
50 | If you didn't know you had eAccelerator installed, it's because you're running MAMP, which
51 | sometime comes pre-bundled with eAccelerator. In this case, the solution is to man up (or
52 | woman up) and get [Homebrew](https://github.com/mxcl/homebrew). You'll be glad you did.
53 |
54 |
55 |
--------------------------------------------------------------------------------
/01_installation/03_web_servers.md:
--------------------------------------------------------------------------------
1 | # Web Servers
2 |
3 | ## Using Apache httpd
4 |
5 | Before starting things up, make sure `mod_rewrite` is enabled, and the `AllowOverride` directive
6 | is set to `All` on the necessary directories involved. Be sure to restart the server before
7 | checking things.
8 |
9 | In order to provide applications with clean URLs, distributions ship with a set of `.htaccess` files for use with Apache, which will handle URL rewriting.
10 |
11 | By default, these files can be utilized by finding all references to `AllowOverride` in your `httpd.conf` configuration file and setting the values to `All`. On an OS X system, your setup may look something like this:
12 |
13 | ```apache
14 |
15 | Options Indexes FollowSymLinks MultiViews
16 | AllowOverride All
17 | Order allow,deny
18 | Allow from all
19 |
20 | ```
21 |
22 | Once you've made sure `AllowOverride` has been set correctly, you can toss a project in your `DocumentRoot`. Using the past example, placing the project in `/Library/WebServer/Documents/project` would allow you to access your application at `http://localhost/project`.
23 |
24 | **In production**, it is recommended that you set `AllowOverride` to `None` and instead, create a `` configuration pointed at your application's `webroot` directory, which contains the rewrite rules. For example, if your application is located in `/var/www/html/project`, your configuration would resemble the following:
25 |
26 | ```apache
27 |
28 | ServerName example.com
29 | DocumentRoot "/var/www/html/project/app/webroot"
30 | ErrorLog "logs/error_log"
31 |
32 |
33 | RewriteEngine On
34 | RewriteCond %{REQUEST_FILENAME} !-d
35 | RewriteCond %{REQUEST_FILENAME} !-f
36 | RewriteCond %{REQUEST_FILENAME} !favicon.ico$
37 | RewriteRule ^ index.php [QSA,L]
38 |
39 |
40 | ```
41 |
42 | In this case, your app is available at `http://example.com`.
43 |
44 | ## Using NGINX
45 |
46 | Nginx requires a simple rewrite configuration that enables it to serve dynamic URLs. With nginx, each domain has its own configuration file that encapsulates all such rewriting rules.
47 |
48 | The following example file is typically stored at `/etc/nginx/sites-available/example.org.conf`. All instances of `example.org` can be replaced with the domain name of your site or application.
49 |
50 | ```nginx
51 | server {
52 | listen 80;
53 | server_name example.org;
54 | root /path/to/project/app/webroot;
55 |
56 | index index.php;
57 | try_files $uri $uri/ /index.php?$args;
58 |
59 | location ~ \.php$ {
60 | # These are example paths. Check your FPM configuration as your setup may vary.
61 | include /usr/local/etc/nginx/fastcgi.conf;
62 |
63 | # Either use a FPM socket...
64 | fastcgi_pass unix:/usr/local/var/run/php-fpm.socket;
65 |
66 | # ... or an address to bind to.
67 | # fastcgi_pass 127.0.0.1:9000;
68 | }
69 | }
70 | ```
71 |
72 | ## Using Internet Information Services (IIS)
73 |
74 | IIS Is a Microsoft-supplied product which manages many internet services—one of them being HTTPD—that comes standard on most Windows machines.
75 |
76 | ### Obtaining IIS
77 |
78 | To install IIS under Windows you need to go to "Add/Remove Programs" in your control panel. After entering the "Add/Remove Programs" window, click "Turn Windows features on or off" and locate the following:
79 |
80 | - Internet Information Services
81 | - Internet Information Services Hostable Web Core
82 |
83 | For a quick setup, check both. If you want to remove specific features, you can expand "Intenernet Information Services" and remove unwanted features.
84 |
85 | Once this is done you can now locate IIS within your Start menu or find it through search by typing "IIS".
86 |
87 | ### Preparations
88 |
89 | To make IIS work well with li3's pretty URL system, install the URL Rewrite extension located on the [iis.net website](http://www.iis.net/download/URLRewrite) and follow the proper installation instructions located there.
90 |
91 | Next, follow the setup instructions for [PHP on IIS](http://php.iis.net/) for either [IIS 6.0](http://learn.iis.net/page.aspx/247/using-fastcgi-to-host-php-applications-on-iis-60/) or [IIS 7.0](http://learn.iis.net/page.aspx/246/using-fastcgi-to-host-php-applications-on-iis-7/).
92 |
93 | Setup a site by right clicking "Sites" and click "Add Web Site"
94 |
95 | 1. Enter your website name.
96 | 2. Enter the physical path to your application's webroot directory (_\path\to\lithium\app\webroot_).
97 | 3. _Optional:_ If your content is in your home directory, click "Connect As" and click "Specific User". From therec enter your Windows login details, as this will allow IIS to have proper access to the application.
98 | 4. Enter the desired port.
99 | 5. Click "OK".
100 |
101 | ### Adding Rewrite Rules to IIS
102 |
103 | 1. Locate your site in the "Sites" tree and click it.
104 | 2. Locate the URL Rewrite icon from the extension installed earlier.
105 | 3. On the right hand side under "Inbound Rules" click "Import Rules"
106 | 4. Click the ellipsis (...) next to the field box and locate the `.htaccess` file within your `\path\to\lithium\app\webroot\` directory and select it.
107 | 5. Click "Import".
108 | 6. On the right hand side, click "Apply".
109 |
110 | Your li3 application should now be accessible via IIS.
111 |
112 |
113 | Tested with IIS7 (which is shipped with Windows 7).
114 |
115 |
116 | ## On Lighty
117 |
118 | This quick guide is meant to help Lighty users get a development setup running in order to create li3 apps served up by Lighttpd. Lighttpd is a web server designed and optimized for high-performance production environments. It has a small memory footprint, yet contains a rich feature set. If you're planning on using li3 and Lighttpd together, this guide is for you.
119 |
120 | ### Running Lighty
121 |
122 | You can download a copy of Lighty on the [official website](http://www.lighttpd.net/download/). Once downloaded, follow the instructions to get a running instance.
123 |
124 | Installation details should be found in the `INSTALL` file packaged with the downloaded archive. Online installation tips can also be found on [the Lighttpd website](http://redmine.lighttpd.net/projects/lighttpd/wiki/InstallFromSource).
125 |
126 | ### Configuration
127 |
128 | Let us assume for this example that you're developing locally and would like your app to be accessible at `http://lithium.local/li3`.
129 |
130 | First, enable `mod_magnet` in your `lighttpd.conf`.
131 |
132 | ```lighty
133 | server.modules += ( "mod_magnet" )
134 | ```
135 |
136 | Then, save the following script in a file named `li3.lua`, preferably somewhere near your `lighttpd.conf`.
137 |
138 | ```lighty
139 | -- Helper function
140 | function file_exists(path)
141 | local attr = lighty.stat(path)
142 | if (attr) then
143 | return true
144 | else
145 | return false
146 | end
147 | end
148 | function removePrefix(str, prefix)
149 | return str:sub(1,#prefix+1) == prefix.."/" and str:sub(#prefix+2)
150 | end
151 |
152 | --[[
153 | Prefix without the trailing slash.
154 | If you are _not_ serving out of a subfolder, leave `prefix` empty.
155 | --]]
156 | local prefix = '/li3'
157 |
158 | -- The magic ;)
159 | if (not file_exists(lighty.env["physical.path"])) then
160 | -- File still missing: pass it to the fastCGI backend.
161 | request_uri = removePrefix(lighty.env["uri.path"], prefix)
162 | if request_uri then
163 | lighty.env["uri.path"] = prefix .. "/index.php"
164 | local uriquery = lighty.env["uri.query"] or ""
165 | lighty.env["uri.query"] = uriquery .. (uriquery ~= "" and "&" or "") .. "url=" .. request_uri
166 | lighty.env["physical.rel-path"] = lighty.env["uri.path"]
167 | lighty.env["request.orig-uri"] = lighty.env["request.uri"]
168 | lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]
169 | end
170 | end
171 | -- Fallthrough will put it back into the lighty request loop..
172 | -- That means we get the 304 handling for free. ;)
173 | ```
174 |
175 | Finally, in your `lighttpd.conf`, add the following conditional:
176 |
177 | ```lighty
178 | $HTTP["host"] =~ "lithium.local" {
179 | server.document-root = "/path/to/your/app/webroot/"
180 | magnet.attract-physical-path-to = ( "/path/to/li3.lua" )
181 | }
182 | ```
183 |
184 | You'll probably need to add a line item in your `/etc/hosts` file as well:
185 |
186 | ```text
187 | 127.0.0.1 lithium.local
188 | ```
189 |
190 | Restart your lighttpd process, and you're done!
191 |
192 | ## Using Caddy
193 |
194 | Caddy is a new HTTP/2 web server with automatic HTTPS written in Go. It is pretty fast and easy to learn. You can get a quick start reading the [Getting Started](https://caddyserver.com/docs/getting-started) chapter from the [documentation](https://caddyserver.com/docs).
195 |
196 | To make use of Caddy and be able to start the Caddy Server with the command `caddy` there has to be a Caddyfile present. This file resides in the root directory of the project.
197 |
198 | The following example file is typically stored at `/path/to/framework` with the name `Caddyfile`.
199 |
200 | ```nginx
201 | localhost:2020
202 |
203 | gzip
204 |
205 | root app/webroot
206 | fastcgi / localhost:9000 php
207 |
208 | log app/resources/tmp/logs/access.log
209 | errors app/resources/tmp/logs/error.log
210 |
211 | rewrite / {
212 | if {file} not favicon.ico
213 | to {path} {path}/ /index.php?url={query}
214 | }
215 | ```
216 |
217 | If you want to run Caddy with different plugins, check their documentation for a list of all possible directives.
218 |
--------------------------------------------------------------------------------
/01_installation/README.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Starting a New Project
4 |
5 | The best way to start a *project* is to base it on a already available project *distribution*. There are distributions for general web projects or for projects that require an micro-framework style approach.
6 | Distributions come with a predefined app [file structure](../architecture/file-structure), some boilerplate code and the bundled core library.
7 |
8 | We'll base our new project off the officially supported [standard distribution](https://github.com/UnionOfRAD/framework).
9 |
10 | For the start we'll use [composer](https://getcomposer.org/) to create our project in
11 | the current directory as `project`.
12 |
13 | ```bash
14 | composer create-project --prefer-dist unionofrad/framework project
15 | ```
16 |
17 | ## Pedal to the Metal
18 |
19 | For the purposes of this guide, we'll be using PHP's builtin development webserver. This is good for development but should of course not be used in production. Instructions on how to use other
20 | webservers in production can be found on the [Web-Servers](web-servers) page.
21 |
22 | ### Permissions
23 |
24 | The framework will need write access to the `app/resources/tmp` as it used to store cached versions of
25 | already compiled templates or logs. The webserver user must be able to write to this directory.
26 |
27 | ### Starting the Webserver
28 |
29 | Make sure you are in the root of the project. Now start PHP's builtin webserver using the following command. Your project is then being served at 127.0.0.1 on port 8080.
30 |
31 | ```bash
32 | php -S 127.0.0.1:8080 -t app/webroot index.php
33 | ```
34 |
35 | Finally, pull up the project in your browser and visit [`http://127.0.0.1:8080`](http://127.0.0.1:8080).
36 | At this point, you should be presented with the default home page. **You're up and running!**
37 |
38 |
39 | ## Livin' on the Edge
40 |
41 | The methods described in the previous sections will download the most recent tagged version of
42 | the framework and core library. In some cases, it may be desirable to update both to the very
43 | latest available revision, which may not have been tagged yet.
44 |
45 | ```bash
46 | composer create-project -s dev unionofrad/framework project
47 |
48 | cd project
49 | git checkout 1.0
50 |
51 | cd libraries/lithium
52 | git checkout 1.0
53 | ```
54 |
55 |
--------------------------------------------------------------------------------
/03_architecture/01_mvc.md:
--------------------------------------------------------------------------------
1 | # Model-View-Controller
2 |
3 | Understanding the Model View Controller (or MVC) approach to building web applications is fundamental to programming with li3. At a basic level, MVC programming allows the programmer to separate the way information is presented from how it is gathered and processed. Maintaining this separation makes adding new features simpler and more intuitive. It also makes fixing bugs and creating entirely new ways to view your application much easier.
4 |
5 | While not a comprehensive guide on MVC, this section aims to introduce you to the concept as well as impart a few best practices from the li3 perspective.
6 |
7 | ## Models
8 |
9 | The primary goal of a model is to manage domain knowledge for a particular object. Model classes in li3 are typically named after the object they manage. The model is in charge of CRUD operations for the object, data validation, as well as commonly used data manipulation operations upon that object's data.
10 |
11 | Most applications contain a model that manages the user domain. Features of this Users model might include:
12 |
13 | * Validation of user information before saving (valid email address, required field enforcement)
14 | * Simple saving and fetching of user data
15 | * Concatenation of the first and last names for a "virtual" full name field
16 | * Complicated finder methods for user data (finding a user by account status or age)
17 |
18 | ## Views
19 |
20 | Views present model data and the interface for your application. They contain some logic, but it's usually pretty light things like conditionally showing information, reusable bits of presentational code, or looping through objects in order to present them.
21 |
22 | While most views in li3 contain HTML, MVC programming allows you to switch between presentational technologies easily. A well-built MVC application should be able to show and switch between HTML, PDF, SVG or JSON with minimal effort.
23 |
24 | ## Controllers
25 |
26 | In desktop software, controllers typically respond to user commands. Similarly on the web, a controller handles an HTTP request. Logic in a controller is mainly focused on application flow.
27 |
28 | Typical controller functions might include:
29 |
30 | * Authenticating a request, and re-routing a user based on permissions
31 | * Using models to hand the view layer processed data
32 | * Handling an error in the application
33 | * Forwarding a user along to a different action based on model state
34 |
35 | Controllers are often named after the model they primarily deal with.
36 |
37 | ## MVC Best Practices
38 |
39 | Now that you know the primary purposes of each part of the MVC architecture, you should also be aware of a number of MVC best practices that will keep your code neatly organized and easier to maintain:
40 |
41 | * Models should be feature-rich; Controllers should be very thin.
42 | * Views should only contain logic that is presentational in nature. This usually means no access to models.
43 | * Keep presentational languages like HTML out of your models and controllers.
44 | * Actions dealing primarily with a given model should be placed in that model's controller.
--------------------------------------------------------------------------------
/03_architecture/02_aop.md:
--------------------------------------------------------------------------------
1 | # Aspect-oriented Programming
2 |
3 | If you've been a programmer for a while or have received formal training as a programmer,
4 | it's very likely you've heard of Object-Oriented programming, or OOP. This way of viewing
5 | code focuses on creating objects that represent the data and behavior of real-world
6 | objects. OOP programmers tend to organize modules into groups that inherit from each other
7 | and are (hopefully) loosely coupled or independent of one another.
8 |
9 | ## Cause for Concern
10 |
11 | Occasionally, a technique or feature in an application doesn't seem to fit well in a
12 | single object. Rather than being encapsulated once, it seems to need to "cut across"
13 | many different parts of the architecture at once. Logging, error detection and handling,
14 | workflow auditing, and caching are some examples of cross-cutting concerns that find their
15 | way into most web applications.
16 |
17 | Aspect-Oriented Programming, or AOP, is a technique that focuses on the organization and
18 | modularity of these cross-cutting concerns.
19 |
20 | ## AOP
21 |
22 | Aspect Oriented Programming finds a home in the li3 in at least two main pieces of
23 | functionality: [filters](../common-tasks/filters)
24 | and strategies.
25 |
26 | [An entire guide is devoted to li3's filter system](../common-tasks/filters),
27 | but let's review it at a high
28 | level to see how it fits in the AOP paradigm. A filter is basically a bit of code you
29 | want applied to the existing flow of logic in the framework. For example, you might want
30 | a bit of logic that logs errors and sends them to a web service for reporting. This
31 | cross-cutting concern could really be utilized in many different parts of the code: in
32 | the model, view, or controller layers. Filters allow you to inject your logic into the
33 | flow, logging errors at whatever level they occur. This happens without having to extend
34 | or subclass core objects, nor implement messy beforeX afterX methods in your application
35 | classes.
36 |
37 | Apart from filters, strategies are often used as a way to implement many different ways to
38 | accomplish a similar task. For example, the li3 session functionality is handled by the
39 | Session class. You can configure that class to use a number of different storage engines,
40 | each with it's own storage strategy. For example, you can configure sessions to be stored
41 | in memory either in JSON or Base64 format. Strategies create a cross-cutting solution that
42 | can be applied to a class that's been adapted to handle them.
43 |
44 | ## Summary
45 |
46 | We enjoy the AOP flavor li3 has, mostly for it's centralized way to deal with
47 | cross-cutting concerns. These features usually start scattered across a codebase, and
48 | li3 solves this problem by facilitating a mechanism to house this logic. Though it does
49 | take some getting used to, embracing AOP in your application can help you organize these
50 | features in a concise and easily managed way.
51 |
--------------------------------------------------------------------------------
/03_architecture/03_objects.md:
--------------------------------------------------------------------------------
1 | # Constructors and Initialization
2 |
3 | The base class in li3's hierarchy, `lithium\core\Object`, forms the basic logic for all of the concrete classes in the framework. This class defines several conventions for how classes in li3 should be structured:
4 |
5 | * Universal constructor
6 | * Object configuration
7 | * Object initialization
8 | * Filtering
9 | * Testing utilities
10 |
11 | ## Universal Constructor
12 |
13 | The `Object` class features a unified constructor, which all classes share. The function takes a single argument, `$config` which is an array of properties that is automatically saved to the `$_config` property of the current object. This approach allows for short constructor signatures and creates a unified way to override object configuration, or set object defaults when subclassing.
14 |
15 | ## Object Configuration & Initialization
16 |
17 | Constructor logic should be kept to a minimum. One of the last steps in the unified constructor is to call the objects `init()` method. Here's where you do your heavy lifting.
18 |
19 | If you need to manipulate an object in its un-initialized state, pass `false` as the value to the `init` key to the constructor. This prevents `init()` from running, and is especially handy when building test cases.
20 |
21 | ```php
22 | namespace app/extensions;
23 |
24 | use lithium\net\http\Service;
25 |
26 | class Foo extends \lithium\core\Object {
27 | public $service;
28 |
29 | public function __construct(array $config = []) {
30 | $defaults = [
31 | 'foo' => 'bar'
32 | ];
33 | parent::__construct($config + $defaults);
34 | }
35 |
36 | public function _init() {
37 | parent::_init();
38 | $this->service = new Service();
39 | }
40 |
41 | public function baz() {
42 | echo $this->_config['foo'];
43 | }
44 | }
45 | ```
46 |
47 | ```php
48 | use app\extensions\Foo;
49 |
50 | $foo = new Foo();
51 | $foo->baz(); // 'bar'
52 |
53 | $foo2 = new Foo([
54 | 'foo' => '123'
55 | ]);
56 | $foo2->baz(); // '123'
57 |
58 | $foo3 = new Foo([
59 | 'init' => 'false'
60 | ]);
61 | get_class($foo3->service); // PHP Warning...
62 | get_class($foo->service); // 'lithium\net\http\Service'
63 | ```
64 |
65 |
66 | The filtering system was overhauled in framework version 1.1. Filters are
67 | now managed via a dedicated Filters class, instead of before where Object managed
68 | them.
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/03_architecture/04_file_structure.md:
--------------------------------------------------------------------------------
1 | # Application File Structure
2 |
3 | Before you get started with li3, it's important to know how your application's main folder is structured, and where everything should go. Clone yourself a copy of the main repo, and you'll find an app folder structure that contains:
4 |
5 | * `config`
6 | * `controllers`
7 | * `extensions`
8 | * `libraries`
9 | * `models`
10 | * `resources`
11 | * `tests`
12 | * `views`
13 | * `webroot`
14 |
15 | Let's tackle each of these folders one-by-one, covering what each is used for, and how you can best organize your application.
16 |
17 | ## Config
18 |
19 | The `config` folder contains three main pieces: bootstrap files, your connections information, and your routes definitions.
20 |
21 |
22 | ### Bootstrapping
23 |
24 | The main bootstrap file in the config folder is `bootstrap.php`. This file is second file PHP will execute in any request cycle, and it loads up the li3 framework along with any other extras that you define there. As such, it's a great place to place initial application configuration.
25 |
26 | The best practice for bootstrapping parts of your application is by writing specific bootstrap files and placing them in `config/bootstrap/filename.php`. Once written, include the configuration into the main bootstrap file:
27 |
28 | ```php
29 | // Adding my custom configuration or initialization...
30 | require __DIR__ . '/bootstrap/myconfig.php';
31 | ```
32 |
33 | ### Connections
34 |
35 | The `connections.php` file in config lists the connections you have to external data resources, most often some type of database.
36 |
37 | Connections should be listed in this file, as calls to `Connections::add()`, like so:
38 |
39 | ```php
40 | Connections::add('default', [
41 | 'type' => 'database',
42 | 'adapter' => 'MySql',
43 | 'host' => 'localhost',
44 | 'login' => 'root',
45 | 'password' => '',
46 | 'database' => 'my_blog'
47 | ]);
48 |
49 | Connections::add('couch', [
50 | 'type' => 'http', 'adapter' => 'CouchDb', 'host' => '127.0.0.1', 'port' => 5984
51 | ]);
52 |
53 | Connections::add('mongo', ['type' => 'MongoDb', 'database' => 'project']);
54 | ```
55 |
56 | The particulars on the adapter will shape how the connection definition is put together, but this list should constitute what you've got in `connections.php`.
57 |
58 | ### Routes
59 |
60 | Routes definitions is how you inform the framework how URLs and bits of code match up. At the most basic level, it's how you tell li3 which controller should respond to a request to a given URL.
61 |
62 | For example, I could specify that any request to `/login` is handled by `UsersController::login()` like this:
63 |
64 | ```php
65 | Router::connect('/login', ['controller' => 'users', 'action' => 'login']);
66 | ```
67 |
68 | More on routes later, but this file should house all such configuration information.
69 |
70 | ## Controllers
71 |
72 | Application controllers are housed here. Each controller is named in CamelCased, plural format (i.e. `PostsController.php`), and placed here.
73 |
74 | ## Extensions
75 |
76 | The extensions folder is meant to store extension classes that you've created for your application. Custom helpers, adapters, console commands and data classes.
77 |
78 | ### Adapter
79 |
80 | A number of li3 classes extend the `lithium\core\Adaptable` static class. This class allows you to take a number of different approaches and easily switch out implementations for an application task. Authentication, session handling, and caching are a few examples of li3 functionality that have been made adaptable.
81 |
82 | Custom application adapters should be placed in this folder, organized by subfolder. For example, if I created a custom adapter for the `Auth` class, based on my LDAP setup, I'd create the implementation in `/extensions/adapter/auth/Ldap.php`.
83 |
84 | ### Command
85 |
86 | Custom console applications are placed in `command`. Commands you create should be built similar to those you see in `/lithium/console/command`.
87 |
88 | As an example, say I created a custom mailer queue, and I need to create a command to fill up the queue with announcement emails and trigger the send process. I'd create a new class called `QueueFiller` that extends `lithium\console\Command`, and place it in `app/extensions/command/QueueFiller.php`.
89 |
90 | Once there, running `li3 queue-filler` from the command line will trigger the logic I've written.
91 |
92 | ### Data
93 |
94 | Occasionally a new application will require custom data classes to work with a storage engine not already accommodated for by core classes.
95 |
96 | If you've got some proprietary database, and it's result objects or query structures are very customized, you'll extend the classes in the `lithium\data` namespace (i.e. `Source`, `source\Database` or `source\Http`) and place them here for use in your application.
97 |
98 | ### Helper
99 |
100 | Custom view helpers are common need. These classes should extend `lithium\template\Helper` and be placed in this folder.
101 |
102 | ## Libraries
103 |
104 | The `libraries` folder is meant to house entire li3 applications—or plugins—that your main application makes use of.
105 |
106 | Third-party, non-li3 libraries should be placed here as well. If you're integrating with Facebook, or using part of the Zend Framework in your application, place those files here as well.
107 |
108 | ## Models
109 |
110 | Application models are stored here, in CamelCased, plural naming format (i.e. `Posts.php`).
111 |
112 | ## Resources
113 |
114 | The resources folder is used by a li3 application as a non-web viewable storage place for application data. By default globalization files (such as `.po` string files) and temporary application files are stored there.
115 |
116 | As you build your application, the `resources` folder is a great place for other application data like custom cache files, SQLite databases, or a temporary spot for uploaded files.
117 |
118 | The `resources` is a place where the web server has write access, so make sure to keep that in mind when securing your application.
119 |
120 | ## Tests
121 |
122 | The `tests` folder is where the unit testing framework files for your application reside. The testing setup for li3 is covered elsewhere, but let's cover what ends up in each of the testing subfolders here.
123 |
124 | ### Cases
125 |
126 | All of the actual unit testing logic for the main MVC classes in the application go in the `cases` folder. Subfolders should already exist inside of `cases` to prompt you where each sort of test case should go.
127 |
128 | ### Integration
129 |
130 | Unit tests in the `integration` folder test the interaction between two or more classes, or abstract class implementations.
131 |
132 | ### Mocks
133 |
134 | Classes in `mocks` are used to facilitate certain unit tests. For example, if I'm testing a custom model I've created, I can create a new class in `mocks/data/MockSource.php` and use it in my model unit testing logic.
135 |
136 | It is recommend that you use subfolder organization that mimics your application's and li3's namespacing and folder structure.
137 |
138 | ## Views
139 |
140 | The `views` folder contains any pieces of view-level code in your application. The two main core folders here to note are `elements` and `layouts`. Other view folders here contain views organized by controller name.
141 |
142 | ### Elements
143 |
144 | Elements are small bits of view code that are used in multiple views. A common navigational element, menu, or form might end up as an element in this folder. Element files are suffixed with a compound extension: the first showing the type of view code, and the last as `.php`.
145 |
146 | Example element filenames might include `nav.html.php`, `animateLink.js.php`, or `header.xml.php`.
147 |
148 | ### Layouts
149 |
150 | Views (and the elements they may contain) are rendered inside of view layouts. The layouts for your application reside in this folder, named similar to elements.
151 |
152 | A layout named `default` will be used to render a view if none other has been specified by the controller.
153 |
154 | ### Controller View Folders
155 |
156 | Along with the `elements` and `layouts` folders, many other controller-specific view folders will reside here.
157 |
158 | One default example is the `pages` folder here that holds all the views for the core `PagesController`. As you create new controllers and views for your application, create new folders here, named after your controllers.
159 |
160 | ## Webroot
161 |
162 | The `webroot` folder of your application is (ideally) the only place in your application that is web-visible. The `index.php` file takes care of bootstrapping li3, and the rest of the folders and files here are used for static content delivery.
163 |
164 | Things like JavaScript files, images, Flash objects, and movies should end up here for easy access for your web server to deliver to the client.
165 |
--------------------------------------------------------------------------------
/03_architecture/05_response-lifecycle.md:
--------------------------------------------------------------------------------
1 | # A Typical Request and Response
2 |
3 | When building an application with li3, it's important to understand how a typical request is handled. This gives you a better idea of the possibilities inherent in the system, and allows you to better troubleshoot any problems that might arise during development. This also gives you a good macro view of what's going on before we dive into how controllers, views, and context works inside li3.
4 |
5 | ## Initial Request and Bootstrapping
6 |
7 | While a li3 application can be configured a number of different ways, the safest is by pointing your web server to the `/app/webroot` folder inside the application. It can be pointed at the root directory of the application, but either way, the request is forwarded on until `/app/webroot/index.php` handles it.
8 |
9 | This directory index file does two things: first, it loads up li3's main bootstrap file (and any related bootstrap files therein). Second, it instantiates the dispatcher, and hands its `run()` method a new `Request` object. The `Request` object aggregates all the GET / POST / environment data, and is the canonical source of any of that information throughout the life of the request.
10 |
11 | ## Request Dispatching & Routing
12 |
13 | Once `Dispatcher` has the request, it asks the router to process it. This processing basically matches the request against each configured route, in the order they were defined. Once a match is found, the Router returns parameter information necessary to find a controller class, and dispatch a method call against it.
14 |
15 | Once those parameters have been returned, the Dispatcher uses them to instantiate the correct controller object, and invokes it. The invokation logic in each controller informs it on which action to call to handle the response.
16 |
17 | ## Action Rendering
18 |
19 | Each controller action contains a set of business logic to build a facet or interface in your application. It may interact with models or other classes to fetch, validate, sanitize, and process the data.
20 |
21 | Once the data is ready for the view layer, the action hands it off either through `$this->set()`, or by returning an associative array. That data is passed, along with information about the response type, to the Media class.
22 |
23 | ## The Media Class
24 |
25 | The Media class facilitates content-type mapping (mapping between content-types and file extensions), handling static assets, and globally configuring how the framework handles output in different formats.
26 |
27 | Once the controller action data and request has been passed to Media, it matches the request with the correct response type, and triggers the view layer accordingly. Most often this results in rendering an HTML view (and its elements) inside of an HTML layout.
28 |
29 | Media is extremely flexible, however, and the response could result in JSON serialization, XML formatting, or writing out the contents of an audio file to the output buffer. Content type mapping is done by calling `Media::type()`, usually in a bootstrap file.
30 |
31 | ## Content Output
32 |
33 | Finally, the response content, along with any headers, are packed into a response class, which is passed by the controller back up to `Dispatcher`. The dispatcher returns the Response object to index.php, where it is echoed to the output buffer.
34 |
35 | This echo writes the headers, and performs a buffered output of whatever content was returned from the Media class.
36 |
--------------------------------------------------------------------------------
/03_architecture/06_dependencies.md:
--------------------------------------------------------------------------------
1 | # Managing Dependencies
2 |
3 | Objects that subclass li3's core `Object` class can also manage dependencies at runtime using a simple dependency injection mechanism.
4 |
5 |
6 |
--------------------------------------------------------------------------------
/03_architecture/README.md:
--------------------------------------------------------------------------------
1 | # Architecture
2 |
3 | Though most of this manual covers how to create your own classes in li3 (controllers, models,
4 | and the like), you may want to familiarize yourself with some of the basic architectural ideas
5 | and philosophies behind the framework. As you come to know the code—and start to build plugins
6 | and extensions—keeping additions in style with li3's architecture becomes important.
7 |
8 | This guide explains some of the ideas and practices behind how li3 is put together, at a
9 | fundamental level. Inspecting the core libraries will make much more sense after understanding
10 | the principles outlined here.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/04_configuration/01_bootstrapping.md:
--------------------------------------------------------------------------------
1 | # Bootstrapping
2 |
3 | Configuration in li3 is meant to be minimal and easy to understand. As such, it's based on a simple set of PHP files that are included as needed. The main boostrap file is `app/config/bootstrap.php` and houses all of the configuration for your application. While there might be a tendency to add bits of configuration to this file, realize it's really only meant as a switchhouse for other configuration details.
4 |
5 | The core configuration sets are either actively required from this main file, or commented out (because we like to run lean by default). Before creating new files, peruse the configurations in `app/config/bootstrap`, as many commonly used mechanisms (caching, sessions, globalization) already have configuration examples in this folder.
6 |
7 | While we've included a lot of examples to get you started, feel free to add new files to the `boostrap` directory and require them from the main `bootstrap.php` file. If you're working in the cloud or interacting with a specific set of APIs or just need a place to keep a few global constants, those configurations are well-placed in this organization scheme.
8 |
9 | ### Action
10 |
11 | The `action.php` configuration file is used to house request lifecycle filters. If you've got some piece of logic that sits in between lifecycle events, this is a great place for it. For starters, this file contains a filter that detects the current environment settings and loads all the routes in the main application and in all enabled plugins.
12 |
13 | ### Cache
14 |
15 | This configuration file is used to set cache adapter and strategy settings. As your application matures, caching will become more important and more complex. Use this file to tell the application what storage adapter to use, along with the relevant settings.
16 |
17 | ### Connections
18 |
19 | The `connections.php` file houses persistent storage options. Most commonly, this means database connection details.
20 |
21 | ### Console
22 |
23 | Much like `action.php`, this file houses CLI-related filters and settings. If your console applications require specific settings due to your terminal emulation or color settings, this is the place for it. This file also contains some basic environmental and routes-based settings, but for console applications.
24 |
25 | ### Errors
26 |
27 | Error and exception handling related configuration is kept here. For starters, you'll find a filter in this file that tells li3's ErrorHandler class how to trap exceptions and errors triggered in controller actions.
28 |
29 | ### G11n
30 |
31 | All globalization configuration happens here. Timezone, current locale, catalog details and translation-related filters can already be found in this file.
32 |
33 | ### Libraries
34 |
35 | Every collection of classes located in a single base directory is handled as a _library_ in li3. With the exception of the primary library (initially called "app"), these packages of code are usually found in `/libraries` or `app/libraries` and are managed, listed, and enabled here.
36 |
37 | The first part of this configuration file sets up the main path components the rest of li3 will base itself from. Unless you're doing some customization, you can probably leave this as-is. It actually keeps things running a bit faster than they would otherwise.
38 |
39 | At the bottom of the file, however, you'll see the main components of the appliation being registered by the `Libraries::add()` method. Note that the "app" library, including li3 itself are handled just like any other third-party addon or plugin.
40 |
41 | This manual is also a library: enabled by downloading the codebase to `/libraries` (or `app/libraries`) and adding this to `libraries.php`:
42 |
43 | ```php
44 | Libraries::add('manual');
45 | ```
46 |
47 | ### Media
48 |
49 | The `Media` class is central to understanding how li3 handles and renders any given response. The `media.php` file handles the configuration surrounding this class.
50 |
51 | For instance, you can register different format handlers, or uncomment a filter that allows plugin static assets to be routed to more easily.
52 |
53 | ### Session
54 |
55 | As titled, this configuration file handles the details surrounding user sessions. Details like adapter type, session name, and authentication configuration are all managed here.
56 |
--------------------------------------------------------------------------------
/04_configuration/02_environment.md:
--------------------------------------------------------------------------------
1 | # Environment Configuration
2 |
3 | Most applications use a set of different environments to validate new features and test the fixes for bugs before letting the end users see it. li3's `Environment` class offers a way to organize settings and make logic in your application conditional based on where the application thinks it resides.
4 |
5 | Rather than wrapping conditionals around all of your database settings and API keys and endpoints, you can use the `Environment` class to direct the application automatically, based on context.
6 |
7 | li3 offers a default set of environments (development, test, and production) as well as a starting point for determining which one the application resides in. This guide covers the process of showing li3 how to detect and define your different environments and use them in configuration details.
8 |
9 | ## Getting Started: Detection
10 |
11 | The first step in using different environmental settings is telling li3 how to detect where it is operating from. There are a number of clues often used in determining the environment: IP address or ranges, hostname pattern matching, or possibly even looking at the filesystem or database.
12 |
13 | Environment detection is determined by default as follows:
14 |
15 | * The request is local (IP address 127.0.0.1, etc.): _development_.
16 | * The request URL or hostname starts with "test": _test_.
17 | * Neither of the above: _production_.
18 |
19 | To customize this behavior, use the `Environment::is()` method inside a bootstrap file. Supply a closure that inspects an incoming `Request` and returns the name of the environment deteted. Here's a simple example:
20 |
21 | ```php
22 | Environment::is(function($request){
23 | $host = $request->env('HTTP_HOST');
24 |
25 | if ($host == 'project.local' || $host == 'localhost') {
26 | return 'development';
27 | }
28 | if (preg_match('/^qa/', $host)) {
29 | return 'qa';
30 | }
31 | if (preg_match('/beta/', $host)) {
32 | return 'staging';
33 | }
34 | return 'production';
35 | });
36 | ```
37 |
38 | This code defines four custom environments: _development_ if the hostname matches a predetermined set of hostnames, _qa_ if the hostname begins with "qa" (qa1.example.com, qa2.example.com), and _staging_ if the hostname contains "beta" (beta.example.com, www.example-beta.com).
39 |
40 | If none of those conditions are met, the default is production.
41 |
42 | ## Environment-Specific Settings
43 |
44 | Once detection is configured, you're able to set environment-specific variables inside of your bootstrap files.
45 |
46 | ```php
47 | Environment::set('development', ['service_endpoint', 'dev.service.example.com']);
48 | Environment::set('staging' , ['service_endpoint', 'beta.service.example.com']);
49 | Environment::set('qa' , ['service_endpoint', 'qa1.service.example.com']);
50 | Environment::set('production' , ['service_endpoint', 'www.service.example.com']);
51 |
52 | // If run on my local system:
53 | Environment::get('service_endpoint'); // 'dev.service.example.com'
54 | ```
55 |
56 | ## Adaptable Environment Settings
57 |
58 | Subclasses of the core `Adaptable` class (`Logger`, `Connections`, `Cache`, `Session`, etc.) can also be configured on an environmental basis. For core functionality, this is much cleaner than large sets of `Environment::set` calls in your bootstrap files.
59 |
60 | The most common example is configuring different database connections based on environment. Here's a quick example:
61 |
62 | ```php
63 | Connections::add('default', [
64 | 'production' => [
65 | 'type' => 'database',
66 | 'adapter' => 'MySql',
67 | 'host' => 'live.example.com',
68 | 'login' => 'mysqluser',
69 | 'password' => 's3cr3tg035h3rE',
70 | 'database' => 'app-production'
71 | ],
72 | 'development' => [
73 | 'type' => 'database',
74 | 'adapter' => 'MySql',
75 | 'host' => 'localhost',
76 | 'login' => 'root',
77 | 'password' => '4d1fFeR3n75eCr37',
78 | 'database' => 'app'
79 | ]
80 | ]);
81 | ```
82 |
83 | While it is handy for using the same types of technologies in each environment, this mechanism allows you to switch between engines as well. For example, using different kinds of Cache engines in each environment:
84 |
85 | ```php
86 | Cache::config([
87 | 'userData' => [
88 | 'development' => ['adapter' => 'File'],
89 | 'production' => ['adapter' => 'Memcache']
90 | ]
91 | ]);
92 | ```
93 |
94 | For more information about creating your own classes that utilize `Adaptable` and `Environment` for automatically changing settings, see the Adaptable guide in the Advanced Tasks section.
95 |
--------------------------------------------------------------------------------
/04_configuration/03_third_party_libraries.md:
--------------------------------------------------------------------------------
1 | # Working with External Libraries
2 |
3 | li3 applications are made up of collections of _libraries_. A library is any discrete group of PHP class files; this includes any li3 plugins, other class libraries like PEAR and Zend Framework, and other frameworks like Symfony 2. Even your application and the li3 core itself are considered libraries in li3 parlance.
4 |
5 | ## Basics
6 |
7 | Libraries are managed by the `lithium\core\Libraries` class, which handles auto-loading, service location, and introspecting available classes. When loading classes, li3 assumes a [PSR-0 compatible](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) class mapping structure. For more information, see the [documentation for the `Libraries` class](/docs/api/lithium/latest:1.x/lithium/core/Libraries).
8 |
9 | The default li3 distribution ships with two `libraries` directories: one located at the root of the distribution, and one located in the `app` folder (or any application you generate with the console tooling). Libraries can be installed into either of these directories, and both are used interchangeably throughout this guide. The global `libraries` directory is intended for libraries which are shared across multiple applications, whereas the local directory is intended for application-specific ones, and will override the global directory in the event of a conflict.
10 |
11 | By convention, library configurations are added in `config/bootstrap/libraries.php`, and are loaded in your application's bootstrap process. However, there are no rules or requirements as to where or when this can happen. The default bootstrap files are simply that: a default; they're designed to be changed and re-organized as your application requirements dictate.
12 |
13 | ## Installation
14 |
15 | Generally, there are two ways in which a library can be packaged and distributed. In the first, the library _is_ the distribution. In this case, the root of the PHP class libraries is the same as the root of the repository or distribution. An example of this is [the li3 core](https://github.com/UnionOfRAD/lithium).
16 |
17 | The alternative approach is for a distribution to contain its library in a subdirectory. Examples would be the [Zend Framework](https://github.com/zendframework/zf2/tree/master/library) or [Symfony 2](https://github.com/symfony/symfony/tree/master/src).
18 |
19 | In the first case, installation is as simple as downloading (or cloning, or adding as a submodule) the library you wish to use into your `libraries` directory. When registering the library with [`Libraries::add()`](/docs/api/lithium/latest:1.x/lithium/core/Libraries::add(), no other configuration is necessary, since li3 knows where to look for it, and how to map its classes.
20 |
21 | Since that's not possible for libraries in the second case, we recommend an alternative installation: download (clone, etc.) the distribution to `libraries/_source`, and symlink the root of the library source into the `libraries` directory.
22 |
23 | For example, the Zend Framework 1.11.7 distribution would be installed to `libraries/_source/ZendFramework-1.11.7`. Then, `libraries/Zend` would be symlinked to `libraries/_source/ZendFramework-1.11.7/library/Zend`, so that the root of the library source would map directly to where li3 expects it to be.
24 |
25 | This not only simplifies your configuration, but has the added benefit of allowing you to switch between different versions of vendor libraries for your application, just by changing a symlink. _Note_: While the `libraries` directories are used by convention, libraries can be referenced anywhere on the filesystem by including a `'path'` key in your `Libraries::add()` configuration.
26 |
27 | ## Configuration
28 |
29 | The following examples should give you an overview of the various approaches to configuring class libraries in li3. The easiest type of library to configure are libraries written for PHP 5.3 or higher which conform to the above-referenced namespacing standard.
30 |
31 | For example, the image manipulation library [ Imagine](https://github.com/avalanche123/Imagine), once installed according to the above (with `lib/Imagine` symlinked to `libraries/Imagine`), can be configured simply with the following:
32 |
33 | ```php
34 | Libraries::add('Imagine');
35 | ```
36 |
37 | Again, because it conforms to the 5.3 namespacing standard, and because (using the symlink) li3 knows how to locate its class files, no other configuration is required.
38 |
39 | ### PEAR
40 |
41 | Since PEAR is typically installed into a system directory (i.e. `/usr/local/lib/php`), you should first symlink it to `libraries/PEAR`, then add the following:
42 |
43 | ```php
44 | Libraries::add("PEAR", [
45 | "prefix" => false,
46 | "includePath" => true,
47 | "transform" => function($class, $config) {
48 | $file = $config['path'] . '/' . str_replace("_", "/", $class) . $config['suffix'];
49 | return file_exists($file) ? $file : null;
50 | }
51 | ]);
52 | ```
53 |
54 | There are a number of things to note about this configuration. First, while PEAR classes use package prefix, there is no standard vendor prefix, so the `'prefix'` setting must be overridden accordingly.
55 |
56 | Second, PEAR classes depend on PHP's include path. Setting `'includePath'` to `true` adds the current library path to the include path. It can also be set to an absolute physical path if, for example, the library depends on the directory _above_ it being in the include path.
57 |
58 | Finally, the `'transform'` key is used to manually map class names to file names.
59 |
60 | ### Zend Framework 1.x
61 |
62 | First, we recommend installing ZF1 from the unofficial Git mirror, at `git://github.com/Enrise/Zend.git`. This distribution contains only the class library, and as such, can be cloned (or submodule'd) directly into the `libraries` directory.
63 |
64 | If you downloaded ZF1, or did an SVN checkout, you'll follow the alternative instructions above so that (for example, with ZF 1.11.7) you'll have `libraries/Zend` symlinked to `libraries/_source/ZendFramework-1.11.7/library/Zend`.
65 |
66 | Once installed, ZF1 can be configured per the following. Again, because li3 assumes 5.3 standard namespacing in all libraries, some special considerations are necessary for dealing with class libraries written for 5.2 and lower.
67 |
68 | ```php
69 | Libraries::add("Zend", [
70 | "prefix" => "Zend_",
71 | "includePath" => LITHIUM_LIBRARY_PATH, // or LITHIUM_APP_PATH . '/libraries'
72 | "bootstrap" => "Loader/Autoloader.php",
73 | "loader" => ["Zend_Loader_Autoloader", "autoload"],
74 | "transform" => function($class) { return str_replace("_", "/", $class) . ".php"; }
75 | ]);
76 | ```
77 |
78 | First, because we're dealing with underscore-prefixed classes, we need to override the default prefix. Also, ZF1 depends on its parent directory being included in PHP's `include_path`, which we can tell `Libraries` to do, using the directory constants to provide an absolute path. Again, if your system is already configured for this, you can omit the `'includePath'` key.
79 |
80 | Next, since ZF1 uses its own autoloader, we can tell `Libraries` to delegate to that one, first by setting its class file as the bootstrap file, then by indicating it should use the `Zend_Loader_Autoloader` class as the loader.
81 |
82 | Most importantly, we're overriding how class names are transformed into path names, by passing in a custom function which transforms PEAR-style class names. Finally, to use classes in Zend's incubator, we can add a separate configuration for this.
83 |
84 | Note, the following should appear **above** the primary ZF configuration, because they both have the same class prefix. However, this configuration will verify that a file exists before attempting to autoload it, allowing classes to "fall through" to other loaders.
85 |
86 | ```php
87 | Libraries::add("ZendIncubator", [
88 | "prefix" => "Zend_",
89 | "includePath" => '/path/to/libraries/ZF_Install_Dir/incubator/library',
90 | "transform" => function($class) {
91 | $file = str_replace("_", "/", $class) . ".php";
92 | return file_exists($file) ? $file : null;
93 | }
94 | ]);
95 | ```
96 |
97 | ### Zend Framework 2.x
98 |
99 | Fortunately, installing and configuring ZF2 is quite a bit easier. It can be cloned (or added as a submodule) from the official repository at `git://github.com/zendframework/zf2.git` into `libraries/_source/zf2`. Then, symlink `libraries/_source/zf2/library/Zend` to `libraries/Zend`.
100 |
101 | Because ZF2 uses the same class naming scheme as li3, configuring it is quite a bit easier:
102 |
103 | ```php
104 | Libraries::add("Zend");
105 | ```
106 |
107 | As with ZF1 above, using Zend's native autoloader is technically optional. Here, it is omitted specifically because both li3 and ZF2 class loading rules were written to the same specification, and using this approach, li3 can cache ZF class paths along with its own.
108 |
109 | ### Usage example: Zend Framework 1.x / 2.x
110 |
111 | Once you've properly installed and configured your chosen version of Zend Framework, you can use its classes as you would any other:
112 |
113 | ```php
114 | namespace app\controllers;
115 |
116 | /**
117 | * Import the class names. Use this style for static dependencies.
118 | */
119 | use Zend_Mail_Storage_Pop3; // For ZF1
120 | use Zend\Mail\Storage\Pop3; // For ZF2
121 |
122 | class EmailController extends \lithium\action\Controller {
123 |
124 | /**
125 | * Use this style for dynamic dependencies.
126 | */
127 | protected function _init() {
128 | $this->_classes += [
129 | 'pop3' => 'Zend_Mail_Storage_Pop3' // ZF1
130 | 'pop3' => 'Zend\Mail\Storage\Pop3' // ZF2
131 | ];
132 | }
133 |
134 | public function index() {
135 | // If used statically:
136 | $mail = new Zend_Mail_Storage_Pop3([
137 | 'host' => 'localhost', 'user' => 'test', 'password' => 'test'
138 | ]);
139 |
140 | // If used dynamically:
141 | $mail = $this->_instance('pop3', [
142 | 'host' => 'localhost', 'user' => 'test', 'password' => 'test'
143 | ]);
144 |
145 | return compact('mail');
146 | }
147 |
148 | }
149 | ```
150 |
151 |
152 | ### TCPDF
153 |
154 | Some legacy vendor libraries have no consistent class-to-file mapping scheme whatsoever. These libraries must be mapped by hand. Below is an example of a small library (TCPDF), containing just a few files.
155 |
156 | _Note_: For larger libraries, generating the map by hand can be tedious. Check out [the `Inspector` class](/docs/api/lithium/latest:1.x/lithium/analysis/Inspector), which can be used to introspect classes and files to generate a map automatically.
157 |
158 | ```php
159 | Libraries::add('tcpdf', [
160 | 'prefix' => false,
161 | 'transform' => function($class, $config) {
162 | $map = [
163 | 'TCPDF2DBarcode' => '2dbarcodes',
164 | 'TCPDFBarcode' => 'barcodes',
165 | 'TCPDF_UNICODE_DATA' => 'unicode_data',
166 | 'PDF417' => 'pdf417',
167 | 'TCPDF' => 'tcpdf',
168 | 'QRcode' => 'qrcode'
169 | ];
170 | if (!isset($map[$class])) {
171 | return false;
172 | }
173 | return "{$config['path']}/{$map[$class]}{$config['suffix']}";
174 | }
175 | ]);
176 | ```
177 |
178 | Again, the lack of vendor prefix is denoted by setting `'prefix'` to `false`. Next, the class-to-file mapping function is passed in the `'transform'` key, which contains an array mapping class names to file names. These are then used to generate and return a full physical path to the correct file.
179 |
--------------------------------------------------------------------------------
/04_configuration/README.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
--------------------------------------------------------------------------------
/05_models/01_connections.md:
--------------------------------------------------------------------------------
1 | # Connections
2 |
3 | Connection configuration is the first place you need to start when working with models. li3 supports a number of different relational and non-relational databases, and each is defined through the connection configuration.
4 |
5 | Connections are configured in the core bootstrap file `app/config/bootstrap/connections.php`. The `Connections::add()` method is used in this file to make different connections available to your models.
6 |
7 | Each connection is made up of two parts: the first part is the name that identifies the connection. This must be unique, and can be used to fetch connection details and configure models. If you create a connection named "default", your models will use it unless configured otherwise.
8 |
9 | The second part of a connection is an array of configuration options specific to the database type you're planning to use. Each connection configuration may differ, but most will include the following:
10 |
11 | - `'type'` _string_: The type of data source that defines this connection; typically a
12 | class or namespace name. Relational database data sources, use `'database'`, while
13 | CouchDB and other HTTP-related data sources use `'http'`, etc. For classes which
14 | directly extend `lithium\data\Source`, and do not use an adapter, simply use the
15 | name of the class, i.e. `'MongoDb'`.
16 |
17 | - `'adapter'` _string_: For `type`s such as `'database'` which are adapter-driven,
18 | provides the name of the adapter associated with this configuration.
19 |
20 | - `'host'` _string_: The host name that the database should connect to. Typically
21 | defaults to `'localhost'`.
22 |
23 | - `'login'` _string_: If the connection requires authentication, specifies the login
24 | name to use.
25 |
26 | - `'password'` _string_: If the connection requires authentication, specifies the
27 | password to use.
28 |
29 | A quick look at the examples in the default `connections.php` file illustrate the possibilities:
30 |
31 | ```php
32 | Connections::add('default', [
33 | 'type' => 'MongoDb',
34 | 'host' => 'localhost',
35 | 'database' => 'project'
36 | ]);
37 |
38 | Connections::add('default', [
39 | 'type' => 'http',
40 | 'adapter' => 'CouchDb',
41 | 'host' => 'localhost',
42 | 'database' => 'project'
43 | ]);
44 |
45 | Connections::add('default', [
46 | 'type' => 'database',
47 | 'adapter' => 'MySql',
48 | 'host' => 'localhost',
49 | 'login' => 'root',
50 | 'password' => '',
51 | 'database' => 'project',
52 | 'encoding' => 'UTF-8'
53 | ]);
54 | ```
55 |
56 | ## Models and Connections
57 |
58 | Models will look for the `'default'` connection first. If you've got more than one connection you're using, or wish a model to use an alternate, specify the connection name in the model's `$_meta` property.
59 |
60 | ```php
61 | class Posts extends \lithium\data\Model {
62 |
63 | protected $_meta = [
64 | 'connection' => 'legacy'
65 | ];
66 | }
67 | ```
68 |
69 | For connection-less models you may disable the connection altogether by setting the connection to use to `false`.
70 |
71 | ```php
72 | class Movies extends \lithium\data\Model {
73 |
74 | protected $_meta = [
75 | 'connection' => false
76 | ];
77 | }
78 | ```
79 |
80 | If you ever need to access the connection for model directly, you may do so by using the `connection()` method. The following example shows how to make use of this feature to control PDO transactions.
81 |
82 | ```php
83 | $source = Posts::connection(); // Gives you the connected data source i.e. a `Database` object.
84 | $pdo = $source->connection; // Gives you the underlying connection of that object.
85 |
86 | $pdo->beginTransaction();
87 | $pdo->commit();
88 | $pdo->rollback();
89 | ```
90 |
91 | ## Connections and Environments
92 |
93 | Many applications use different databases depending on which environment the application is currently being hosted from. For example, you might want to switch your MongoDB connection from a locally hosted database in development, but use a cloud-based hosting service once you're in production. Connection configurations were built with this in mind.
94 |
95 | Once your environments have been defined, use their names as keys in the configuration array, as shown here:
96 |
97 | ```php
98 | Connections:add('default', [ // 'default' is the name of the connection
99 | 'development' => [
100 | 'type' => 'MongoDb',
101 | 'host' => 'localhost',
102 | 'database' => 'myapp'
103 | ],
104 | 'production' => [
105 | 'type' => 'MongoDb',
106 | 'host' => 'flame.mongohq.com:27111',
107 | 'database' => 'myapp',
108 | 'login' => 'myuser',
109 | 'password' => 'mysecret'
110 | ]
111 | ]);
112 | ```
113 |
--------------------------------------------------------------------------------
/05_models/02_data_mutation.md:
--------------------------------------------------------------------------------
1 | # Data Mutation
2 |
3 | ## Create
4 |
5 | The `create()` method instantiates a new record or document object, initialized with any data passed in.
6 |
7 | ```php
8 | $post = Posts::create(['title' => 'New post']);
9 | echo $post->title; // echoes 'New post'
10 | ```
11 |
12 |
13 | While this method creates a new object, there is no effect
14 | on the database until the save() method is called.
15 |
16 |
17 | This method can also be used to simulate loading a pre-existing object from the database, without actually querying the database:
18 |
19 | ```php
20 | $post = Posts::create(['id' => $id, 'moreData' => 'foo'], ['exists' => true]);
21 | $post->title = 'New title';
22 | $success = $post->save();
23 | ```
24 |
25 | This will create an update query against the object with an ID matching `$id`. Also note that only the `title` field will be updated.
26 |
27 | ## Update
28 |
29 | The `update()` method allows you to update multiple records or documents with the given data, restricted by the given set of criteria (optional).
30 |
31 | The `$data` parameter is typically an array of key/value pairs that specify the new data with which the records will be updated. For SQL databases, this can optionally be an SQL fragment representing the `SET` clause of an `UPDATE` query. The `$conditions` parameter is an array of key/value pairs representing the scope of the records to be updated. The `$options` parameter specifies any database-specific options to use when performing the operation. The options parameter varies amongst the various types of data sources. More detail is available in the source code documentation for the `update()` methods of each data source type (Example: `\lithium\data\source\Database.php`).
32 |
33 | ```
34 | // Change the author for all documents.
35 | Posts::update(['author' => 'Michael']);
36 |
37 | // Set a default title for all empty titles
38 | Posts::update(['title' => 'Untitled'], ['title' => '']);
39 | ```
40 |
41 | ## Delete
42 |
43 | Deleting entities from your data source is accomplished using either the `delete()`
44 | entity method or the static `remove()` method.
45 |
46 | Ususally you should first retrieve the entity to-be-deleted, probably do some safety check
47 | and then delete it.
48 |
49 | ```php
50 | $post = Posts::first(); // Read the first post.
51 | $post->delete(); // Delete first post.
52 | ```
53 |
54 | To delete multiple records you first retrieve a set of entities
55 | then invoke the delete method on all of them. As the result
56 | of the find call here is actually a `Collection` any method
57 | called on the collection will be dispatched to each contained entity.
58 |
59 |
60 | ```php
61 | // Select all drafted posts, $posts is a Collection.
62 | $posts = Posts::find('all', [
63 | 'conditions' => ['is_draft' => true]
64 | ]);
65 |
66 | // Iterates over each post in collection and deletes it.
67 | $posts->delete();
68 |
69 | // ... is the same as:
70 | foreach ($posts as $post) {
71 | $post->delete();
72 | }
73 | ```
74 |
75 | As an alternative to quickly remove massive sets of entities `remove()` together with
76 | conditions can be used. The `$conditions` parameter is first and is an array of key/value
77 | pairs representing the scope of the records or documents to be deleted.
78 |
79 | ```php
80 | // Delete all posts!!
81 | $success = Posts::remove();
82 |
83 | // Delete drafted posts only.
84 | Posts::remove(['is_draft' => true]);
85 | ```
86 |
87 |
88 | Using the remove() method with no $conditions
89 | parameter specified will delete all entities in your data source.
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/05_models/03_saving.md:
--------------------------------------------------------------------------------
1 | # Saving Entities
2 |
3 | Persisting data means that new data is being stored or updated. Before you can save your data, you have to initialize a `Model`. This can either be done with the `find()` methods shown earlier or—if you want to create a new entity with the static `create()` method. `save()` is a method (called on record and document objects) to create or update the record or document in the database that corresponds to `$entity`.
4 |
5 | ```php
6 | // Create a new post, add title and author, then save.
7 | $post = Posts::create();
8 | $post->title = 'My first blog post.';
9 | $post->author = 'Michael';
10 | $post->save();
11 |
12 | // Same as above.
13 | $post = Posts::create([
14 | 'title' => 'My first blog post.',
15 | 'author' => 'Michael'
16 | ]);
17 | $post->save();
18 |
19 | // Find the first blog post and change the author
20 | $post = Posts::first();
21 | $post->author = 'John';
22 | $post->save();
23 | ```
24 |
25 | Note that `save()` also validates your data if you have any validation rules defined in your model. It returns either `true` or `false`, depending on the success of the validation and saving process. You'll normally use this in your controller like so:
26 |
27 | ```php
28 | public function add() {
29 | $post = Posts::create();
30 |
31 | if($this->request->data && $post->save($this->request->data)) {
32 | $this->redirect('Posts::index');
33 | }
34 | return compact('post');
35 | }
36 | ```
37 | This redirects the user only to the `index` action if the saving process was successful. If not, the form is rendered again and errors are shown in the view.
38 |
39 | To override the validation checks and save anyway, you can pass the `'validate'` option:
40 |
41 | ```php
42 | $post->title = "We Don't Need No Stinkin' Validation";
43 | $post->body = "I know what I'm doing.";
44 | $post->save(null, ['validate' => false]);
45 | ```
46 |
47 |
48 | There is more information about validation and how to use the
49 | validates() in the Validation chapter.
50 |
51 |
52 | The `$entity` parameter is the record or document object to be saved in the database. This parameter is implicit and should not be passed under normal circumstances. In the above example, the call to `save()` on the `$post` object is transparently proxied through to the `Posts` model class, and `$post` is passed in as the `$entity` parameter. The `$data` parameter is for any data that should be assigned to the record before it is saved.
53 |
54 | There is also an `$options` parameter that has the following settable elements.
55 |
56 | * `'callbacks'` _boolean_: If `false`, all callbacks will be disabled before executing. Defaults to `true`.
57 | * `'validate'` _mixed_: If `false`, validation will be skipped, and the record will be immediately saved. Defaults to `true`. May also be specified as an array, in which case it will replace the default validation rules specified in the `$validates` property of the model.
58 | * `'events'` _mixed_: A string or array defining one or more validation _events_. Events are different contexts in which data events can occur, and correspond to the optional `'on'` key in validation rules. They will be passed to the validates() method if `'validate'` is not `false`.
59 | * `'whitelist'` _array_: An array of fields that are allowed to be saved to this record.
60 |
61 |
62 |
--------------------------------------------------------------------------------
/05_models/04_validation.md:
--------------------------------------------------------------------------------
1 | # Validation
2 |
3 | This section focuses on validation in the model layer, as well as covering the core utility class `Validator`.
4 |
5 | Part of a model's responsibility in an MVC application is to be the gatekeeper for the data it handles. While a great deal of a model's functionality deals with fetching and relating data, validation is an important part in describing the business logic and keeping your data clean and secure.
6 |
7 | ## Basic Validation
8 |
9 | Validation is done by defining a special property on the model object called `$validates`. When calling `validates()` on an entity the framework inspects this property and validates data to be saved against this set of rules.
10 |
11 | Because it's likely the most common case, we'll start with a simple model data validation example based around an HTML form. Let's consider an application that handles user registration. We'll define a `Users` model and create some simple validation logic in order to make sure submitted user information is complete and in the correct form.
12 |
13 | ### Defining Validation Rules
14 |
15 | Let's create a simple `Users` model, and begin with the definition of a few simple validation rules.
16 |
17 | ```php
18 | namespace app\models;
19 |
20 | class Users extends lithium\data\Model {
21 |
22 | public $validates = [
23 | 'name' => [
24 | [
25 | 'notEmpty',
26 | 'required' => true,
27 | 'message' => 'Please supply a name.'
28 | ]
29 | ],
30 | 'password' => [
31 | [
32 | 'notEmpty',
33 | 'required' => true,
34 | 'message' => 'Please supply a password.'
35 | ]
36 | ]
37 | ];
38 | }
39 | ```
40 |
41 |
42 | Note that these are application-level validation rules, and do not interact with any rules or constraints defined in your data source. If such constraints fail, an exception will be thrown by the database layer. Validation checks run only against the rules defined in application code.
43 |
44 |
45 | This initial example shows how rules are defined. First, each set of rules is keyed by the model field name. Each field is then assigned an array of rules, where the first value is the name of the rule, and the subsequent keys define additional information about each rule.
46 |
47 | In addition to the rule name, there are a number of special keys you can use to modify a rule:
48 |
49 | - `message`: The error message displayed if this rule fails.
50 | - `required` (boolean): Specifies that data for this field _must_ be submitted in order to validate. Defaults to `true`.
51 | - `skipEmpty` (boolean): Causes the rule to be skipped if the value is null or empty. Defaults to `false`.
52 | - `format`: The name of the rule format required to validate the data, or `any` or `all`.
53 |
54 | You may also declare multiple rules per field. Here, we add two additional rules to the name field in order to ensure names are alphanumeric and have a minimum and maximum length.
55 |
56 | ```php
57 | namespace app\models;
58 |
59 | class Users extends lithium\data\Model {
60 |
61 | public $validates = [
62 | 'name' => [
63 | [
64 | 'notEmpty',
65 | 'message' => 'Please supply a name.'
66 | ],
67 | [
68 | 'alphaNumeric',
69 | 'message' => 'A name may only contain letters and numbers.'
70 | ],
71 | [
72 | 'lengthBetween', 'min' => 7, 'max' => 23,
73 | 'message' => 'Must be between 7 and 23 characters long.'
74 | ]
75 | ],
76 | 'password' => [
77 | [
78 | 'notEmpty',
79 | 'message' => 'Please supply a password.'
80 | ]
81 | ]
82 | ];
83 | }
84 | ```
85 |
86 |
87 | For a complete list of built-in validation rules, see the Validator API.
88 |
89 |
90 | ### Form Validation
91 |
92 | Here's a simple form we might use to collect user data. This would be contained in `app/views/users/add.html.php`.
93 |
94 | ```
95 | = $this->form->create($user) ?>
96 | = $this->form->field('name') ?>
97 | = $this->form->field('password') ?>
98 | = $this->form->submit('save user') ?>
99 | = $this->form->end() ?>
100 | ```
101 |
102 | By using the `Form` helper and binding our user entity to it, we gain the additional benefit that later validation errors are handled and - should some exist - displayed automatically for us.
103 |
104 | ### Handling Validation in the Controller
105 |
106 | Once that data is submitted through the form to the controller, we handle it in our controller action.
107 |
108 | ```php
109 | namespace app\controllers;
110 |
111 | use app\models\Users;
112 |
113 | class UsersController extends lithium\action\Controller {
114 |
115 | public function add() {
116 | if ($this->request->data) {
117 | $user = Users::create($this->request->data);
118 |
119 | if ($user->save()) {
120 | $this->redirect('/users/home');
121 | }
122 | }
123 | }
124 | }
125 | ```
126 |
127 | If there's submitted data to this action, we create a new user with it. If a save is successful, we redirect to a landing page. The **implicit logic** here is that validation rules are checked, and `save()` returns a `false` if any rules fail.
128 |
129 | Since the action's view contains the HTML form we just built using the `Form` helper, if there are any validation problems, the HTML form is rendered again, this time with the errors displayed inline.
130 |
131 | ## Advanced Validation
132 |
133 | ### Explicit Validation
134 |
135 | As shown in our example above `save()` will implictly validate the data prior saving. If you'd like
136 | to validate data explictly you'd use the entities `validate()` method. That method takes 2 parameters.
137 |
138 | The `$entity` parameter specifies the model entity to validate, which is typically either a `Record` or `Document` object. In the example below, the `$entity` parameter is equal to the `$post` object instance.
139 |
140 | The ``$options` parameter has the following settable elements:
141 |
142 | * `'rules'` _array_: If specified, this array will _replace_ the default validation rules defined in `$validates`.
143 | * `'events'` _mixed_: A string or array defining one or more validation _events_. Events are different contexts in which data events can occur, and correspond to the optional `'on'` key in validation rules. For example, by default, `'events'` is set to either `'create'` or `'update'`, depending on whether `$entity` already exists. Then, individual rules can specify `'on' => 'create'` or `'on' => 'update'` to only be applied at certain times. Using this parameter, you can set up custom events in your rules as well, such as `'on' => 'login'`. Note that when defining validation rules, the `'on'` key can also be an array of multiple events.
144 |
145 | ```
146 | $post = Posts::create($data);
147 |
148 | if ($success = $post->validates()) {
149 | // ...
150 | $post->save(null, ['validate' => false]);
151 | }
152 | ```
153 |
154 | This method uses the `Validator` class to perform data validation. An array representation of the entity object to be tested is passed to the `check()` method, along with the model's validation rules. Any rules defined in the `Validator` class can be used to validate fields.
155 |
156 |
157 | When validation fails any validation errors get attached to the entity.
158 |
159 |
160 |
161 | ### Accessing Validation Errors
162 |
163 | If you need to know about model validation problems before the application renders the view, you can inspect errors on the `Entity` object you're working with. Get the last errors by calling `errors()`:
164 |
165 | ```php
166 | // ...
167 | public function add() {
168 | if ($this->request->data) {
169 | $user = Users::create($this->request->data);
170 | $user->save();
171 | $errors = $user->errors();
172 |
173 | // Redirect or perform other logic here
174 | // based on contents of $errors.
175 | }
176 | }
177 | // ...
178 | ```
179 |
180 | ### Invalidating Manually
181 |
182 | You can also manually invalidate model fields by calling `errors()` and supplying key/value pairs to denote problems. While you'll usually want to create custom rules (covered next), this is sometimes a helpful trick to use in model filters, etc.
183 |
184 | ```php
185 | $user->errors('name', 'This name is too funny.');
186 | ```
187 |
188 | ### Creating Custom Rules
189 |
190 | While the validator features a number of handy rules, you'll inevitably want to create your own validation rules. This done (at runtime) by calling `Validator::add()` to specify new rule logic.
191 |
192 | The simplest form of rule addition is by Regular Expression:
193 |
194 | ```php
195 | Validator::add('zeroToNine', '/^[0-9]$/');
196 | ```
197 |
198 | If you need more than pattern recognition, you can also supply rules as anonymous functions:
199 |
200 | ```php
201 | Validator::add('nameTaken' function($value) {
202 | $result = Users::findByName($value);
203 | return count($result) > 0;
204 | });
205 | ```
206 |
207 | Once a rule as been defined, it can be referenced by name in a model `$validates` property or direct use of the `Validator` class magic methods:
208 |
209 | ```php
210 | Validator::add('zeroToNine', '/^[0-9]$/');
211 | Validator::isZeroToNine('7');
212 | ```
213 |
214 | ### Named Validation Rules
215 |
216 |
This feature is available with version 1.1.
217 |
218 | Validation rules inside the model can be optionally defined as _named_ rules. This changes the format
219 | of the data `Model::errors()` returns and allows you to determine which exact rule failed.
220 |
221 | ```php
222 | namespace app\models;
223 |
224 | class Users extends lithium\data\Model {
225 |
226 | public $validates = [
227 | 'name' => [
228 | 'foo' => [
229 | 'notEmpty'
230 | 'message' => 'Must not be empty.'
231 | // ...
232 | ],
233 | 'bar' => [
234 | // ...
235 | ]
236 | ]
237 | ];
238 | }
239 | ```
240 |
241 | ```php
242 | $user->validate();
243 | $user->errors(); // Returns:
244 | // [
245 | // 'name' => [
246 | // 'foo' => 'Must not be empty'
247 | // ]
248 | // ]
249 | ```
250 |
251 | ### Providing Custom Validation Messages in the Template
252 |
253 |
This feature is available with version 1.1.
254 |
255 | In order to make use of this feature, you must use _named validation rules_.
256 |
257 | This allows easier translation of messages and customization in case there is no
258 | control over the model (i.e. developing a "theme" for a customer without touching
259 | the data layer).
260 |
261 | Also some might prefer to provide messages in the template, as one could argue
262 | they are part of the presenation layer.
263 |
264 | ```php
265 | $this->form->field('name', [
266 | 'error' => [
267 | 'foo' => 'Please please do not leave empty :]'
268 | ]
269 | ));
270 | ```
271 |
272 | Once a `'default'` key is set for the custom messages, it'll be used for any unmatched
273 | validation errors.
274 |
275 | ```php
276 | $this->form->field('name', [
277 | 'error' => [
278 | 'default' => 'Something is wrong in this field.'
279 | 'foo' => 'Please please do not leave empty :]'
280 | ]
281 | ));
282 | ```
283 |
284 |
285 | When there's no default message given, messages defined in the model
286 | may be rendered.
287 |
288 |
--------------------------------------------------------------------------------
/05_models/05_querying.md:
--------------------------------------------------------------------------------
1 | # Querying
2 |
3 | Reading data from your database (or any datasource available) is a common
4 | tasks that you will perform. All query-related operations may be done through
5 | the static `find()` method, along with some additional utility methods provided
6 | for convenience.
7 |
8 | ```php
9 | // Read all posts.
10 | $posts = Posts::find('all');
11 |
12 | // Read the first post.
13 | $post = Posts::find('first');
14 |
15 | // Read all posts with the newest ones first.
16 | $posts = Posts::find('all', [
17 | 'order' => ['created' => 'DESC']
18 | ]);
19 |
20 | // Read only the title of the newest post.
21 | $post = Posts::find('first', [
22 | 'fields' => ['title'],
23 | 'order' => ['created' => 'DESC']
24 | ]);
25 |
26 | // Read only posts where the author name is "michael".
27 | $posts = Posts::find('all', [
28 | 'conditions' => ['author' => 'michael']
29 | ]);
30 | ```
31 |
32 |
33 | The framework protects against injection attacks by quoting
34 | condition values. Other options i.e. 'fields' are
35 | however not automatically quoted. Read more about the topic
36 | and potential countermeasures in the Security chapter.
37 |
38 |
39 | ## Built-in Finders
40 |
41 | `find()` utilizes _finders_ to do most of its work. The first parameter to `find()`
42 | selects the type of finder you want to use. The second parameter provides options to the
43 | finder.
44 |
45 | There are four built-in finders. The most commonly used ones are `'all'` and `'first'`.
46 |
47 |
48 | You can also create your own custom finders via `finder()`.
49 |
50 |
51 | - `'all'`: Returns a collection of records matching the conditions.
52 | - `'first'`: Returns the first record matching the conditions.
53 | - `'count'`: Returns an integer count of all records matching the conditions.
54 | - `'list'`: Returns a one dimensional array, where the key is the primary
55 | key and the value the title of the record (a `'title'` field is required
56 | for this). A result may look like: `array(1 => 'Foo', 2 => 'Bar')`.
57 |
58 | All bultin-in finders take the same set of options to control which and
59 | how many records they return.
60 |
61 | ## Using Conditions
62 |
63 | The `'conditions'` option allows you to provide conditions for the query
64 | i.e. `'array('is_published' => true)`. The following example finds all
65 | posts author name is `michael`.
66 |
67 | ```php
68 | $posts = Posts::find('all', [
69 | 'conditions' => ['author' => 'michael']
70 | ]);
71 | ```
72 |
73 | By default the conditions are AND'ed together so the following
74 | example would require each post to be published **and** authored
75 | by `michael`.
76 |
77 | ```php
78 | $posts = Posts::find('all', [
79 | 'conditions' => [
80 | 'author' => 'michael',
81 | 'is_published' => true
82 | ]
83 | ]);
84 | ```
85 |
86 | To OR conditions together use the following syntax.
87 |
88 | ```php
89 | $posts = Posts::find('all', [
90 | 'conditions' => [
91 | 'or' => [
92 | 'author' => 'michael',
93 | 'is_published' => true
94 | ]
95 | ]
96 | ]);
97 | ```
98 |
99 | To find all records from a set of authors use the array
100 | syntax.
101 |
102 | ```php
103 | $posts = Posts::find('all', [
104 | 'conditions' => [
105 | 'author' => ['michael', 'nate']
106 | ]
107 | ]);
108 | ```
109 |
110 | ## Ordering
111 |
112 | The `'order'` option allows to control the order in which the data will be returned, i.e.
113 | `'created ASC'` sorts by created date in ascending order, `'created DESC'` sorts
114 | the same field in descending order. To sort by multiple fields use the array
115 | syntax `array('title' => 'ASC', 'id' => 'ASC)`.
116 |
117 | The following are equal groups:
118 | ```php
119 | Posts::find('all', ['order' => 'title']);
120 | Posts::find('all', ['order' => 'title ASC']);
121 | Posts::find('all', ['order' => ['title']]);
122 | Posts::find('all', ['order' => ['title' => 'ASC']]);
123 |
124 | Posts::find('all', ['order' => ['title', 'id']]);
125 | Posts::find('all', ['order' => ['title' => 'ASC', 'id' => 'ASC']]);
126 | ```
127 |
128 | ## Restricting Returned Fields
129 |
130 | The `'fields'` option allows to specify the fields that should be retrieved. By default
131 | all fields are retrieved. To optimize query performance, limit to just the ones actually
132 | needed.
133 |
134 | ```php
135 | Posts::find('all', [
136 | 'fields' => ['title', 'author']
137 | ]);
138 | ```
139 |
140 | ## Limiting Number of Records
141 |
142 | The `'limit'` option allows to limit the set of returned records to a maximum number.
143 |
144 | ```php
145 | Posts::find('all', [
146 | 'limit' => 10
147 | ]);
148 | ```
149 |
150 | ## Pagination
151 |
152 | The `'page'` option allows to paginate data sets. It specifies the page of the set
153 | together with the limit option specifying the number of records per page. The first
154 | page starts at `1`.
155 |
156 | ```php
157 | $page1 = Posts::find('all', [
158 | 'page' => 1,
159 | 'limit' => 10
160 | ]);
161 | $page2 = Posts::find('all', [
162 | 'page' => 2,
163 | 'limit' => 10
164 | ]);
165 | ```
166 |
167 | ## Shorthands
168 |
169 | li3 also provides some additional basic methods around the `find()` method which
170 | make your code less verbose and easier to read:
171 |
172 | ```php
173 | // Shorthand for find first by primary key.
174 | Posts::find(23);
175 |
176 | Posts::all();
177 | ```
178 |
179 |
180 | Note list is a keyword in PHP and so cannot be called magically like other finders.
181 |
182 |
183 | The example below shows two different approaches to finding all the posts related to the
184 | username "Michael". The first bare approach shows how to use `find()` directly. The second
185 | example uses camelCase convention to tell li3 to filter by a specific field name and
186 | value.
187 |
188 | ```php
189 | $posts = Posts::findAllByUsername('michael');
190 | // ... is functionally equal to ...
191 | $posts = Posts::find('all', [
192 | 'conditions' => ['username' => 'michael']
193 | ]);
194 | ```
195 |
196 | ## Custom Finders
197 |
198 | The basic finders are nice, but the framework also provides you with a set of highly
199 | dynamic methods that match against your dataset. It allows you to build
200 | custom _finders_ to extend functionality.
201 |
202 | As you use your models, you might start to wish for a shortcut. For example,
203 | instead of having to do this repeatedly:
204 |
205 | ```php
206 | $recentComments = Comments::find('all', [
207 | 'conditions' => [
208 | 'created' => [
209 | '>=' => date('Y-m-d H:i:s', time() - (86400 * 3))
210 | ]
211 | ]
212 | ]);
213 | ```
214 |
215 | You could create a custom finder method that packages the specified conditions into a one-liner:
216 |
217 | ```php
218 | $recentComments = Comments::find('recent');
219 |
220 | // or, as a "magic" method:
221 |
222 | $recentComments = Comments::recent();
223 | ```
224 |
225 | At a basic level, this is done by utilizing the `finder()` method of the model.
226 | You call `finder()` and supply the name of the finder, along with a definition
227 | so li3 knows how to form the query. The definition in this simple case looks
228 | just like the query array we supplied to `find()` earlier:
229 |
230 | ```php
231 | Comments::finder('recent', [
232 | 'conditions' => [
233 | 'created' => [
234 | '>=' => date('Y-m-d H:i:s', time() - (86400 * 3))
235 | ]
236 | ]
237 | ]);
238 | ```
239 |
240 | Some finder implementations might require a little processing in addition to a default set
241 | of conditions. In that case, you can define a finder using a closure that will be called
242 | as part of find chaining. In this use case, you supply the name of the finder along with a
243 | closure that looks much like a filter definition:
244 |
245 |
246 | The syntax for filter and thus for finders has changed in 1.1.
247 | Below you find the new syntax. The old one receives 3 parameters
248 | (`$self`, `$params`, `$chain`).
249 |
250 |
251 | ```php
252 | Comments::finder('recentCategories', function($params, $next){
253 | // Set up default conditions
254 | $defaults = [
255 | 'created' => [
256 | '>=' => date('Y-m-d H:i:s', time() - (86400 * 3))
257 | ]
258 | ];
259 |
260 | // Merge with supplied params
261 | $params['options']['conditions'] = $defaults + (array) $params['options']['conditions'];
262 |
263 | // Do a bit of reformatting
264 | $results = [];
265 | foreach ($next($params) as $entity) {
266 | $results[] = $entity->categoryName;
267 | }
268 |
269 | // Returns an array of recent categories given the supplied query params.
270 | return $results;
271 | });
272 | ```
273 |
274 | ## Rich Returned Values
275 |
276 | The response from a model method is not just a plain array but actually an
277 | entity object (or a collection of them). This means that you can perform a
278 | variety of actions on them if you need to. Here are a few examples:
279 |
280 | ```
281 | // Find all Posts
282 | $posts = Posts::all();
283 |
284 | // Get the first and last post of the collection
285 | $first = $posts->first();
286 | $last = $posts->last();
287 |
288 | // Iterate over all posts and print out the title
289 | foreach ($posts as $post) {
290 | echo $post->title;
291 | }
292 | ```
293 |
294 | Entities can be easily converted into other formats.
295 |
296 | ```php
297 | Posts::find('all')->data();
298 | Posts::find('all')->to('array');
299 | ```
300 |
301 | ## Default Query Options
302 |
303 | In cases where you always want finders results constrained to i.e. certain conditions,
304 | default query options can be used. Default options may be defined by using the `query()`
305 | method or alternatively by defining the `$_query` property on the model class.
306 |
307 | Specific query options overwrite default ones. As both are merged by simply using the `+`
308 | operator for arrays. Note that this can also be a common pitfall.
309 |
310 | ```php
311 | Posts::query([
312 | 'conditions' => ['is_published' => true],
313 | 'limit' => 4
314 | ]);
315 |
316 | // Will retrieve maximum of 4 results which are published.
317 | Posts::find('all');
318 |
319 | // Potential pitfall: will retrieve results published or not
320 | // for author michael. Limited to 4 results maximum.
321 | Posts::find('all', [
322 | 'conditions' => ['author' => 'michael']
323 | ]);
324 |
325 | // Will retrieve only published results for author michael.
326 | // Limited to 4 results.
327 | Posts::find('all', [
328 | 'conditions' => ['author' => 'michael', 'is_published' => true]
329 | ]);
330 | ```
331 |
332 |
--------------------------------------------------------------------------------
/05_models/06_adding_functions.md:
--------------------------------------------------------------------------------
1 | # Adding Functionality
2 |
3 | This section outlines a few of the ways you can extend model functionality as you build your application. As you think about different pieces of functionality to add, please keep in mind that there are two main ways to add functionality to your models:
4 |
5 | 1. Static methods that apply to the Model
6 | 2. Instance methods that apply to each Entity (Document or Record)
7 |
8 | ## Static vs Instance Methods
9 |
10 | As a rule of thumb, all static methods work with a collection of objects on the database, while non-static methods are tied to one single document/record. Consider the following example:
11 |
12 | ```
13 | $post = Posts::first(['conditions' => ['author' => 'foo']]);
14 | $post->title = 'Hello World';
15 | $post->save();
16 | ```
17 |
18 | The `first()` method is static because it iterates over a bunch of entities in your database and returns the first entry where the `author` equals `foobar`. The result you get is an instance of a `Post` so all subsequent methods on it are _non-static_. The second line in the example sets the `title` and the third line saves it back to the database. This concept feels natural and also has the benefit of instantly knowing on what kind of dataset you're operating.
19 |
20 | If you remember this simple rule, you'll understand how the framework reacts to new functions placed in models—and you'll be able to use them more effectively.
21 |
22 | ## Static Data Access
23 |
24 | One simple example is if you have a bit of data that is specific to a model's domain, and you want to make that data available to controllers using those models.
25 |
26 | ```php
27 | namespace app\models;
28 |
29 | class Users extends \lithium\data\Model {
30 |
31 | protected static $_roles = ['admin', 'friend', 'stranger'];
32 |
33 | public static function roles() {
34 | return static::$_roles;
35 | }
36 | }
37 | ```
38 |
39 | Because this method is accessed statically, it behaves as you'd expect:
40 |
41 | ```php
42 | if (!in_array('admin', Users::roles())) {
43 | return false;
44 | }
45 | ```
46 |
47 | ## Model Instance Methods
48 |
49 | It's often useful to add a method to a model so that you can easily transform data once you've got a model instance (of type `Entity`, either `Document` or `Record`) in your controllers and views. In this case, you'll need to create a method that accepts the entity itself as the first argument, then any addition parameters (if any). Here's a simple use case of creating a method on a `Users` model that formats a full name based on first and last names:
50 |
51 | ```php
52 | namespace app\models;
53 |
54 | class Users extends \lithium\data\Model {
55 |
56 | public function fullName($entity) {
57 | return $entity->firstName . ' ' . $entity->middleInitial . '. ' . $entity->lastName;
58 | }
59 | }
60 | ```
61 |
62 | If you want to add additional parameters, do so after you've specified the entity as the first:
63 |
64 | ```php
65 | namespace app\models;
66 |
67 | class Users extends \lithium\data\Model {
68 |
69 | public function fullName($entity, $suffix = false) {
70 | $name = $entity->firstName . ' ' . $entity->middleInitial . '. ' . $entity->lastName;
71 |
72 | if ($suffix) {
73 | $name .= ' ' . $entity->suffix;
74 | }
75 | return $name;
76 | }
77 | }
78 | ```
79 |
80 | Once this is done, use it wherever you've got access to a `Users` model instance:
81 |
82 | ```php
83 | $firstUser = Users::first();
84 |
85 | $firstUser->fullName(); // "Bill S. Preston"
86 | $firstUser->fullName(true); // "Bill S. Preston Esq."
87 | ```
88 |
89 |
--------------------------------------------------------------------------------
/05_models/07_relationships.md:
--------------------------------------------------------------------------------
1 | # Model Relationships
2 |
3 | The data that applications manipulate is usually structured in some way. Objects have links to other objects, and the model layer that represents that data should reflect the structure inherent in the data it represents and interacts with.
4 |
5 | li3's data layer offers a way to facilitate data relationships and structure. This guide is meant to show you how to specify and define these data relationships and use them to your advantage as you build your application.
6 |
7 | ## Defining Model Relationships
8 |
9 | Before you define a model relationship in your code, it's important to understand the terminology that describes a relationship between two objects in your system. In li3 (and elsewhere), a specific set of terms are used to describe model relationships:
10 |
11 | * _hasOne_: the current object is linked to a single object of another type
12 | * _hasMany_: the current object is linked to many objects of another type
13 | * _belongsTo_: the current object is owned and marked as related to another object
14 |
15 |
16 | If you're having a hard time remembering hasOne/hasMany versus belongsTo, just
17 | remember this: if the current model contains some sort of marker (like a foreign key), it
18 | belongsTo another model.
19 |
20 |
21 | Defining this object relationship is simple: you populate special properties on the model object. For example, let's say we're building an online store. Each `Category` is filled with many `Product` objects. In this case, we'd want to specify `Category` hasMany `Product`. Let's see how this is done:
22 |
23 | ```php
24 | class Categories extends \lithium\data\Model {
25 |
26 | public $hasMany = ['Products'];
27 | }
28 | ```
29 |
30 | This simple declaration relies on convention, and is the functional equivalent to this:
31 |
32 | ```php
33 | class Categories extends \lithium\data\Model {
34 |
35 | public $hasMany = ['Products' => [
36 | 'to' => 'Products',
37 | 'key' => 'category_id',
38 | 'constraints' => [],
39 | 'fields' => [],
40 | 'order' => null,
41 | 'limit' => null
42 | ]];
43 | }
44 | ```
45 |
46 | Unless specified otherwise, the relationship assumes you're using the exact class name specified, with a key that is an under_scored version of the model's class name, suffixed with `_id`. All other sorting and limit options are assumed to be empty.
47 |
48 | All of the model relationships use these same keys (although there's no reason to order or limit hasOne or belongsTo) and can be configured likewise.
49 |
50 | ## Reading Related Data
51 |
52 | Once a relationship as been configured, you can use your models to fetch related data. We can now do this in a controller:
53 |
54 | ```php
55 | $categories = Categories::find('all', [
56 | 'with' => 'Products'
57 | ]);
58 |
59 | print_r($categories->to('array'));
60 | /* outputs:
61 | Array
62 | (
63 | [id] => 1
64 | [name] => Audio
65 | [products] => Array
66 | (
67 | [0] => Array
68 | (
69 | [id] => 1
70 | [category_id] => 1
71 | [name] => Headphones
72 | [price] => 21.99
73 | )
74 | [1] => Array
75 | (
76 | [id] => 2
77 | [category_id] => 1
78 | [name] => Desk Speakers
79 | [price] => 39.95
80 | )
81 | [2] => Array
82 | (
83 | [id] => 3
84 | [category_id] => 1
85 | [name] => Portable Radio
86 | [price] => 9.95
87 | )
88 | )
89 | )
90 | */
91 | ```
92 |
93 | Notice the new `with` key supplied to the model? This tells the model that you want related data joined to the normal result.
94 |
95 | As you can see from the output, the related data has been added on to the model result. While we're printing out array contents here, you can as easily loop through or access the same information at `$categories->products` in this case as well.
96 |
97 | ## Ordering Related Data
98 |
99 | Ordering data works mostly as you'd expect it to. The following returns
100 | all categories and their associated products first unsorted and than sorted
101 | by the category title.
102 |
103 | Good practice is to qualify fields (with the model name) in such queries to make the
104 | statement unambigous.
105 |
106 | ```php
107 | Categories::find('all', [
108 | 'with' => 'Products'
109 | ]);
110 |
111 | Categories::find('all', [
112 | 'with' => 'Products',
113 | 'order' => ['Categories.title']
114 | ]);
115 | ```
116 |
117 | To have the nested products themselves sorted inside the `->products`, you
118 | add the qualified relationship field to the order statement.
119 |
120 | ```php
121 | Categories::find('all', [
122 | 'order' => ['Categories.id', 'Products.price'],
123 | 'with' => 'Products'
124 | ]);
125 | ```
126 |
127 | Did you note that we also added `Categories.id` in there? This is to keep
128 | a consistent result set.
129 |
130 |
131 | Sorting the whole result set just by the products
132 | price alone is not possible.
133 |
134 |
135 | If you see an exception thrown (`Associated records hydrated out of order.`), than
136 | simply order by the main models primary key first. Other frameworks will magically
137 | rewrite the query for you and add that primary key automatically. li3 however has
138 | decided against this, as it doesn't want to mess with your queries.
139 |
140 | ## Saving Related Data
141 |
142 | Because relationship setup is simple, so is saving related data. When saving related data, just make sure the proper key values are set so that the underlying data storage engine can match up the data correctly.
143 | Here's a simplified example of how we'd save a newly created product, matched up to a category. First, the `ProductsController`:
144 |
145 | ```php
146 | namespace app\controllers;
147 |
148 | use \app\models\Products;
149 | use \app\models\Categories;
150 |
151 | class ProductsController extends \lithium\action\Controller {
152 |
153 | public function create() {
154 | // If form data has been submitted, create a new Product.
155 | if ($this->request->data) {
156 | Products::create($this->request->data)->save();
157 | }
158 |
159 | // Create a list of categories to send to the view.
160 | $categories = Categories::find('all');
161 | $categoryList = [];
162 | foreach ($categories as $category) {
163 | $categoryList[$category->id] = $category->name;
164 | }
165 |
166 | $this->set(compact('categoryList'));
167 | }
168 | }
169 | ```
170 |
171 | And, here is the view that contains the form:
172 |
173 | ```
174 | = $this->form->create() ?>
175 | = $this->form->select('category_id', $categoryList) ?>
176 | = $this->form->text('name') ?>
177 | = $this->form->text('price') ?>
178 | = $this->form->submit('Create Product') ?>
179 | = $this->form->end() ?>
180 | ```
181 |
--------------------------------------------------------------------------------
/05_models/08_meta.md:
--------------------------------------------------------------------------------
1 | # Model Meta Information
2 |
3 | Each model needs some meta-information to initialize. By default you don't have to define this
4 | information yourself. The model class is _smart_ enough to figure all of the configuration options
5 | out on its own - as long as you follow the frameworks conventions.
6 |
7 | However changing the meta-information may be desired in such cases where you are creating a model which needs to access a table which doesn't follow our conventions.
8 |
9 | ## Defining Meta Information
10 |
11 | The following lists all meta-information options that can be changed:
12 |
13 | * **name**: Name of the model, defaults to the class name.
14 | * **title**: The field that is most closely associated with being the title of each record. Defaults to `title` or `name` if either of those fields are available.
15 | * **key**: The field that acts as the primary key or identifier for a record. Defaults to `id` if that field is available. The key can also be set using the `key()` method.
16 | * **source**: The table name or document collection being accessed. The default is an all lowercase version of the class name with underscores as spaces. Class names like `BlogPost` and `BlogPosts` would default to the `blog_posts` table.
17 | * **connection**: the identifier of the data connection to be used, as defined in the `\app\bootstrap\database.php` file. Defaults to `default`.
18 |
19 | You may either define the `$_meta` property on the model class or call the model's `meta()` method to
20 | change meta-information. All options will be merged with the defaults.
21 |
22 | ```php
23 | class Posts extends \lithium\data\Model {
24 |
25 | protected $_meta = [
26 | 'connection' => 'legacy',
27 | 'source' => 'tblPost',
28 | 'key' => 'post_id'
29 | ];
30 | }
31 |
32 | // ... or ...
33 |
34 | Posts::meta('key', 'post_id');
35 | ```
36 |
37 | ## Retrieving Meta Information
38 |
39 | ```php
40 | Posts::meta('source');
41 | Posts::meta('key');
42 | // an alternative for the key is:
43 | Posts::key();
44 | ```
45 |
46 | ## Verifying Model Fields
47 |
48 | The `hasField()` method checks to see if a particular field exists in a model's schema. This method can check a single field, or return the first field found in an array of multiple options. The parameter is `$field`, a single field (string) or list of fields (array) to check the existence of.
49 |
50 |
--------------------------------------------------------------------------------
/05_models/10_using_data_sources.md:
--------------------------------------------------------------------------------
1 | # Using Data Sources
2 |
3 | ## Introduction
4 |
5 | Before we dive into the next section about models, it's important to understand how a model manipulates it's underlying data. In li3, this is done through a collection of classes called data sources.
6 |
7 | While models expose a common API for your application to manipulate data, the purpose of a data source is to manage the direct connection details for a specific data storage medium. Apart from providing a common way to read and write, models use data sources to connect to, disconnect from, and describe their underlying services.
8 |
9 | ## Core Data Sources
10 |
11 | li3 provides a number of data sources your models can utilize. The list is growing, but currently includes:
12 |
13 | * MySQL
14 | * SQLite3
15 | * CouchDB
16 | * MongoDB
17 |
18 | ## Data Sources and Models
19 |
20 | Most of the time, using a data source is an automatic part of setting up a connection to your data store. This is usually done in the `config/bootstrap/connections.php` bootstrap file:
21 |
22 | ```php
23 | use lithium\data\Connections;
24 |
25 | Connections::add('default', [
26 | 'type' => 'MongoDb',
27 | 'host' => 'localhost',
28 | 'database' => 'my_lithium_app'
29 | ]);
30 |
31 | Connections::add('legacy', [
32 | 'type' => 'database',
33 | 'adapter' => 'MySql',
34 | 'host' => 'localhost',
35 | 'login' => 'root',
36 | 'password' => '53Cre7',
37 | 'database' => 'my_older_lithium_app'
38 | ]);
39 | ```
40 |
41 | The `Connections` class handles adapter classes efficiently by only loading adapter classes and creating instances when they are requested (using `Connections::get()`).
42 |
43 | Adapters are usually subclasses of `lithium\data\Source`. Once these connections have been made, the data source is available to your models. Since one of the connections defined above is named `default`, newly created models will automatically use that connection and its related data source adapter.
44 |
45 |
--------------------------------------------------------------------------------
/05_models/README.md:
--------------------------------------------------------------------------------
1 | # Models
2 |
3 | Models play a key role in nearly every web application. Their main purpose is to abstract business logic and database operations from higher levels (controllers and views). They also act as a gatekeeper and—if properly implemented—make sure that only valid and allowed data gets passed through them. Models in li3 have three main purposes:
4 |
5 | 1. Provide an abstraction layer to the underlying data source(s)
6 | 2. Perform common data operations (like [fetching](querying) and [storing](saving) data)
7 | 3. Help with [validating](validation) data.
8 |
9 | Also, li3 makes it easy to extend models so that they fit your application's needs. Thanks to the nifty autoloading mechanism, models are lazy-loaded and are only initialized when you need them. In the next sections you will learn how to use models and perform common operations on them. Later sections will provide you with a more detailed look on models like [relationships](relationships) between them and how to [extend models](adding-functions-to-models) to fit your application's needs.
10 |
11 | The `Model` class is the starting point for the domain logic of your application. Models are tasked with providing meaning to otherwise raw and unprocessed data (e.g. user profile). Models expose a consistent and unified API to interact with an underlying datasource (e.g. MongoDB, CouchDB, MySQL) for operations such as querying, saving, updating and deleting data from the persistent storage.
12 |
13 |
14 | As the framework is capable of working with document oriented data sources as it is with relational databases, we use the term entity to refer to what might be considered a document in one data source type or a record/row in another type.
15 |
16 |
17 | Models allow you to interact with your data in two fundamentally different ways: [querying](querying) and [data mutation](data-mutation) (saving/updating/deleting). All query-related operations may be done through the static `find()` method, along with some additional utility methods provided for convenience. Classes extending the `Model` class should, conventionally, be named as plural, CamelCase and be placed in the `models` directory. i.e. a posts model would be `model/Posts.php`.
18 |
19 | ## Where To Go Next
20 |
21 | Read the section below on how to _create a model_ first, then continue with a quick look into the basics of creating [connections](connections), creating/updating/deleting entites in [data mutation](data-mutation), persisting entities by [saving](saving) them to the datastore. Finally [querying](querying) shows how to get all the precious data back.
22 |
23 | ## Creating a Model
24 |
25 | li3 provides you with a general-purpose class that all your models should extend. You can find the `Model` class in the `lithium\data` namespace. If you do nothing more than extend it, you instantly get a bunch of functionality that covers basic CRUD as well as more complex tasks.
26 |
27 | Let's say you want to store and manage blog posts in your database. According to our conventions, you create a new file called `Posts.php` in `app/models`. The basic structure looks like this:
28 |
29 | ```
30 | namespace app\models;
31 |
32 | class Posts extends \lithium\data\Model {}
33 | ```
34 |
35 |
36 | li3 also allows model creation via the console: You can enter li3 create model Posts into the command line (assuming you have configured the command line for use) and the code above will automatically be created in a file called \app\models\Posts.php.
37 |
38 |
39 |
--------------------------------------------------------------------------------
/06_controllers/01_actions.md:
--------------------------------------------------------------------------------
1 | # Actions
2 | li3 controllers reside inside the application's `/controllers` directory and extend the `lithium\action\Controller` core class. Let's start by creating a simple controller inside of an application. Controllers are often named after the objects they manage. This way the URL and model line up as well, and it's easy to know where certain bits of logic should live.
3 |
4 | For example, let's create a new controller UsersController. Let's create a new file in `/controllers/UsersController.php` that looks like this:
5 |
6 | ```php
7 | namespace app\controllers;
8 |
9 | class UsersController extends \lithium\action\Controller {
10 |
11 | public function index() {}
12 | }
13 | ```
14 |
15 | Each _public_ function in a controller is considered by the li3 core to be a routable action. In fact, li3's default routing rules make these actions accessible via a browser immediately (in this case /users/index).
16 |
17 | The `index()` action is a special action: if no action name is specified in the URL, li3 will try to pull up the index action instead. For example, a visitor accessing http://example.com/users/ on your application will see the results of the `index()` action. All other controller actions (unless routed otherwise) are at least accessed by the default route.
18 |
19 | For example, we can create a new controller action that would be accessible at `/users/view/`:
20 |
21 | ```php
22 | namespace app\controllers;
23 |
24 | class UsersController extends \lithium\action\Controller {
25 |
26 | public function index() {}
27 |
28 | public function view() {}
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/06_controllers/02_parameters.md:
--------------------------------------------------------------------------------
1 | # Parameters
2 |
3 | An important part of a controller's role in an application is processing incoming request data to show the correct response. In this section, we'll show a few examples that should help you to get data into your controller actions.
4 |
5 | ## GET Parameters
6 |
7 | One of the most user-friendly ways to handle incoming data is through the URL. Information passed along with a GET request is handled a number of different ways, based on your preferences.
8 |
9 | The easiest way to handle incoming GET data is by realizing that URL segments that follow the action name are mapped to controller action parameters. Here's a few examples:
10 |
11 | ```text
12 | http://example.com/users/view/1 --> UsersController::view($userId);
13 | http://example.com/posts/show/using+controllers/8384/ --> PostsController::show($title, $postId);
14 | ```
15 |
16 | GET information passed this way is also accessible via the incoming request object:
17 |
18 | ```text
19 | http://example.com/users/view/1 --> $this->request->args[0]
20 | ```
21 |
22 | While we'll always recommend using clear URL-based variables, it's important to mention that GET parameters passed as raw query string variables are also available as an attribute of the incoming request:
23 |
24 | ```text
25 | http://example.com/users/view?userId=1 --> $this->request->query['userId']
26 | ```
27 |
28 | ## POSTed Data
29 |
30 | POSTed data is also gathered from the request object. Form field data is found on the request object inside an array with keys named after the input elements generated on the referring page. For example, consider an HTML form that included these elements:
31 |
32 | ```html
33 |
34 |
35 | ```
36 |
37 | Accessing these values when submitted to a controller action is as easy as:
38 |
39 | ```php
40 | $this->request->data['title'];
41 | $this->request->data['category'];
42 | ```
43 |
--------------------------------------------------------------------------------
/06_controllers/03_routing.md:
--------------------------------------------------------------------------------
1 | # Routing HTTP Requests
2 |
3 | ## Introduction
4 |
5 | li3's routing allows developers to completely decouple the application's URLs from it's underlying structure. It works by creating a set of `Route` objects that tell li3 to respond to incoming requests, and which bits of code they relate to in your application. While this makes for great SEO and usability, it also keeps things nimble in respect to change.
6 |
7 | As such, the router has two main responsibilities. First, to determine the correct set of dispatch parameters based on an incoming request. Secondly, to generate URLs from a given set of parameters.
8 |
9 | Though this section's main focus is to show you how to create routes according your needs, we'll also cover how the router builds URLs based on parameters you supply.
10 |
11 | ## Defining Routes
12 |
13 | Defining routes is done in the application directory at `/config/routes.php`, by using the `Router::connect()` method to create `Route` objects that define URL-to-code mappings.
14 |
15 |
16 | The router will match routes in the order they are defined. In other words, the first route that matches will be returned and used for dispatching.
17 |
18 |
19 | ### Routing Definition Example
20 |
21 | Let's start with a simple example: connecting a URL with a controller method:
22 |
23 | ```php
24 | // The following lines are equivalent...
25 | Router::connect('/help', ['controller' => 'Users', 'action' => 'support']);
26 | Router::connect('/help', 'Users::support');
27 | ```
28 |
29 | If your application was hosted at http://www.example.com, requesting http://www.example.com/help would show you the rendered results of the `support()` action of `UsersController` in your application.
30 |
31 | ### Params & Regex
32 |
33 | While helpful, you'll quickly run into situations where something a bit more complex is needed. Most routes in an application include dynamic parameters that are handed to the controller. These parameters are marked in route definitions using the `{:paramname}` syntax. Consider the following example from an application that shows Basketball game rosters:
34 |
35 | ```php
36 | Router::connect('/{:controller}/{:action}/{:gameId}/{:playerId}', 'Rosters::view');
37 | ```
38 |
39 | This action forwards the users on to the `view()` method of `RostersController`, and sets the corresponding params on the request so they're available in the controller (`$this->request->params['gameId']` and `$this->request->params['playerId']`, in this case).
40 |
41 | Apart from allowing users to supply those values, you can also supply them statically in a route:
42 |
43 | ```php
44 | Router::connect('/socks', ['Products::view', 'id' => 72739]);
45 | ```
46 |
47 | In order to avoid overlapping cases and provide routing clarity, you can also specify a route parameter with an accompanying regular expression. Similarly defined routes use the `{:paramname:regex}` syntax. There are a few examples in the default `routes.php` file that ships with li3:
48 |
49 | ```php
50 | Router::connect('/{:controller}/{:action}/{:id:\d+}');
51 | ```
52 |
53 | Here, we're routing incoming requests along to their respective controllers and actions, but also tracking on a new parameter "id" if the URL ends with a numerical component. The regex here is important. If not defined, this route would also match `/products/viewCategory/electronics` if defined before another route that matches it better.
54 |
55 | ### Default Parameters
56 |
57 | There are a number of default parameters that li3 is aware of. As you build your routes, keep these routes in mind, as they're reserved for routing/dispatching purposes:
58 |
59 | - `controller` : The name of the controller to dispatch.
60 | - `action` : The name of the action to call in the dispatched controller.
61 | - `type` : Used for media type routing (covered in the Controllers guide).
62 | - `args` : Used for continuation routes.
63 |
64 | ### Continuation Routes
65 |
66 | Continuation routes are a new class of route definitions that wrap other routes. They're especially handy if you're used to using some sort of route prefixes to define a state in your application. Such uses may include:
67 |
68 | - Localization
69 | - Administrative sections of the application
70 | - API endpoints
71 |
72 | This is done by using the special `{:args}` parameter and setting the `continue` parameter to `true`. Once this is defined, you can allow later routes to match as needed. Here's a simple example to wrap your application's URLs according to locale:
73 |
74 | ```php
75 | Router::connect('/{:locale:en|de|it|jp}/{:args}', [], ['continue' => true]);
76 | ```
77 |
78 | As you can see, this route tells li3 that routes that are prefixed with 'en', 'de', 'it', or 'jp' should set an additional `locale` request parameter then be passed back to the router for further matching. A few other examples:
79 |
80 | ```php
81 | // API endpoint versioning (i.e. /v1/products/list.json)
82 | Router::connect('/{:version:v\d+}/{:args}', [], ['continue' => true]);
83 |
84 | // Admin routing...
85 | Router::connect('/admin/{:args}', [], ['continue' => true]);
86 |
87 | // For rendering all static pages...
88 | Router::connect('/pages/{:args}', 'Pages::view', ['continue' => true]);
89 | ```
90 |
91 | ### Route Matching
92 |
93 | li3's router is also used in reverse: instead of turning URLs into parameters (controllers and actions, at least) it can also create application URLs based on supplied parameters based on the defined routes.
94 |
95 | Usually you'll be using this functionality without realizing it. For example, it's used by the `Html` helper in views to create links. Normally it's faster and easier to use the supplied helper functions. If however you're doing something in a layer that doesn't have easy access to this functionality, you can use the router directly.
96 |
97 | Full details are supplied in the API docs, but the basic idea is that you can use `Router::match()` to do this. Just supply a set of parameters, and the router will return a URL (if any) that matches that set of parameters:
98 |
99 | ```php
100 | // Imagine this route has already been defined:
101 | Router::connect('/unicorns', 'Ponies::magic');
102 |
103 | Router::match(['controller' => 'Ponies', 'action' => 'magic']);
104 | // Returns '/unicorns'
105 | Router::match('Ponies::magic');
106 | // Also returns '/unicorns'
107 | ```
108 |
--------------------------------------------------------------------------------
/06_controllers/04_flow_control.md:
--------------------------------------------------------------------------------
1 | # Flow Control
2 |
3 |
4 | Occasionally a controller action will want to divert, re-route, or automatically configure the view layer based on an incoming request. There are various controller methods to help facilitate request flow handling.
5 |
6 | ## Redirection
7 |
8 | The most basic type of flow control at the controller level is redirection. It's common to redirect a user to a new URL once an action has been performed. This type of control is done through the controller's `redirect()` method. Here's an example of a controller action that redirects the request:
9 |
10 | ```php
11 | public function add() {
12 | // Validate and save user data POSTed to the
13 | // controller action found in $this->request->data...
14 |
15 | return $this->redirect(["Users::view", "id" => $user->id, "?" => "welcome"]);
16 | }
17 | ```
18 |
19 | The URL specified can be relative to the application or point to an outside resource. The `redirect()` function also features a second `$options` parameter that also allows you to set HTTP status headers, and make decisions about whether or not to `exit()` after a redirect. Be sure to check the API for `lithium\action\Controller::redirect()` for more details.
20 |
21 | ## Other Types of Flow Control
22 |
23 | li3 provides other options for request flow control, such as [exception and error handling](exceptions-errors) and [type rendering & detection](type-rendering-detection), both of which are explained in more detail in their respective manual pages.
24 |
--------------------------------------------------------------------------------
/06_controllers/05_type_rendering_detection.md:
--------------------------------------------------------------------------------
1 | # Type Rendering and Detection
2 |
3 | Although a typical request to a li3 application receives an HTML response, the framework is built to be extremely flexible in handling and serving different types of content. This functionality is especially important in applications that have many different components or endpoints. If your app also feeds data to a Flash object (AMF/XML) and a mobile phone (XML/JSON), responding to requests in different ways with the same underlying logic can be a huge time saver.
4 |
5 | The flow for handling a given type of a response works something like the following:
6 |
7 | 1. A request is sent to the application, containing some sort of indicator of the request type. li3's default routing allows for simple extension detection, for example.
8 | 2. As li3 bootstraps, a media type and handler is registered with the `\net\http\Media` class.
9 | 3. The application detects the request type and sets the response type.
10 | 4. Once a controller is ready to render the data, the registered handler receives the data and renders the output.
11 |
12 | ## Detecting and Setting Types
13 |
14 | The easiest way to set a type is by declaring it as part of the route. One of li3's default routes already does this for you:
15 |
16 | ```php
17 | Router::connect('/{:controller}/{:action}/{:id:[0-9]+}.{:type}', ['id' => null]);
18 | ```
19 |
20 |
21 |
22 | You will need to uncomment the appropriate default route in the application folder's /config/routes.php file in order to use the features described below. There are preformatted routes for both document type data sources and relational database sources available in the routes.php file.
23 |
24 |
25 | In effect, this forces a request to /controller/action/7345.json to be rendered by the JSON media handler currently registered. You can use this pattern to expand your routes to apply type-matching to a wider array of requests:
26 |
27 |
28 |
29 | ```php
30 | // http://example.com/controller/action.xml
31 | Router::connect('/{:controller}/{:action}.{:type}');
32 | ```
33 |
34 | ### Example
35 | Let's assume that you are using a MySQL database as a datasource and you are retrieving data from the `blog_posts` table. First, we need to enable the routes in the `routes.php` file as shown below:
36 |
37 | ```php
38 | /**
39 | * ### Database object routes
40 | *
41 | * The routes below are used primarily for accessing database objects, where `{:id}` corresponds to
42 | * the primary key of the database object, and can be accessed in the controller as
43 | * `$this->request->id`.
44 | *
45 | * If you're using a relational database, such as MySQL, SQLite or Postgres, where the primary key
46 | * is an integer, uncomment the routes below to enable URLs like `/posts/edit/1138`,
47 | * `/posts/view/1138.json`, etc.
48 | */
49 | Router::connect('/{:controller}/{:action}/{:id:\d+}.{:type}', ['id' => null]);
50 | Router::connect('/{:controller}/{:action}/{:id:\d+}');
51 | ```
52 |
53 | Next, let's assume that there is already a view created to display the blog post at `views/blogposts/view.html.php`. This means that when the controller's `view()` method is called, the aforementioned html view will be loaded.
54 |
55 | In our controller, we can use the automatically generated `view()` method, provided you used the CLI to create the controller, which will get the id of the post from the URL. If you didn't use the CLI to create the controller, then your view method should look something like this:
56 |
57 | ```php
58 | public function view() {
59 | $purchaseorder = PoHeader::find($this->request->id);
60 | return compact('purchaseorder');
61 | }
62 | ```
63 |
64 | With these pieces in place, pointing the browser to `blogposts/view/1` will retrieve the blog post with id of 1 and render it into an HTML view, exactly as you expect. If, however, you wish to render the same content in JSON as part of an API or other REST type application, then you can simply point the browser to `blogposts/view/1.json` and the data for the blog post will be rendered to the browser in a JSON format. This is a very powerful feature as it allows you to develop a single controller method that can be quickly output into a variety of formats with very little effort.
65 |
66 | ## Other Ways to Change The Render Type
67 |
68 | You can also statically define the type for a route by adding the 'type' key to the route definition:
69 |
70 | ```php
71 | Router::connect('/latest/feed', [
72 | 'Posts::index',
73 | 'type' => 'xml'
74 | ]);
75 | ```
76 |
77 | If you'd rather use other information to convey the request type to li3 (headers, GET variables, etc.) you can gather that information then set `$this->_render['type']` in the controller action.
78 |
79 | Manual type rendering can also be done by handing the type's name to the render function:
80 |
81 | ```php
82 | $this->render(['csv' => Post::find('all')]);
83 | ```
84 |
85 | ## Handler Registration
86 |
87 | As mentioned earlier, type handlers are registered by the `\net\http\Media` class. This is usually done in the `/config/bootstrap/media.php` bootstrap file. Be sure to uncomment the corresponding line in the main bootstrap file to enable this functionality.
88 |
89 | Register your type by passing the relevant information to `Media::type()` inside the bootstrap. Here's what the general pattern looks like:
90 |
91 | ```php
92 | Media::type('typeName', 'contentType', [$options]);
93 | ```
94 |
95 | To give you an idea of how this process is completed, let's register a new handler for the BSON data type. If you're curious, BSON is a binary, serialized form of JSON used by MongoDB. Start by declaring a new media type in `/config/boostrap/media.php` and uncommenting the `media.php` line in your main bootstrap file:
96 |
97 | ```php
98 | Media::type('bson', 'application/bson', []);
99 | ```
100 |
101 | This gets us pretty far. If you make a request with a .bson extension that matches a configured route (one with {:type} in it), li3 will already hunt for a `.bson.php` template in the controller's template directory. You can continue to customize li3's behavior by utilizing the `$options` array you supply to `Media::type()`. After checking the API docs for `Media::type()`, we realize we can utilize a few options to make sure our response isn't housed in an HTML layout and use some functions we've defined for encoding and decoding the data:
102 |
103 | ```php
104 | Media::type('bson', 'application/bson', [
105 | 'layout' => false,
106 | 'encode' => 'bson_encode',
107 | 'decode' => 'bson_decode'
108 | ]);
109 | ```
110 |
111 | Try the request again, and you'll get your BSON-encoded data, minus the HTML layout. Note that the bson_* functions used in this particular example are part of the PECL MongoDB extension. Don't worry if you don't have it installed: the main point is to realize that you can tell `Media` exactly what function to use to render the data (including using closures).
112 |
113 | For more ideas on configuring media types, see the documentation for `Media::type()`.
114 |
--------------------------------------------------------------------------------
/07_views/01_auto_escaping.md:
--------------------------------------------------------------------------------
1 | # Auto-escaping
2 |
3 | You might have noticed on other pages in the manual that li3 uses the short tag syntax to output the contents of a view variable. This syntax is a bit misleading, as li3 does not actually depend on or use short tags: this output behavior works a bit differently from how it seems. The modified functionality is designed to save time and improve your application's security by escaping output. Escaping output is a core strategy of defense in depth.
4 |
5 | When the view layer is rendered, each template is processed by a tokenizer before it is compiled into its final form. During this step something like this with "short tags":
6 |
7 | ```
8 | = $variable ?>
9 | ```
10 |
11 | Is translated into something like this, which is properly escaped:
12 |
13 | ```php
14 |
15 | ```
16 |
17 | The `$h()` function is there to escape HTML output. This mechanism provides an easy and effective way to make sure all dynamically-generated data is displayed safely in your HTML template.
18 |
19 | We highly recommend using the `= ... ?>` syntax in your views, as it aids greatly in hardening your application against cross-site scripting (XSS) and related attack techniques.
20 |
21 |
22 | One exception to this rule is when a line of template code references the `$this` object. In those cases, output is written directly to the template, rather than being filtered through `$h()`. This is so that content from helpers is not double-escaped. As such, the following two statements are equivalent:
23 |
24 |
25 | ```php
26 | = $this->form->create() ?>
27 | form->create() ?>
28 | ```
29 |
30 | This is an important consideration when accessing properties and methods from the template renderer. If you intend to echo content directly from `$this` which is not coming from a helper (this is not a common occurence), you must manually escape it, like so:
31 |
32 | ```
33 | foo) ?>
34 | ```
35 |
--------------------------------------------------------------------------------
/07_views/02_layouts.md:
--------------------------------------------------------------------------------
1 | # Layouts
2 |
3 | A layout is the basic skeleton of an HTML page. Within a layout file, you might typically have a header, footer, and any other HTML code that should appear on every page that uses the layout.
4 |
5 | For layouts, all files should be located in the application's `views/layouts` directory. When naming an HTML layout file, the naming convention is `{layout_name}.html.php`.
6 |
7 | As an example, the default template in li3 would be found at `views/layouts/default.html.php`. You can also have layouts for other formats, such as XML, and you would name the file accordingly (e.g. `default.xml.php`).
8 |
9 | A layout file looks like what you might typically expect of an HTML file. Below is the code for the default template that ships with li3:
10 |
11 | ```
12 |
13 |
14 |
15 | = $this->html->charset() ?>
16 | Application > = $this->title() ?>
17 | = $this->html->style(['debug', 'lithium']) ?>
18 | = $this->scripts() ?>
19 | = $this->html->link('Icon', null, ['type' => 'icon']) ?>
20 |
21 |
22 |
23 |
24 |
Application
25 |
26 | Powered by = $this->html->link('li3', 'http://li3.me/') ?>.
27 |
28 |
29 |
30 | = $this->content() ?>
31 |
32 |
33 |
34 |
35 | ```
36 |
37 | Within the layout file, there are several familiar components. For example, in the section, you find li3's code for displaying titles, linking CSS files & JS files, setting the Charset, etc. There is a page header, and also the closing tags for body and html round out the layout file's code.
38 |
39 | When it comes to displaying the content of the page, just include a statement to echo $this->content() in the layout wherever the content is going to be displayed.
40 |
41 | ##Using a Layout
42 | Once you have created a layout, it is just a matter of applying the layout to your views when they are rendered from the controller. This can be done from each controller method when the `render()` method is called as shown in the example below:
43 |
44 | ```
45 | public function index() {
46 | return $this->render(['layout' => 'layoutName']);
47 | }
48 | ```
49 |
50 | If you do not specify a layout in the render call, li3 will use the default layout, which is found in `views/layouts/default.html.php`. You can also disable the layout altogether by setting the layout's value to false (e.g. `$this->render(['layout' => false])`).
51 |
52 | ##Setting a Default Layout
53 | You can also specify a default layout to be used for all methods in a controller. This is done through the use of the protected `init()` method. An example of how to set the default layout is shown below:
54 |
55 | ```
56 | protected function _init() {
57 | parent::_init();
58 | $this->_render['layout'] = 'default';
59 | }
60 | ```
61 |
--------------------------------------------------------------------------------
/07_views/03_elements.md:
--------------------------------------------------------------------------------
1 | # Elements
2 |
3 | Since different components of the view layer are often reused, li3 includes the common functionality of wrapping view templates inside of layouts and including small, re-usable view components called elements inside of views. You can also think of an element like a widget or other portion of the displayed content that would be reused across multiple pages.
4 |
5 | ### Examples of Elements
6 |
7 | - site navigation
8 | - single post items
9 | - widgets i.e. upcoming events, blogroll.
10 |
11 | Unless otherwise configured, elements are defined in `app/views/elements`. Inside this folder, the files you define will be tailored to display the output for your chosen element. As an example, the code below defines a product element. The file name is `app/views/elements/product.html.php`.
12 |
13 | ```html
14 |
15 |
= $item->title ?>
16 |
17 |
18 |
19 | description ?>
20 |
21 |
22 | $= $item->price_usd ?>
23 |
24 |
25 | ```
26 |
27 | ### Displaying an Element
28 |
29 | Displaying an element is accomplished via the `View::render()` method. In the following example we render a list of products using the product element we defined above.
30 |
31 | ```html
32 |
39 | ```
40 |
41 | A complete description of the `render()` method can be found in the li3 API documentation under [View::render()](/docs/api/lithium/latest:1.x/lithium/template/View::render)
42 |
--------------------------------------------------------------------------------
/07_views/04_helpers.md:
--------------------------------------------------------------------------------
1 | # Helpers
2 |
3 | li3 helpers take the concept of elements a bit further, and create a place to house reusable presentation logic across your application. The framework comes with a few of it's own, and creating your own application-specific helpers is a snap.
4 |
5 | ## Usage
6 |
7 | Helper usage in li3 is simple because helpers are lazy-loaded by the renderer. To use a helper in the view layer, just reference it as a property of the renderer:
8 |
9 | ```
10 |
11 | Here is a = $this->html->link('link', 'http://li3.me') ?> you'll all enjoy.
12 |
13 | ```
14 |
15 | This approach means you won't have to declare which helpers you're planning on using. Ever. This also means that things aren't loaded up unless (and until!) you're actually using them.
16 |
17 | Remember, you can use helpers anywhere in the view layer: inside layouts, view templates or elements.
18 |
19 | ## Creating Your Own Helpers
20 |
21 | To create a custom helper, create a class in the `app/extensions/helper/` directory that extends li3's base `Helper` class. As a simple example, let's create a simple helper that creates a special sort of link by creating a new file in `app/extensions/helper/AwesomeHtml.php`:
22 |
23 | ```php
24 | namespace app\extensions\helper;
25 |
26 | class AwesomeHtml extends \lithium\template\Helper {
27 |
28 | public function link($title, $url) {
29 | return "$title";
30 | }
31 | }
32 | ```
33 |
34 | One important note to consider here is that the contents of `$title` and `$url` aren't escaped since they're being returned raw from the helper method. _Make sure you make liberal usage of the `escape()` method of the `Helper` class to keep things safe_.
35 |
36 | Because string construction can get a little messy, you may wish to make use of the `_render()` method of the `Helper` class as well. This works when you create an array of string templates as the `_strings` property of a helper. Let's secure our example helper and make it a bit more flexible:
37 |
38 | ```php
39 | namespace app\extensions\helper;
40 |
41 | class AwesomeHtml extends \lithium\template\Helper {
42 |
43 | protected $_strings = [
44 | 'awesome' => '{:title}',
45 | 'super_cool' => '{:title}',
46 | ];
47 |
48 | public function link($title, $url, $options) {
49 | $title = $this->escape($title);
50 | return $this->_render(__METHOD__, $options['type'], compact('title', 'url'));
51 | }
52 | }
53 | ```
54 |
55 | Once this has been setup, we can use the new helper as we would any of the core helpers:
56 |
57 | ```
58 |
59 | You should really check out
60 | = $this->awesomeHtml->link('li3', 'http://li3.me', [
61 | 'type' => 'super_cool'
62 | ]) ?>
63 |
64 | ```
65 |
66 | ## Extending (and replacing) Core Helpers
67 |
68 | li3 gives preference to your classes over the core. If you want to create your own version of the core helpers, it's as easy as creating a new class inside the `app/extensions/helper` directory with the same name.
69 |
70 | For example, we can replace li3's core `Html` helper class with our newly created helper class by renaming the class and filename to 'Html' rather than 'AwesomeHtml'. Doing this also allows you to leave your templates untouched: calls to `$this->html` will reference your helper automatically.
71 |
72 | Also realize that in doing this, you'll probably wish to extend the helper you're replacing to take advantage of the functionality already there. If we took our own advice, the new helper would extend `lithium\template\helper\Html` and `link()` would be refactored to take advantage of the `Html` helper's existing link function.
73 |
--------------------------------------------------------------------------------
/07_views/05_static_content.md:
--------------------------------------------------------------------------------
1 | # Static Content
2 |
3 | There may be cases where your application has static content that needs to be served. li3 provides out-of-the-box functionality to do this via the Pages controller. Static content displayed using the pages controller should be stored in the `views/pages` folder of the application.
4 |
5 | The application's default routing will automatically render static pages with no additional configuration necessary. The default (`/`) route will render the `home` template.
6 |
7 | Other static pages in the `views/pages` directory can be called by name from the URL. As an example, pointing the browser to `/pages/about` would render the `views/pages/about.html.php` view, if the page exists.
8 |
9 | It is also possible to organize and/or nest static pages into directories under `views/pages`. For example, a file located at `views/pages/about/company.html.php` would be displayed by pointing the browser to `/pages/about/company`.
10 |
--------------------------------------------------------------------------------
/07_views/README.md:
--------------------------------------------------------------------------------
1 | # Views
2 |
3 | As one of the three pillars of the Model-View-Controller design pattern, the `View` class (along with other supporting classes) is responsible for taking the data passed from the request and/or controller, inserting this into the requested template/layout, and then returning the fully rendered content.
4 |
5 | Unless otherwise specified, each controller action method results in a rendered view (usually as HTML). View names and locations are determined by convention, according the controller and action names involved. The basic pattern is that views are organized inside `app/views/{controller name}`, with each template named `{action name}.{media}.php`. For example:
6 |
7 | * `UsersController::login()` --> `/app/views/users/login.html.php`
8 | * `NewsItemsController::viewAll()` --> `/app/views/news_items/view_all.html.php`
9 |
10 | ## Accessing View Variables
11 |
12 | Controller-layer data is made available to the template by means of view variables. The names of these variables and their contents depends on how they were passed along by the controller. As covered in the Controller guide, view variables are populated by the use of the `set()` method, or by returning associative arrays from your controller action methods.
13 |
14 | Keys in those arrays determine the names of the view variables. The following lines of code placed in a controller action method all result in a view variable named `$foo` with the contents `"bar"`:
15 |
16 | ```php
17 | $this->set(['foo' => 'bar']);
18 |
19 | // -- or --
20 |
21 | $foo = 'bar';
22 | $this->set(compact('foo'));
23 |
24 | // -- or --
25 |
26 | return ['foo' => 'bar'];
27 |
28 | // -- or --
29 |
30 | $foo = 'bar';
31 | return compact('foo');
32 | ```
33 |
34 | Once this has been done in the controller, you can access the data like so:
35 |
36 | ```
37 |
Spit out data like this: = $foo ?>
38 | ```
39 |
40 | li3 templates are just PHP, so feel free to toss in conditionals, loops and other presentation-based logic as needed.
41 |
42 | ## Auto-Escaping
43 |
44 | You might have noticed that the above example uses the short tag syntax to output the contents of a view variable. This syntax is a bit misleading, as li3 does not depend on or use short tags: this output behavior works a bit differently from how it seems.
45 |
46 | When the view layer is rendered, each template is processed by a tokenizer before it is compiled into its final form. During this step something like this:
47 |
48 | ```
49 | = $variable ?>
50 | ```
51 |
52 | Is translated into something like this:
53 |
54 | ```
55 |
56 | ```
57 |
58 | The `$h()` function you see used there escapes HTML. To make a long story short, this mechanism provides an easy way for you to make sure all dynamically-generated data is safely landing in your HTML template.
59 |
60 | We highly recommend using the `= ... ?>` syntax in your views, as it aids greatly in hardening your application against cross-site scripting (and related) attack techniques.
61 |
62 | _Note:_ One exception to this rule is when a line of template code references the `$this` object. In those cases, output is written directly to the template, rather than being filtered through `$h()`. This is so that content from helpers is not double-escaped. As such, the following two statements are equivalent:
63 |
64 | ```
65 | = $this->form->create() ?>
66 |
67 | form->create() ?>
68 | ```
69 |
70 | This is an important consideration when accessing properties and methods from the template renderer. If you intend to echo content directly from `$this` which is not coming from a helper (this is not a common occurence), you must manually escape it, like so:
71 |
72 | ```
73 | foo) ?>
74 | ```
75 |
76 | ## Layouts & Elements
77 |
78 | Because different components of the view layer are often reused, li3 includes the common functionality of wrapping view templates inside of layouts and including small, re-usable view components called 'elements' inside of views. Unless otherwise configured, elements are defined in `app/views/elements`.
79 |
80 | `$this->_render()` is used within the views to include elements. Any variables passed from the controller to the parent view are also available in the element. The third argument of `$this->_render()` can be used to pass additional variables.
81 |
82 | ```php
83 | // renders app/views/elements/nav.html.php
84 | echo $this->_render('element', 'nav');
85 |
86 | // pass additional variables to the element
87 | echo $this->_render('element', 'nav', [
88 | 'some' => 'additional',
89 | 'vars' => 'available',
90 | 'in' => 'nav.html.php'
91 | ]);
92 | ```
93 |
94 | Layouts contain the header and footer of a rendered view, and are defined in `app/views/layouts`. Unless otherwise directed, li3 will wrap a view's template with the layout defined in `app/views/layouts/default.{type}.php`.
95 |
96 | ```php
97 | namespace app\controllers;
98 |
99 | class MyController extends \lithium\action\Controller {
100 |
101 | /**
102 | * Overriding the layout for all actions in this controller
103 | *
104 | * @var array
105 | */
106 | protected $_render = [
107 | 'layout' => 'yourLayoutName'
108 | ];
109 |
110 | /**
111 | * This action uses another layout which is located
112 | * at `app/views/layouts/anotherLayout.html.php` by default.
113 | * @return array
114 | */
115 | public function someAction() {
116 | $this->_render['layout'] = 'anotherLayout';
117 | }
118 | }
119 | ```
120 |
121 | Layouts should call `= $this->content() ?>` to render the content of the inner view template in the desired location.
122 |
123 | ### Output Handlers
124 |
125 | In the view rendering context, you'll have a number of different tools at your disposal. Besides PHP itself, the view layer features a number of handlers you can use to print out information in templates.
126 |
127 | If you're in the view layer, `$this` refers to the current `Renderer` adapter. Renderers are used to process different types of templates. The default renderer that ships with li3 is the `File` renderer, which handles plain PHP files (you can also write your own renderers if you wish to use a custom templating engine). When a renderer is initialized it sets up a number of handlers to help you output content in your views. li3's default view renderer features these handlers:
128 |
129 | * `$this->url()`: Used for reverse routing lookups in views. For example:
130 |
131 | ```
132 | $this->url(['Posts::view', 'id' => 13]);
133 | // Returns the URL for the matching route, e.g. '/posts/view/13'
134 | ```
135 |
136 | The `url()` output handler is a friendly wrapper for [`Router::match()`](/docs/api/lithium/latest:1.x/lithium/net/http/Router::match), and automatically passes some contextual parameters behind-the-scenes.
137 |
138 | * `$this->path()`: This handler generates asset paths to physical files. This is especially helpful for applications that will live at different parts of the domain during its lifecycle.
139 |
140 | ```
141 | $this->path('videos/funny_cats.flv');
142 |
143 | // If we're running at http://example.com/lithium/, this will return:
144 | // /lithium/videos/funny_cats.flv
145 |
146 | // If we're running at http://example.com/, this will return:
147 | // /videos/funny_cats.flv
148 | ```
149 |
150 | Like `url()`, this handler is a wrapper for another class method, [`Media::asset()`](/docs/api/lithium/latest:1.x/lithium/net/http/Media::asset). Therefore, options that this method accepts can be passed to `path()` as well. For example, if you want to verify that the asset exists before returning the path, you can do the following:
151 |
152 | ```
153 | // Returns the path if the file exists, otherwise false.
154 | $path = $this->path('files/download_835.pdf', ['check' => true]);
155 | ```
156 |
157 | You can also add a timestamp to the end of the URL, which is useful for working with browser caches:
158 |
159 | ```
160 | // Returns i.e. '/css/application.css?1290108597'
161 | $style = $this->path('css/application.css', ['timestamp' => true]);
162 | ```
163 |
164 | See the `$options` parameter of `Media::asset()` for more information.
165 |
166 | * `$this->content()`: Prints out the content of the template to be rendered. This is really a requirement for most layouts.
167 |
168 | * `$this->title()`: Prints out the title of the current template. If an argument is supplied, it sets the title for the current template.
169 |
170 | ```
171 | // In your view:
172 | title('Home') ?>
173 |
174 | // In your layout:
175 | My Awesome Application: = $this->title() ?>
176 | ```
177 |
178 | * `$this->scripts()`: Prints out the scripts specified for the current template. Usually, this is used by the [`script()` method of the `Html` helper](/docs/api/lithium/latest:1.x/lithium/template/helper/Html::script) when the `'inline'` option is set to `false`. However, you can append tags manually as well:
179 |
180 | ```
181 | // In your view:
182 | scripts('');
184 | $this->scripts('')
185 | ?>
186 |
187 | // In your layout:
188 |
189 | = $this->scripts() ?>
190 |
191 | ```
192 |
193 | In particular, this is useful when page-specific scripts are created inline in the page, and you'd like to place them in the `` along with your other scripts:
194 |
195 | ```
196 |
197 |
202 | scripts(ob_get_clean()) ?>
203 | ```
204 |
205 | * `$this->styles()`: Much the same as `scripts()` above, the `styles()` handler acts as a repository for any page-specific style sheets to be included in the layout. While primarily used by the [`style()` method of the `Html` helper](/docs/api/lithium/latest:1.x/lithium/template/helper/Html::style) (again, see the `'inline'` option), it may be used manually as well:
206 |
207 | ```
208 | // In your view:
209 | styles('');
211 | $this->styles('')
212 | ?>
213 |
214 | // In your layout:
215 |
216 | = $this->styles() ?>
217 |
218 | ```
219 |
--------------------------------------------------------------------------------
/08_quality_code/01_security.md:
--------------------------------------------------------------------------------
1 | # Securing Applications
2 |
3 | ## Preventing SQL Injections
4 |
5 | [SQL Injections](https://en.wikipedia.org/wiki/SQL_injection) are a common type of attack
6 | and can take very different forms.
7 |
8 | The ORM will quote any values that are passed as condition values automatically in order to
9 | protect your application from injection attacks.
10 |
11 | ```php
12 | $author = 'UNTRUSTED USER INPUT';
13 |
14 | Posts::find('first', [
15 | 'conditions' => ['author' => $author]
16 | ]);
17 | ```
18 |
19 | However you **cannot rely** on this feature if you use user input in any other place of a query. The
20 | following example shows how untrusted user input can be used safely used to specify the set of
21 | fields to be retrieved. As a countermeasure we first check if the provided field actually exists.
22 |
23 |
24 | ```php
25 | $field = 'UNTRUSTED USER INPUT';
26 |
27 | if (!Posts::hasField($field)) {
28 | throw new Exception('Invalid field.');
29 | }
30 |
31 | Posts::find('first', [
32 | 'fields' => [$field, 'title']
33 | ]);
34 | ```
35 |
36 | ## Securing Form Fields and Values
37 |
38 | The `FormSignature` class cryptographically signs web forms, to prevent adding or removing
39 | fields, or modifying hidden (locked) fields.
40 |
41 | Using the `Security` helper, `FormSignature` calculates a hash of all fields in a form, so that
42 | when the form is submitted, the fields may be validated to ensure that none were added or
43 | removed, and that fields designated as _locked_ have not had their values altered.
44 |
45 | To enable form signing in a view, simply call `$this->security->sign()` before generating your
46 | form. In the controller, you may then validate the request by passing `$this->request` to the
47 | `check()` method.
48 |
49 | Inside the view:
50 | ```
51 | security->sign() ?>
52 |
53 | = $this->form->create($post) ?>
54 | = $this->form->field('title') ?>
55 | = $this->form->field('body', ['type' => 'textarea']) ?>
56 | = $this->form->field('') ?>
57 | = $this->form->submit('save') ?>
58 | = $this->form->end() ?>
59 | ```
60 |
61 | Inside a controller action:
62 | ```php
63 | if ($this->request->is('post') && !FormSignature::check($this->request)) {
64 | // The key didn't match, meaning the request has been tampered with.
65 | }
66 | ```
67 |
68 |
69 | To make form signing work, you must use the form helper to create the form and its fields.
70 |
71 |
72 | When fields are inserted dynamically into the form (i.e. through JavaScript) then those can
73 | be manually excluded while checking the signature.
74 |
75 | ```php
76 | FormSignature::check($this->request, [
77 | 'exclude' => [
78 | '_wysihtml5'
79 | ]
80 | ]);
81 | ```
82 |
83 | ## Preventing Mass Assignment
84 |
85 | To prevent your application from opening up to the so called [mass assingment vulnerabilty](http://en.wikipedia.org/wiki/Mass_assignment_vulnerability),
86 | the framework provides you with the _whitelist_ feature. This whitelist can be used to limit the set of fields which get updated during create or update operations.
87 |
88 | ```php
89 | $data = [
90 | 'name' => 'John Doe',
91 | 'email' => 'haxx0r@example.org' // Added by the attacker.
92 | ];
93 |
94 | $user = Users::findById($authed['user']['id']);
95 | $user->save();
96 | ```
97 |
98 | As a countermeasure additionally use the whitelist feature. So that in this
99 | case really just the name field gets updated.
100 |
101 | ```php
102 | $user = Users::findById($authed['user']['id']);
103 | $user->save($data, [
104 | 'whitelist' => ['name']
105 | ]);
106 | ```
107 |
108 |
109 | Always prefer whitelisting over blacklisting.
110 |
111 |
112 | ## Secure Your Database
113 |
114 | [There has recently been a study](http://cispa.saarland/wp-content/uploads/2015/02/MongoDB_documentation.pdf) into how many people aren't properly securing their mongo databases. The study found that over 40,000 sites using MongoDB didn't correctly configure their databases to use a password so that a malicious user could connect without any verification. When putting your database system online, make sure that you properly configure your database so that it is secure.
115 |
--------------------------------------------------------------------------------
/08_quality_code/02_testing.md:
--------------------------------------------------------------------------------
1 | # The Unit Testing Framework
2 |
3 | Applications with any amount of complexity or reuse necessitate test coverage. li3's unit testing framework is home grown, and is used for the framework's own testing. It's simple, lightweight, and ready for immediate use.
4 |
5 | ## Getting Started
6 |
7 | Since the unit testing framework is built into li3, you might already have it up and running. Once you've downloaded and installed li3, point your web browser to `/test` under your application's base URL.
8 |
9 | The li3 Unit Test Dashboard is where you'll be able to view test cases, run unit tests, and view reports. Initially, you'll only be seeing li3's core tests. Soon enough, however, you'll be managing your own application's unit testing setup.
10 |
11 | All of your application's unit tests will reside in `/app/tests/`. There are three main test folders you'll need to be using: `cases`, `integration`, and `mocks`. The `cases` folder holds unit tests for single classes, `integration` holds test cases that span two or more classes, and `mocks` is used to create fake data for use during testing.
12 |
13 | ## Test Cases
14 |
15 | The `cases` folder is used to house all the core logic for your unit tests. If you take a peek inside `/app/tests/cases`, you'll see that you already have three folders used to organize your application's unit tests. This folder structure dictates the namespace for each unit test class, and should generally mirror your application's class/namespace structure.
16 |
17 | Let's start out by creating a simple test case as a working example. Our first working example will be a model unit test. Let's start by creating one using the `li3 create` console command.
18 |
19 | ```bash
20 | cd /path/to/lithium/app
21 |
22 | li3 create model Posts
23 | # Outputs: Posts created in app\models.
24 | ```
25 |
26 | We can also use the `li3 create` command to create our test case class.
27 |
28 | ```bash
29 | li3 create test model Posts
30 | # Outputs: PostsTest created for Posts in app\tests\cases\models.
31 | ```
32 |
33 | Doing so creates a test file template class that extends `lithium\test\Unit` and looks like the following:
34 | ```php
35 |
49 | ```
50 |
51 | The two initial methods supplied act as they're named. The `setUp()` method is used to perform any preparation work you'll need to perform your unit testing logic. This might be anything from setting up database connections to initializing mock data. Similarly, `tearDown()` is used to clean up anything that might be left over once a unit test has been completed. These methods are called before and after each method in your unit test case.
52 |
53 | The meat of the unit test, however, will be housed inside of methods you create. Each piece of your unit testing logic should be placed inside of a method whose name starts with 'test'. Before we make any adjustments to the `Posts` model, let's exercise a bit of TDD and write an example test method first.
54 |
55 | Since our test case is a subclass of `lithium\test\Unit`, we have easy access to a number of methods that help us validate test assertions. Since they're plainly named, I'll list some here. For more information, please refer to the API documentation for `lithium\test\Unit`.
56 |
57 | - `assertEqual()`
58 | - `assertNotEqual()`
59 | - `assertIdentical()`
60 | - `assertTrue()`
61 | - `assertFalse()`
62 | - `assertNull()`
63 | - `assertNoPattern()`
64 | - `assertPattern()`
65 | - `assertTags()`
66 | - `assertCookie()`
67 | - `expectException()`
68 |
69 | Every post should have a great title, and any editor knows that post titles containing the phrase "top ten" are pure rubbish. We'll eventually need a method in our Posts model that searches for this phrase and warns us. Before writing that method, let's establish a test case to cover it. We'll call it `testIsGoodTitle()`. See an example implementation below:
70 |
71 | ```php
72 | assertTrue(Posts::isGoodTitle("How to Win Friends and Influence People"));
86 | $this->assertFalse(Posts::isGoodTitle("The Top 10 Best Top Ten Lists"));
87 | }
88 | }
89 |
90 | ?>
91 | ```
92 |
93 | Turn back to your browser showing the Unit Test Dashboard, and refresh it. You should see a new entry at the top of the list on the left hand side that shows our `PostsTest` unit test case. Clicking on the `PostsTest` test case should show you the test results. At this point you won't get far—the model will likely complain about a missing connection or function: as it should!
94 |
95 | Let's start working on the model so we can get that test to pass. First, let's specify our model as not having any connection. We'll adjust this later, but let's do this now for simplicity's sake.
96 |
97 | ```php
98 | false];
105 |
106 | public $validates = [];
107 | }
108 |
109 | ?>
110 | ```
111 |
112 | Once that's in place, running the test again should have it barking about how `isGoodTitle()` hasn't been defined. Let's provide a rudimentary implementation in the model to satisfy it:
113 |
114 | ```php
115 | false];
122 |
123 | public $validates = [];
124 |
125 | public static function isGoodTitle($title) {
126 | return !stristr($title, 'top ten');
127 | }
128 | }
129 |
130 | ?>
131 | ```
132 |
133 | At this point, your test cases should run successfully in the Unit Test Dashboard.
134 |
135 | ## Mocks
136 |
137 | Mocks are used in place of actual sources of information. You can create a mock for just about anything: a data source, model data, a console command response... anything. Since we're dealing primarily with the model in this example, let's continue that train of thought, and use some mocks to help us test our new model functionality.
138 |
139 | Let's create a MockPosts that returns test data we can use to run through our `isGoodTitle()` method. One easy way to do that is to create a new class that just returns a RecordSet (in the case of an SQL database) or a Document (in the case of a document database) collection.
140 |
141 | Start by creating a new file in `app/tests/mocks/data/MockPosts.php`:
142 |
143 | ```php
144 | [
156 | 'id' => 1, 'title' => 'Top ten reasons why this is a bad title.'
157 | ]]);
158 | break;
159 | case 'all':
160 | default :
161 | return new RecordSet(['data' => [
162 | ['id' => 1, 'title' => 'Top ten reasons why this is a bad title.'],
163 | ['id' => 2, 'title' => 'Sensationalist Over-dramatization!'],
164 | ['id' => 3, 'title' => 'Heavy Editorializing!'],
165 | ]]);
166 | break;
167 | }
168 | }
169 | }
170 |
171 | ?>
172 | ```
173 |
174 | What we've got here is essentially a model that spits out hard-coded data when we call `find()`. In some cases, this might really be all we need. Let's use this in our main test case by adding the following function:
175 |
176 | ```php
177 | public function testMockTitles() {
178 | $results = MockPosts::find('all');
179 |
180 | $first = $results->current();
181 | $this->assertFalse(MockPosts::isGoodTitle($first['title']));
182 | }
183 | ```
184 |
185 | Head back to the Unit Test Dashboard to make sure this runs successfully, and you're done!
186 |
187 | ### State
188 |
189 | One challenge with testing is creating and initializing your objects. The `__set_state()` function allows test writers to quickly create objects with their pre-existing properties and values intact. This method can be called statically on any class that extends `Object` to return an instance of itself.
190 |
191 | ```php
192 | class MockObject extends \lithium\core\Object {
193 |
194 | protected $_protected = null;
195 |
196 | public function getProtected() {
197 | echo $this->$_protected;
198 | }
199 | }
200 |
201 | $object = MockObject::__set_state([
202 | '_protected' => 'testing'
203 | ]);
204 |
205 | $object->getProtected(); // 'testing'
206 | ```
207 |
208 |
--------------------------------------------------------------------------------
/08_quality_code/03_analysis.md:
--------------------------------------------------------------------------------
1 | # Static Code Analysis
2 |
3 | Simply defined, static code analysis reviews and analyzes code outside of the runtime environment. In short, this means that the code is analyzed without actually running the code. li3 offers a plugin that can perform certain types of static code analysis on your application.
4 |
5 | ## Installing
6 |
7 | The _quality_ plugin for li3 is available at `git://github.com/UnionOfRAD/li3_quality.git`. Once the repository is cloned into your `libraries` folder, It is activated by adding the following code to `config/bootstrap/libraries.php` file:
8 |
9 | ```php
10 | Libraries::add('li3_quality');
11 | ```
12 |
13 | ## Available Tools
14 |
15 | The following tools are available in `li3_quality` for performing static code analysis:
16 |
17 | * Syntax: checks the conformance of your code against li3's coding standards
18 | * Coverage: analyzes the percentage of code covered by unit tests
19 | * Documented: looks for undocumented classes and methods in the library
20 | * Complexity: measures cyclomatic complexity in the library
21 |
22 | ## Code Analysis from the Browser
23 |
24 | The quality plugin is woven into li3's unit testing framework, and there are features of static code analysis that are available from li3's browser based interface, available by pointing the browser to `http://example.com/test`
25 |
26 | Once you have made a choice on what is going to be tested from the left menu, options for measuring cyclomatic complexity, code coverage, and identifying syntax violations are available from the top menu.
27 |
28 | ## Code Analysis from the Command Line
29 |
30 | `li3_quality` is also available for use from the command line:
31 |
32 | ```sh
33 | li3 quality
34 | ```
35 |
36 | ```
37 | li3 console started in the development environment. Use the --env=environment key to alter this.
38 | USAGE
39 | li3 quality syntax
40 | li3 quality documented
41 | li3 quality coverage
42 | ```
43 |
--------------------------------------------------------------------------------
/08_quality_code/README.md:
--------------------------------------------------------------------------------
1 | # Quality Code
2 |
3 | Clean, roboust and well tested code are fundamental goals of the framework.
4 |
5 | li₃ adheres to
6 | [coding](/docs/book/specs/latest:1.x/accepted/LSR-0-coding),
7 | [testing](/docs/book/specs/latest:1.x/accepted/LSR-2-testing),
8 | and [documentation](/docs/book/specs/latest:1.x/accepted/LSR-1-documenting)
9 | standards which can be found inside the
10 | [specs documentation repository](/docs/book/specs/latest:1.x).
11 |
12 | [Static code analysis](analysis) tools are used to detect standards violations.
13 |
14 | Learn how to write unit, integration and functional tests in the [Testing](testing) chapter.
15 |
16 | To make you application as robost to potential security threads as possible, follow the
17 | [Securing Applications](security) guide.
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/09_common_tasks/02_simple_authentication.md:
--------------------------------------------------------------------------------
1 | # Simple Authentication
2 |
3 | If you're doing much more than simple static content delivery, chances are you'll end up needing to protect access to certain resources and functionality your application provides. li3's authentication setup is simple and allows you to quickly create a framework for managing and protecting those resources.
4 |
5 | ## Data Setup
6 |
7 | The default auth setup makes decisions based on information in your data store. The first thing you'll need to do is set up a model that handles user credentials. That model first needs a connection: set that up first in `config/bootstrap/connections.php`. If you're using MySQL as your data source, it should look something like this:
8 |
9 | ```php
10 | use lithium\data\Connections;
11 |
12 | Connections::add('default', [
13 | 'type' => 'database',
14 | 'adapter' => 'MySql',
15 | 'database' => 'mydatabase',
16 | 'user' => 'myusername',
17 | 'password' => 'mypassword'
18 | ]);
19 | ```
20 |
21 | If you're running with Mongo, it'll look a bit different:
22 |
23 | ```php
24 | Connections::add('default', [
25 | 'type' => 'MongoDb',
26 | 'database' => 'mydatabase',
27 | ]);
28 | ```
29 |
30 | Developers using MySQL will need a `users` table with at least the columns `id`, `username`, and `password`. Those using Mongo will need a collection in the database with a similar structure. You can customize the model and fields that `Auth` will use as we'll see later. Make sure to take a moment and set up your `Users` model as well in `models/Users.php`:
31 |
32 | ```php
33 | namespace app\models;
34 |
35 | class Users extends \lithium\data\Model {}
36 | ```
37 |
38 | Once you've got that setup, your application more or less interacts with the data in the same way, regardless of the particular data source you're using.
39 |
40 | ## User Creation
41 |
42 | Creating users that are compatible with the `Auth` setup is worth noting. While configurable, the default setup assumes a users data source with a hashed `password` field, and a `username` field. Please keep these defaults in mind as you create controller logic or view forms that interact with user data.
43 |
44 | For convenience, we also recommend setting up a filter to automatically hash user passwords when new users are created. You can see an example filter in the __Model__ section of the [MVC auth setup instructions](simple-auth-user). You can continue to follow those instructions to create a controller and template that will allow you to generate users within your application.
45 |
46 | ## Bootstrapping Auth
47 |
48 | Once the data handling is in place, li3 needs to know you intend to use authentication, and with which settings. As with most things, this is done in a specific bootstrap file.
49 |
50 | li3's default application template ships with a file called `config/bootstrap/session.php`, which contains default session storage settings, as well as a commented-out default authentication configuration. To enable this configuration, first edit `config/bootstrap.php` to include (or uncomment) the line requiring the session bootstrap file:
51 |
52 | ```php
53 | require __DIR__ . '/bootstrap/session.php';
54 | ```
55 |
56 | Next, make sure your `Session` setup is using the PHP adapter, then create or uncomment the initial `Auth` configuration:
57 |
58 | ```php
59 | use lithium\storage\Session;
60 | use lithium\security\Auth;
61 |
62 | Session::config([
63 | 'default' => ['adapter' => 'Php']
64 | ]);
65 |
66 | Auth::config([
67 | 'default' => ['adapter' => 'Form']
68 | ]);
69 | ```
70 |
71 | The `Session` setup is pretty straightforward, and the `Auth` configuration tells li3 which adapter we want to use (one suited for credentials submitted via web form), and details about the model involved and used to match incoming credentials against.
72 |
73 | Note that the configuration information is housed in an array keyed `'default'`. `Auth` supports many different simultaneous configurations. Here we're only creating one, but you can add more (each using different adapters/configurations) as needed.
74 |
75 | If you're editing the bootstrap files that ship with each new application, you'll notice that the configuration presented above is quite a bit simpler than the configuration provided. This is because the manual assumes you are following the default conventions, whereas the bootstrap files make it more explicit as to what configuration options are available.
76 |
77 | ## Authentication and Controller Actions
78 |
79 | The first action you'll need to setup is the one that authenticates your users and adjusts the session to mark the user as identified. You could place this as you please, but it's generally accepted to create it in `SessionsController::add()`. The suggested approach is very simple:
80 |
81 | ```php
82 | namespace app\controllers;
83 |
84 | use lithium\security\Auth;
85 |
86 | class SessionsController extends \lithium\action\Controller {
87 |
88 | public function add() {
89 | if ($this->request->data && Auth::check('default', $this->request)) {
90 | return $this->redirect('/');
91 | }
92 | // Handle failed authentication attempts
93 | }
94 |
95 | /* ... */
96 | }
97 | ```
98 |
99 | The meat is the part of the conditional that calls `Auth::check()` and hands it the name of our desired `Auth` configuration name (remember we named the whole config array `'default'`?), and information about the current request.
100 |
101 | If the user has been successfully verified, the session is updated to mark the user as authenticated and the user is redirected to the root of the application. If there are problems with the authentication process the login view is rendered again.
102 |
103 | As a reference, the web form that sends the credentials and is the content of the `add` view at `views/sessions/add.html.php` should contain something that looks like this:
104 |
105 | ```php
106 | = $this->form->create(null) ?>
107 | = $this->form->field('username') ?>
108 | = $this->form->field('password', ['type' => 'password']) ?>
109 | = $this->form->submit('Log in') ?>
110 | = $this->form->end() ?>
111 | ```
112 |
113 | ## Protecting Resources
114 |
115 | The setup for protecting resources is almost the same as it is for initially authenticating the user (though you'd want to redirect the user to the login action on error). Use `Auth::check()` in your controller actions to make sure that sections in your application are blocked from non-authenticated users. For example:
116 |
117 | ```php
118 | namespace app\controllers;
119 |
120 | use lithium\security\Auth;
121 |
122 | class PostsController extends \lithium\action\Controller {
123 |
124 | public function add() {
125 | if (!Auth::check('default')) {
126 | return $this->redirect('Sessions::add');
127 | }
128 |
129 | /* ... */
130 | }
131 | }
132 | ```
133 |
134 | ## Checking Out
135 |
136 | Next, you'll want to create an action that clears an end-user's authentication session on your system. Do that by making a call to `Auth::clear()` in a controller action.
137 |
138 | ```php
139 | namespace app\controllers;
140 |
141 | use lithium\security\Auth;
142 |
143 | class SessionsController extends \lithium\action\Controller {
144 |
145 | /* ... */
146 |
147 | public function delete() {
148 | Auth::clear('default');
149 | return $this->redirect('/');
150 | }
151 | }
152 | ```
153 |
154 | ## Routing
155 |
156 | Finally, in order to give users slightly friendlier URLs than `/sessions/add` and `/sessions/delete`, you can wire up some very simple custom routes in `config/routes.php`. Since these are very specific routes, you'll want to add them at or near the top of your routes file:
157 |
158 | ```php
159 | Router::connect('/login', 'Sessions::add');
160 | Router::connect('/logout', 'Sessions::delete');
161 | ```
162 |
163 | ## More Information
164 |
165 | If you'd like to get under the hood and see how li3 handles password hashing, auth queries & session writes, etc., check out the API documentation for the following:
166 |
167 | - [The `Auth` class](/docs/api/lithium/latest:1.x/lithium/security/Auth)
168 | - [The `Form` auth adapter](/docs/api/lithium/latest:1.x/lithium/security/auth/adapter/Form)
169 | - [The `Password` class](/docs/api/lithium/latest:1.x/lithium/security/Password)
170 |
--------------------------------------------------------------------------------
/09_common_tasks/03_simple_auth_user.md:
--------------------------------------------------------------------------------
1 | # Creating a user in M, V, C
2 |
3 | First, create a database with a `users` table or collection, with at least a primary key (usually `id` or `_id`) and the fields `username` and `password` (these could of course be renamed, but that requires more configuration).
4 |
5 | ## The Model
6 |
7 | Create the model in `models/Users.php`:
8 |
9 | ```php
10 | namespace app\models;
11 |
12 | class Users extends \lithium\data\Model {}
13 | ```
14 |
15 | Then, create a model filter in a new bootstrap file in `app/config/bootstrap/` called `user.php`. This file will automatically hash user passwords before the accounts are created. The `Password` class will automatically use the most secure hashing method available on your system:
16 |
17 | ```php
18 | use app\models\Users;
19 | use lithium\aop\Filters;
20 | use lithium\security\Password;
21 |
22 | Filters::apply(Users::class, 'save', function($params, $next) {
23 | if ($params['data']) {
24 | $params['entity']->set($params['data']);
25 | $params['data'] = [];
26 | }
27 | if (!$params['entity']->exists()) {
28 | $params['entity']->password = Password::hash($params['entity']->password);
29 | }
30 | return $next($params);
31 | });
32 | ```
33 |
34 |
This example uses new-style filters, available with 1.1.
35 |
36 | Now add the following line to app/config/bootstrap.php to include your new user.php bootstrap file.
37 |
38 | ```php
39 | require __DIR__ . '/bootstrap/user.php';
40 | ```
41 |
42 | ## The Controller
43 |
44 | Create this file in `controllers/UsersController`:
45 |
46 | ```php
47 | namespace app\controllers;
48 |
49 | use lithium\security\Auth;
50 | use app\models\Users;
51 |
52 | class UsersController extends \lithium\action\Controller {
53 |
54 | public function index() {
55 | $users = Users::all();
56 | return compact('users');
57 | }
58 |
59 | public function add() {
60 | $user = Users::create($this->request->data);
61 |
62 | if (($this->request->data) && $user->save()) {
63 | return $this->redirect('Users::index');
64 | }
65 | return compact('user');
66 | }
67 | }
68 | ```
69 |
70 | ## The views
71 |
72 | Then create the templates.
73 |
74 | `views/users/add.html.php`:
75 |
76 | ```html
77 |
95 | ```
96 |
--------------------------------------------------------------------------------
/09_common_tasks/04_logging.md:
--------------------------------------------------------------------------------
1 | # Logging
2 |
3 | Logging in li3 is handled through the `Logger` class. This class is designed to provide a consistent interface for writing log messages across the application. The `Logger` class can also be set up with a series of named configurations that contain a log adapter to write to.
4 |
5 | ## Configuration
6 |
7 | When configuring adapters, you may specify one or more priorities for each, using the `'priority'` key. This key can be a single priority level (string), or an array of multiple levels. When a log message is written, all adapters that are configured to accept the priority level with which the message was written will receive the message.
8 |
9 | ```php
10 | Logger::config([
11 | 'default' => ['adapter' => 'Syslog'],
12 | 'badnews' => [
13 | 'adapter' => 'File',
14 | 'priority' => ['emergency', 'alert', 'critical', 'error']
15 | ]
16 | ]);
17 | ```
18 |
19 | ## Usage
20 |
21 | The `Logger` class has a single public function called `write()`. This is the method used to write log messages. li3 also allows you to use the priority as the method name. For instance, in the above configuration example, you could call the `Logger::critical()` method
22 |
23 | ### Example
24 |
25 | ```php
26 | // Using the `write()` method:
27 | Logger::write('critical', 'This is a critical message');
28 |
29 | // is the same as:
30 | Logger::critical('This is a critical message');
31 | ```
32 |
33 | By default, the logger will write a message to any adapter that has the specified priority in the log message in its configuration. To write to an adapter other than the default adapter(s), you can use the options parameter which expects an array.
34 |
35 | ```php
36 | Logger::write('critical', 'This is a critical message', ['name' => 'badnews']);
37 | ```
38 |
39 |
40 | Attempting to use an undefined priority level will raise an exception. See the list of available adapters for more information on what adapters are available, and how to configure them.
41 |
42 |
43 | ## See Also
44 |
45 | * [List of Logger Adapters](/docs/api/lithium/latest:1.x/lithium/analysis/logger/adapter)
46 | * [Logger API Documentation](/docs/api/lithium/latest:1.x/lithium/analysis/Logger)
47 |
--------------------------------------------------------------------------------
/09_common_tasks/05_caching.md:
--------------------------------------------------------------------------------
1 | # Caching
2 |
3 | The `Cache` static class provides a consistent interface to configure and utilize the different cache adapters included with li3, as well as your own adapters.
4 |
5 | ## Included Adapters
6 |
7 | The framework ships with several adapters for caching. These adapters can be found in
8 | `lithium/storage/cache/adapter`. Each adapter has its own special characteristics,
9 | pick one (or two) dependent on your specific use case.
10 |
11 | * `Memcache` - A libmemcached based adapter, highly recommended.
12 | * `Apc` - Can be used if you're on an older PHP version and cannot use memcached, but have APC or APCu available.
13 | * `Redis` - Recommended if you're already using redis for other tasks.
14 | * `File` - A minimal file-based cache, good if your app lives in constrained environment or you'd like to cache BLOBs.
15 | * `Memory` - A minimal in-memory cache, good for testing purposes.
16 | * `Xcache` - An alternative to `Apc`, not recommended as support might be phased out.
17 |
18 | Also see the [Cache Adapters API Documentation](/docs/api/lithium/latest:1.x/lithium/storage/cache/adapter) for more information.
19 |
20 | ## Enabling/Disabling Caching
21 |
22 | To control whether or not caching is enabled, you can either comment or uncomment the
23 | following line in your application's `config/bootstrap.php` file:
24 |
25 | ```php
26 | /**
27 | * This file contains configurations for connecting to external caching resources, as well as
28 | * default caching rules for various systems within your application
29 | */
30 | require __DIR__ . '/bootstrap/cache.php';
31 | ```
32 |
33 |
34 | By default, caching is enabled in the framework.
35 |
36 |
37 | ## Configuration
38 |
39 | In most cases, you will configure various named cache configurations in your bootstrap
40 | process, which will then be available to you in all other parts of your application. A
41 | simple example configuration:
42 |
43 | ```php
44 | Cache::config([
45 | 'local' => [
46 | 'adapter' => 'Apc'
47 | ],
48 | 'distributed' => [
49 | 'adapter' => 'Memcached',
50 | 'host' => '127.0.0.1:11211'
51 | ],
52 | 'default' => [
53 | 'adapter' => 'File',
54 | 'strategies => ['Serializer']
55 | ]
56 | ];
57 | ```
58 |
59 | ### Strategies
60 |
61 | Each cache configuration can be configured with _strategies_. These influence how values are read and written
62 | into the cache. Some adapters already handle serialization for you, others like `File` do not do this. This
63 | is why we configure the `File` adapter using the general `Serializer` strategy. Other stratgies can be found
64 | in the [Cache Strategies API Documentation](/docs/api/lithium/latest:1.x/lithium/storage/cache/strategy).
65 |
66 | ### Scoping
67 |
68 | Adapter configurations can be scoped, adapters will then handle the
69 | namespacing of the keys transparently for you. This prevents caches
70 | from "stepping on each others toes".
71 |
72 | ```
73 | Cache::config([
74 | 'primary' => ['adapter' => 'Apc', 'scope' => 'primary'],
75 | 'secondary' => ['adapter' => 'Apc', 'scope' => 'secondary']
76 | ];
77 | ```
78 |
79 | ## General Operation
80 |
81 | Adapters provide a consistent interface for basic cache operations (`write`, `read`,
82 | `increment`, `decrement`, `delete` and `clear`), which can be used interchangeably between
83 | all adapters. Some adapters may provide additional methods that are not consistently
84 | available across other adapters.
85 |
86 | ### Writing to the Cache
87 |
88 | All cache operations take the name of the configuration as their first argument. This
89 | allows you to use the best cache configuration for your use cache.
90 |
91 | ```php
92 | // Will store the value `'bar'` under the `foo` key, using the default expiry.
93 | Cache::write('default', 'foo', 'bar');
94 | ```
95 |
96 |
97 | The read/write and delete methods can handle multi keys/values or so called batch operations.
98 | Simply pass an array of keys (and value pairs) to the respective method.
99 |
100 |
101 | To specify an **expiry**, use the 4th parameter of the method. Expiry time is a `strtotime()`
102 | compatible string. Alternatively an integer denoting the seconds until the item expires
103 | (TTL). If no expiry time is set, then the default cache expiration time set with the cache
104 | adapter configuration will be used. To persist an item use `Cache::PERSIST`.
105 |
106 | ```php
107 | Cache::write('default', 'foo', 'bar', '+1 hour');
108 | Cache::write('default', 'foo', 'bar', Cache::PERSIST);
109 | ```
110 |
111 | ### (Atomically) Increment/Decrement Values
112 |
113 | Two specialized methods for writing to the cache are `Cache::increment()` and `Cache::decrement()`. These
114 | can be used i.e. if you want to increase a counter. Some adapters handle these operations atomically others
115 | can't. Please check your adapter configuration for details.
116 |
117 | ```php
118 | Cache::increment('default', 'pageviews'); // increment count by one
119 | Cache::increment('default', 'pageviews', 2); // increment count by two
120 | ```
121 |
122 | ### Reading from the Cache
123 |
124 | Reading from cache is pretty is and after reading the above you should already be able to
125 | guess, how this works.
126 |
127 | ```php
128 | // Will read the value under the `foo` key, if isn't found returns `null`.
129 | Cache::read('default', 'foo');
130 | ```
131 |
132 | ## Caching BLOBs
133 |
134 |
This feature is available with version 1.1.
135 |
136 | BLOBs are binary large objects or simply put: _files_. The file cache adapter is capable
137 | of storing BLOBs using the following configuration.
138 |
139 | ```php
140 | Cache::config([
141 | 'blob' => [
142 | 'adapter' => 'File',
143 | 'streams' => true
144 | ]
145 | ]);
146 | ```
147 | Imagine - upon user request - a PDF is compiled. This requires quite a
148 | bit of CPU time and memory. Upon following requests for the same PDF you
149 | want to save some cycles and return the file from cache. This example
150 | shows you how.
151 |
152 | ```php
153 | // We will need a stream handle we can write to and read from.
154 | $stream = fopen('php://temp', 'wb');
155 |
156 | // Pseudocode; generate a PDF then store it in the stream.
157 | $pdf->generate()->store($stream);
158 |
159 | // We must rewind the stream, as Cache will not do this for us.
160 | rewind($stream);
161 |
162 | // Store the contents of $stream into a cache item.
163 | Cache::write('blob', 'productCatalogPdf', $stream);
164 | ```
165 |
166 | ```php
167 | // ... later somewhere else in the galaxy ...
168 | $stream = Cache::read('blob', 'productCatalogPdf');
169 |
170 | // Output $stream to the client.
171 | // echo stream_get_contents($stream);
172 | ```
173 |
174 |
175 |
--------------------------------------------------------------------------------
/09_common_tasks/06_error_handling.md:
--------------------------------------------------------------------------------
1 | # Exceptions and Error Handling
2 |
3 | Error handling in li3 is done using the core ErrorHandler class. `ErrorHandler` allows PHP errors and exceptions to be handled in a uniform way. Using `ErrorHandler`s configuration makes it possible to have broad but tightly controlled error handling across your application.
4 |
5 | `ErrorHandler` configuration is done by creating a new error-specific bootstrap file that contains your `ErrorHandler` configuration and initialization. To illustrate how this is done, let's consider an imaginary (but common) scenario. Rather than tossing up error messages and stack traces to your users, it's better to create some sort of way to handle exceptions and render user-friendly error pages.
6 |
7 | Let's start by creating a way to handle page not found-like errors. If a request can't be routed properly, the li3 dispatcher will throw an exception to let you know that a controller or view can't be found. Though the example here will be specific to this case, it should provide a mental framework that will allow you to understand how to catch errors and exceptions, and handle them accordingly.
8 |
9 | Start by creating a new bootstrap file in the application directory called `/config/bootstrap/error.php`:
10 |
11 | ```php
12 | use lithium\core\ErrorHandler;
13 |
14 | $conditions = ['type' => 'lithium\action\DispatchException'];
15 |
16 | ErrorHandler::apply('lithium\action\Dispatcher::run', $conditions, function($exception, $params) {
17 | var_dump(compact('exception', 'params'));
18 | die();
19 | });
20 | ```
21 |
22 | This simple example shows how you can create a lambda that handles any `DispatchException`s being thrown in your entire application. The function you pass to `apply()` can be more involved, depending on what you want to do, however.
23 |
24 | Here's a more complete example, showing how you'd actually render a template, and include logging:
25 |
26 | ```php
27 | use lithium\core\ErrorHandler;
28 | use lithium\analysis\Logger;
29 | use lithium\template\View;
30 |
31 | Logger::config(['error' => ['adapter' => 'File']]);
32 |
33 | $render = function($template, $content) {
34 | $view = new View([
35 | 'paths' => [
36 | 'template' => '{:library}/views/{:controller}/{:template}.{:type}.php',
37 | 'layout' => '{:library}/views/layouts/{:layout}.{:type}.php',
38 | ]
39 | ]);
40 | echo $view->render('all', compact('content'), compact('template') + [
41 | 'controller' => 'errors',
42 | 'layout' => 'default',
43 | 'type' => 'html'
44 | ]);
45 | };
46 | ErrorHandler::apply('lithium\action\Dispatcher::run', $conditions, function($exception, $params) {
47 | Logger::write('error', "Page Not Found...");
48 | $render('404', compact('exception', 'params'));
49 | });
50 | ```
51 |
52 | If you've got more than one type of exception you want to handle, just add more calls to `apply()` in your error bootstrap file.
53 |
--------------------------------------------------------------------------------
/09_common_tasks/08_console_applications.md:
--------------------------------------------------------------------------------
1 | # Console Applications
2 |
3 | Console applications in li3 allow you to access your application
4 | infrastructure from the command line. The `Console` libraries li3
5 | features also allow you to perform oft-used features in a shell
6 | environment.
7 |
8 | # The `li3` Shell Command
9 |
10 | Before we get started, you'll want to make sure that your shell knows
11 | where the `li3` command is.
12 |
13 | If my li3 installation was at `/usr/local/lithium`, I'd add the
14 | following to my Bash configuration to make sure the `li3` command was
15 | universally available to me:
16 |
17 | ```bash
18 | PATH=$PATH:/usr/local/lithium/console
19 | ```
20 |
21 | # Creating a new Command
22 |
23 | User-defined commands are located in `/app/extensions/command/` by
24 | default. Let's create a simple application that list the repositories of
25 | a given organization.
26 |
27 | First, create the new file at `/app/extensions/command/Repos.php`. This
28 | is what we'll start with:
29 |
30 | ```php
31 | namespace app\extensions\command;
32 |
33 | class Repos extends \lithium\console\Command {}
34 | ```
35 |
36 | If you run `$ li3` now, you'll see that li3 can already see your
37 | command. Look towards the end of the output, just after "COMMANDS via
38 | app."
39 |
40 | ```text
41 | COMMANDS via app
42 | repos
43 | ```
44 |
45 | If you document the Repos class with a comment, it will be shown when
46 | li3 is executed without any arguments.
47 |
48 | # Command I/O
49 |
50 | By default, the `run()` method of your newly created Command is called
51 | when a user executes `li3 ` from a shell. The easiest way
52 | to get input from a user is via command-line arguments. Arguments passed
53 | to the Command are supplied as arguments to `run()`.
54 |
55 | ```php
56 | namespace app\extensions\command;
57 |
58 | class Repos extends \lithium\console\Command {
59 |
60 | public function run($org = '') {
61 | echo "Org: $org\n";
62 | }
63 | }
64 | ```
65 |
66 | You can also use `in()` to ask the user for input:
67 |
68 | ```php
69 | namespace app\extensions\command;
70 |
71 | class Repos extends \lithium\console\Command {
72 | public function run($org = '') {
73 | if($org == '') {
74 | $org = $this->in("Organization?");
75 | }
76 | echo "Org: $org\n";
77 | }
78 | }
79 | ```
80 |
81 | And rather than using `echo` to send output to the user, we can use
82 | `out()` or `error()` to send to STDOUT and STDERR. Apart from adding
83 | newlines automatically, these methods can also send colored output by using style tags.
84 |
85 | ```php
86 | namespace app\extensions\command;
87 |
88 | class Repos extends \lithium\console\Command {
89 | public function run($org = '') {
90 | if($org == '') {
91 | $this->error("{:red}No organization supplied.{:end}");
92 | $org = $this->in("Organization?");
93 | }
94 | $this->out("{:green}Org: $org{:end}");
95 | }
96 | }
97 | ```
98 |
99 | # Adding Functionality
100 |
101 | Finally, let's add a bit of functionality to interact with the GitHub
102 | API. Because we have full access to li3 classes, we declare them via
103 | `uses` above the class definition like we normally would and use those
104 | classes in our Command logic:
105 |
106 | ```php
107 | namespace app\extensions\command;
108 |
109 | use lithium\net\http\Service;
110 |
111 | class Repos extends \lithium\console\Command {
112 | public function run($org = '') {
113 | if($org == '') {
114 | $this->error("{:red}No organization supplied.{:end}");
115 | $org = $this->in("Organization?");
116 | }
117 | $this->out("{:green}Org: $org{:end}");
118 |
119 | $service = new Service([
120 | 'scheme' => 'https',
121 | 'host' => 'api.github.com'
122 | ]);
123 |
124 | $repos = $service->get('/orgs/' . $org . '/repos');
125 | $this->header("$org Repos:");
126 | foreach($repos as $repo) {
127 | $this->out($repo['full_name']);
128 | }
129 | }
130 | }
131 | ```
132 |
133 | Here's a sample of the output:
134 |
135 | ```bash
136 | $ li3 repos UnionOfRad
137 | ```
138 |
139 | ```text
140 | Org: UnionOfRad
141 | /-----------------
142 | UnionOfRad Repos:
143 | /-----------------
144 | UnionOfRAD/phpca
145 | UnionOfRAD/li3_sqlsrv
146 | UnionOfRAD/framework
147 | UnionOfRAD/li3_docs
148 | UnionOfRAD/lithium_bin
149 | UnionOfRAD/li3_design
150 | UnionOfRAD/manual
151 | UnionOfRAD/lithium
152 | UnionOfRAD/li3_qa
153 | UnionOfRAD/li3_cldr
154 | UnionOfRAD/li3_lab
155 | UnionOfRAD/li3_bot
156 | UnionOfRAD/li3_lldr
157 | UnionOfRAD/li3_quality
158 | UnionOfRAD/li3_queue
159 | UnionOfRAD/sphere
160 | UnionOfRAD/li3_couchbase
161 | UnionOfRAD/li3_sqltools
162 | UnionOfRAD/li3_fixtures
163 | ```
164 |
--------------------------------------------------------------------------------
/09_common_tasks/09_plugins.md:
--------------------------------------------------------------------------------
1 | # Creating a Plugin
2 |
3 | If you've written a piece of functionality that you want to share among multiple applications, or distribute within the li3 community, you'll want to create a _plugin_. The good news is, if you've already created a li3 application, you're more than half way there.
4 |
5 | First, to understand how li3 deals with plugins, it's important to understand how it deals with _libraries_, so make sure you've read "[Working with 3rd Party Libraries](../configuration/third-party-libraries)". In li3, everything is a library, including the li3 core, other frameworks, your application, and of course, plugins. By convention, plugins are simply libraries that conform to the same organizational conventions as a li3 application.
6 |
7 | ## Requirements
8 |
9 | While there are very few (really no) _hard_ requirements for li3 plugins, there are several very strong recommendations. First and foremost is that you follow the namespacing standards outlined in the [`Libraries` class documentation](/docs/api/lithium/latest:1.x/lithium/core/Libraries). Second, your plugin should include a `config` directory containing a `bootstrap.php` file. Finally, as mentioned above, it's best to conform to the same directory structure as a li3 application. And of course, classes must be namespaced accordingly, with the root namespace matching the name of the plugin. (An exception to this would be if you were using an organizational or vendor-level namespace within which your plugin lived, in which case the plugin's root namespace would look like `vendor\plugin_name`).
10 |
11 | ## What Can Plugins Include?
12 |
13 | Because li3 treats everything as libraries, and because all libraries are essentially on the same playing field, a plugin can include anything. Any type of class or resource that could be included in the li3 core or in an application can be included in a plugin. This includes models, controllers, helpers, adapters, generic classes, even custom routes and web assets.
14 |
15 | Common examples of things to include in plugins are adapters for cache engines, databases or web services, or a collection of models to share between multiple applications that use the same data. Other plugins introduce new classes with new functionality which can have their own types of adapters, such as the [`li3_access` plugin](https://github.com/tmaiaroto/li3_access), which adds access control support. Some plugins, like [`li3_twig`](https://github.com/UnionOfRAD/li3_twig), [`li3_doctrine`](https://github.com/mariano/li3_doctrine2) and [`li3_pdf`](https://github.com/UnionOfRAD/li3_queue), wrap other libraries to provide tight integration with li3.
16 |
17 | ## Creating Your First Plugin
18 |
19 | You can create a plugin simply by running the following from any directory, substituting `my` for whatever you want to call it. This creates a new plugin from li3's default plugin template.
20 |
21 | ```
22 | git clone https://github.com/UnionOfRAD/li3_plugin li3_my
23 | ```
24 |
25 | ## Installing and Configuring
26 |
27 | As with other libraries, plugins are installed in your local or system `libraries` directory by cloning, symlinking, etc. (Again, see "[Working with 3rd Party Libraries](../configuration/third-party-libraries)"). An important thing to know when developing plugins is that when your library is registered through [`Libraries::add()`](/docs/api/lithium/latest:1.x/lithium/core/Libraries::add()), it can also be configured with custom settings.
28 |
29 | While `add()`'s `$config` parameter specifies several default configuration settings, it is also possible to pass any other arbitrary information that you may wish to use in your plugin. This data can then be retrieved within your plugin by calling `Libraries::get('plugin_name')` or `Libraries::get('plugin_name', 'key')`.
30 |
31 | ## Routes and Web Assets
32 |
33 | As mentioned above, your plugin can also include custom routing and web assets. By default, these are both based on filters that are registered in the primary application's bootstrap files. To handle routing, you'll find a default filter in `config/bootstrap/action.php`, which iterates over all registered libraries in reverse order, looking for `config/routes.php` files to include. While the application can easily customize this behavior, by default, simply including `config/routes.php` in your plugin will automatically register your custom routes with the application.
34 |
35 | Likewise with static assets, such as JavaScript, images and CSS files, they are loaded from plugins using a filter which can be found in `config/bootstrap/media.php`. By default, this is commented out in the bootstrap of the primary application, so it should first be enabled. Then, assets can be loaded from the `webroot` directory of the plugin (this directory can be overridden by passing a custom value to the `'webroot'` key in `Libraries::add()`).
36 |
37 | While this works simply in development, the performance is not as good as direct filesystem access. In production, it's better to create a symlink from `libraries/plugin_name/webroot` to `main_app/webroot/plugin_name`.
38 |
--------------------------------------------------------------------------------
/09_common_tasks/10_etags.md:
--------------------------------------------------------------------------------
1 | # ETag Everything, Everything ETagg'ed
2 |
3 | The first goal of this guide is to show you how relatively easy adding web caching support to a li3 application is. The second goal is to introduce you to the topic of web caching in general, showing what makes it so interesting.
4 |
5 | The beneficial effects of web caching are most often underestimated but adding even just basic support to an existing application is a guaranteed win for everybody involved.
6 |
7 | **Embrace the web!**
8 |
9 | Web caching is both simple and complex. As we don't want to put the cart before the horse, we start simple first. This article assumes that you have a basic understanding of HTTP and applies several simplifications. A full list is presented at the end to give additional pointers for further reading.
10 |
11 | ## Entity Tags
12 |
13 | _Validation_ is one aspect of web caching. With it comes the aspect of _conditional requests_. A typical request/response flow involving web caching and entity tags might look as simple as in the following chart.
14 |
15 | 
16 |
17 | The _entity tag_ (also _ETag_ as the HTTP header) is a so called cache validator. In contrast to other validators like _Last-Modified_, entity tags prove to be **very flexible**. They are perfect for nearly any aspect of an application, generating both dynamic and static content: _a typical web application_.
18 |
19 | Entity tags are like fingerprints of the underlying resource of an URL and change when its content changes. They must only be unique in the scope of that URL. The tag itself can practically be any kind of string - most often this is a hash over _some_ part of the resource.
20 |
21 | Now it is important which parts of the resource we choose to generate the hash over. As a last resort this could be as close to the response as computing the hash over the actual body to be sent. Or - and this is much better as we can move the decision of not returning the full response up - generate the hash over the data that is used in rendering the body. Sometimes this work has already been done for you: reuse existing checksums, unique IDs or other modification signals.
22 |
23 | In the following three sections we'll visit three different parts of a li3 application and apply basic web caching for: serving files, serving dynamic content, retrieving arbitrary data from a web service.
24 |
25 | ## Serving Files
26 |
27 | One of the benefits when using MongoDB is that you also get a nice place to store your files. A good example on how and why to do this is [Nate's photobolog tutorial](https://github.com/nateabele/photoblog). This code presented here actually builds off the ideas presented in that tutorial.
28 |
29 | While there are many good things that come with storing you files this way, one downside is that in order to respond to file requests now involves PHP and the database in addition to the web server. To compensate this overhead we'll modify the route handler used for serving the files to use web caching.
30 |
31 | We setup our `Files` model to use GridFs and also add a handy but stubbed `mimeType()` [instance method](../models/adding-functions-to-models). You really want to implement real MIME-type detection here. Either go directly with the [fileinfo extension](http://php.net/manual/en/intro.fileinfo.php) or and alternative solution like [the mm\Mime\Type class](https://github.com/davidpersson/mm/blob/master/src/Mime/Type.php).
32 |
33 | ```php
34 | // models/Files.php
35 |
36 | class Files extends \lithium\data\Model {
37 | protected $_meta = ['source' => 'fs.files'];
38 |
39 | public function mimeType($entity) {
40 | return 'image/png';
41 | }
42 | }
43 | ```
44 |
45 | Now this is where we actually serve the file. We'll be reusing the `md5` field as the entity tag. MongoDB automatically adds that field as the checksum over the contents of the file. The entity tag is **wrapped in quotes** so be sure to strip them off before comparing. Also we must include the entity tag on **both** 200 and 304 responses.
46 |
47 | ```php
48 | // config/bootstrap/routes.php
49 |
50 | use lithium\net\http\Router;
51 | use lithium\action\Response;
52 | use app\models\Files;
53 |
54 | Router::connect('/files/{:id:[0-9a-f]{24}}.{:type}', [], function($request) {
55 | if (!$file = Files::first($request->id)) {
56 | return new Response(['status' => 404]);
57 | }
58 | $response = new Response();
59 |
60 | $hash = $file->md5;
61 | $condition = trim($request->get('http:if_none_match'), '"');
62 |
63 | $response->headers['ETag'] = "\"{$hash}\"";
64 |
65 | if ($condition === $hash) {
66 | $response->status(304);
67 | } else {
68 | $response->headers += [
69 | 'Content-Length' => $file->file->getSize(),
70 | 'Content-Type' => $file->mimeType(),
71 | ];
72 | $response->body = $file->file->getBytes();
73 | }
74 | return $response;
75 | });
76 | ```
77 |
78 | As you will see the pattern of generating, adding and comparing the entity tag will not change much throughout the next example below and basically stays the same.
79 |
80 | ## Serving Dynamic Content
81 |
82 | This example will enable caching for any possibly dynamically generated content by adding a filter to `Dispatcher::run()`. While this is a pretty bulletproof it is also not the most performant solution as we are generating the entity tag over the full body which in turn needs to be always rendered.
83 |
84 | A better solution would be to generate the tag over the data used in the body (see above) and intercept the cycle right before the template is fully rendered. Feel free to post your solution in the comments.
85 |
86 | ```php
87 | // config/bootstrap/action.php
88 |
89 | use lithium\action\Dispatcher;
90 |
91 | Dispatcher::applyFilter('run', function($self, $params, $chain) {
92 | $request = $params['request'];
93 | $response = $chain->next($self, $params, $chain);
94 |
95 | $hash = md5($response->body());
96 | $condition = trim($request->get('http:if_none_match'), '"');
97 |
98 | $response->headers['ETag'] = "\"{$hash}\"";
99 |
100 | if ($condition === $hash) {
101 | $response->status(304);
102 | $response->body = [];
103 | }
104 | return $response;
105 | });
106 | ```
107 |
108 | ## Interacting with a Service
109 |
110 | In this case we are retrieving the latest commits from the li3 GitHub repository. As GitHub has a pretty tight rate limit in place this seems to be a good idea.
111 |
112 | ```php
113 | use lithium\data\Connections;
114 | use lithium\storage\Cache;
115 |
116 | Connections::add("github", [
117 | 'scheme' => 'https',
118 | 'type' => 'lithium\net\http\Service',
119 | 'host' => 'api.github.com'
120 | ]);
121 | $github = Connections::get("github");
122 | $path = 'repos/unionofrad/lithium/commits';
123 |
124 | $cacheKey = 'api_github_com_' . str_replace('/', '_', $path);
125 |
126 | $options = ['return' => true];
127 | if ($cached = Cache::read('default', $cacheKey)) {
128 | $options['headers']['If-None-Match'] = "\"{$cached['condition']}\"";
129 | }
130 | $response = $github->get($path, ['type' => 'json'], $options);
131 |
132 | if ($response->status('code') == 304) {
133 | return $cached['body'];
134 | }
135 | $condition = trim($response->headers['ETag'], '"');
136 | $body = $response->body();
137 |
138 | Cache::write('default', $cacheKey, compact('condition', 'body'));
139 | return $body;
140 | ```
141 |
142 | This time we act as the client to the GitHub service, which now has to handle all the entity tag generation and validation. On the other hand we are burdened with the task of providing persistent caching of the response and the entity tag that comes with.
143 |
144 | ## Sources
145 |
146 | * [RFC 2616 - Caching in HTTP](http://tools.ietf.org/html/rfc2616#section-13)
147 | * [RFC 2616 - ETag](http://tools.ietf.org/html/rfc2616#section-14.44)
148 | * [RFC 2616 - If None Match](http://tools.ietf.org/html/rfc2616#section-14.26)
149 | * [MongoDB GridFS](http://www.mongodb.org/display/DOCS/GridFS+Specification)
150 | * [Wikipedia on HTTP ETag](http://en.wikipedia.org/wiki/HTTP_ETag)
151 | * [Design for the web](http://www.dehora.net/journal/2007/07/earned_value.html)
152 | * [About: Design for the web](http://www.tbray.org/ongoing/When/200x/2007/07/31/Design-for-the-Web)
153 | * [Pitfalls of ETags](http://www.mnot.net/blog/2007/08/07/etags)
154 | * [Things caches do](http://tomayko.com/writings/things-caches-do)
155 | * [Controlling Caches/Tutorial](http://www.mnot.net/cache_docs/)
156 | * [PHP Fileinfo](http://php.net/manual/en/intro.fileinfo.php)
157 | * [MM - the PHP media library](https://github.com/davidpersson/mm)
158 | * [RFC 2616 - Vary](http://tools.ietf.org/html/rfc2616#section-14.44)
159 | * [RFC 2616 - Cache-Control](http://tools.ietf.org/html/rfc2616#section-14.19)
160 | * [How to compare Weak Entity Tags](http://www.w3.org/Protocols/HTTP/1.1/rfc2616bis/issues/#i71)
161 | * [Problems with Entity Tags On Write](http://greenbytes.de/tech/webdav/draft-reschke-http-etag-on-write-latest.html)
162 |
163 |
--------------------------------------------------------------------------------
/09_common_tasks/README.md:
--------------------------------------------------------------------------------
1 | # Common Tasks
2 |
--------------------------------------------------------------------------------
/11_appendices/01_faqs.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: FAQs
3 | ---
4 |
5 | # Frequently Asked Questions
6 |
7 | ### PHP short tags in templates? Haven't those been deprecated for, like, ever?
8 |
9 | You may have noticed that li3 templates use PHP short tags (i.e. `=`). Don't worry though, these aren't actually interpreted by PHP. Instead, li3 uses an internal template compiler to change these tags into full `` statements, with the added benefit of automatic content escaping, so you don't have to think about XSS attacks. Also, the compiler is smart enough to recognize content coming from helpers, etc., so it won't double-escape. For more info, check out the documentation on [the templating system](/docs/api/lithium/latest:1.x/lithium/template).
10 |
11 | ### You guys use statics all over the place. Aren't those really hard to test?
12 |
13 | Strictly speaking, statics are quite easy to test. li3 borrows several concepts from _functional programming_ in order to ensure highly testable code throughout the entire framework, as well as applications built on top of it. In order to understand some of these concepts, first we have to define a few terms:
14 |
15 | _State_: An essential part of writing software, [Wikipedia defines state](http://en.wikipedia.org/wiki/Program_state) as "a snapshot of the measure of various conditions in the system". When developing PHP applications, this varies by context / scope: when the web server first loads up `index.php`, the state is defined by the data used to request the script, including GET or POST data, and any system information exposed in `$_SERVER` or `$_ENV`, as well as any other super-global variable. State can also include non-obvious things like the current date and time. Within a method, the state consists of any parameters passed in, anything within the object to which the method is bound (i.e. `$this`), and any other data defined or made available in the global scope.
16 |
17 | _Side-effect_: A concept closely related to state, _side effects_ are changes that a method or other routine makes to things outside its own scope. Side effects include things like changing the values of global variables, object properties (i.e. `$this`), modifying data in a database or filesystem, changing the value of a parameter passed by reference, or echoing output.
18 |
19 | _Mutability_: Mutability is the quality of something which is changeable. The basic incarnation of mutability in PHP is a variable or object property. By contrast, something which is _immutable_ is not changeable, i.e. a global or class-level constant. Therefore, _mutable state_ is an element of application state (see above) that can be changed. These changes are what produce side-effects. This may seem obvious, but becomes more important later on.
20 |
21 | _Referential transparency_: A quality of a method or function which prescribes that its return value can only be dependent on its parameters, and that it has no effect on the state of the program outside its own scope. Methods which are referentially transparent are very easy to test, since they do not require the setup of any outside state; a given set of parameters should always have the same outcome. Referentially transparent functions are also easy to cache, since multiple calls (with the same parameters) always return the same value. This process is known as _memoization_.
22 |
23 | Getting back to our discussion, the problems with testing statics that people typically refer to actually have to do with _mutable global state_, i.e. a static method that depends on some external (usually global) piece of information which is subject to change by outside forces. The epitome of this anti-pattern is the Singleton. The idea behind singletons is that you have one (and only one) globally-available instance of an object at any one time.
24 |
25 | The essential problem with this is that there's often no way to determine at any given time what the value of a singleton's attributes could be, because any part of your application has access to modify them. **This exemplifies the root cause of almost all software logic bugs.** The only solution is to design the singleton such that, once created, its attributes cannot be changed (i.e., make it _immutable_). Alternatively, re-design the affected architecture so that a singleton is not required.
26 |
27 | Fortunately, all static classes in li3 are either composed of _referentially transparent_ methods, or methods whose usage patterns are oriented around _immutability_. Some examples of referentially transparent methods are `lithium\util\String::insert()`, which inserts values into a template string, or `lithium\util\Inflector::camelize()`, which produces a version of a word or phrase. These functions have no external side-effects, and produce predictable output based on their parameters.
28 |
29 | Examples of immutable static classes would be any class extends [`Adaptable`](/docs/api/lithium/latest:1.x/lithium/core/Adaptable), i.e. `Cache`, `Connections`, etc. These classes model and provide access to system-level resources such as database connections, user sessions, and caching configurations. These classes are configured with information on how to access and operate on their respective resources using the `config()` method of each class. This happens once and only once, during the application's bootstrap process. Subsequent access to those classes (i.e. through the `adapter()` method) always returns the same adapter instance with the same attributes. This avoids the problem of having our application's state change out from under us.
30 |
31 | The other issue often referred to when testing statics is that they're difficult to mock, or replace dependencies. Consider the following:
32 |
33 | ```php
34 | class A {
35 | public static function foo() {
36 | // return some calculated value
37 | }
38 | }
39 |
40 | class B {
41 | public static function bar() {
42 | $result = A::foo();
43 | // perform some calculation on $result
44 | return $result;
45 | }
46 | }
47 | ```
48 |
49 | In this example, every call to `B::bar()` results in a call to `A::foo()`. It is impossible to test `B` in isolation, because it is impossible _not_ to also call `A`. In PHP 5.2 and below, there was no solution to this. However, PHP 5.3 allows "dynamic" static method calls, which enable the following:
50 |
51 | ```php
52 | class B {
53 | public static function bar($dependency) {
54 | $result = $dependency::foo();
55 | // perform some calculation on $result
56 | return $result;
57 | }
58 | }
59 | ```
60 |
61 | This allows `A` to be swapped out for another class, and makes `B` much easier to test, since `$dependency` can be the name of a mock class that can act as a control during testing. This also has the pleasant side-effect of making `B`'s design more flexible.
62 |
63 | li3 addresses this issue of dependencies in a uniform way, using the protected `$_classes` attribute. Consider the following snippet from the `lithium\action\Dispatcher` class:
64 |
65 | ```php
66 | class Dispatcher extends \lithium\core\StaticObject {
67 |
68 | // ...
69 | protected static $_classes = [
70 | 'router' => 'lithium\net\http\Router'
71 | ];
72 | // ...
73 | }
74 | ```
75 |
76 | The `Dispatcher`'s dependencies may then be dynamically configured to use a different routing class with the `config()` method. Internally, calling the `Router` looks like this:
77 |
78 | ```php
79 | $router = static::$_classes['router'];
80 | $result = $router::process($request);
81 | ```
82 |
83 | Not all dependencies are configured this way, but it is the predominant convention throughout the framework. In almost all other cases, dependencies which cannot be changed are dependencies on utility methods, which are almost always referentially transparent. Because of this, these hard-coded dependencies add no difficulty or complexity in testing.
84 |
85 | ### Why are there 'libraries' folder in both the root directory and the app directory?
86 |
87 | Some libraries are used by a single app or on an app-by-app basis. These apps store libraries in the `/app/libraries` directory. Other libraries are shared by one or more apps. The `/libraries` directory is provided to make administration of these libraries easier. There is also a (typically negligible) disk space savings as any given library isn't duplicated in the `/app/libraries` directories for multiple applications.
88 |
89 |
--------------------------------------------------------------------------------
/11_appendices/02_using_in_other_applications.md:
--------------------------------------------------------------------------------
1 | # Using in External Applications
2 |
3 | Because li3 is so flexible, you can bootstrap the core framework into any application environment, and use just the features you need. First, make sure you have installed li3 properly.
4 |
5 | To use li3 classes, simply load the class that manages libraries (`lithium\core\Libraries`), and register the li3 library itself, as follows:
6 |
7 | ```php
8 | include "/path/to/classes/libraries/lithium/core/Libraries.php";
9 | lithium\core\Libraries::add('lithium');
10 | ```
11 |
12 |
13 | Note that `"path/to/classes"` may be relative to your include path.
14 |
15 |
16 | ## Integrating with CakePHP
17 |
18 | CakePHP provides a convenient place to add this configuration: `app/config/bootstrap.php`. Additionally, if you'd like to intermingle li3 classes (models and controllers) with their CakePHP counterparts, you can add the following (again, to `bootstrap.php`):
19 |
20 | ```php
21 | define("LITHIUM_APP_PATH", dirname(__DIR__));
22 | lithium\core\Libraries::add('app', ['bootstrap' => false]);
23 | ```
24 |
25 | This correctly locates your app directory in li3's class loader, and suppresses `app`'s default bootstrap file. You may now import your namespaced app classes in your CakePHP classes, and use li3 and CakePHP models and other classes in parallel. Note that `LITHIUM_APP_PATH` should be defined _before_ `Libraries.php` is included.
26 |
27 | ## Defining data connections
28 |
29 | If you plan on using li3's data layer, you need to create `connections.php` inside `LITHIUM_APP_PATH/config`. If you are defining your connections dynamically, this file can be empty. By convention, however, this file is used to configure your database and web service connections. See [`lithium\data\Connections`](/docs/api/lithium/latest:1.x/lithium/data/Connections) for more information on how to do this.
30 |
--------------------------------------------------------------------------------
/11_appendices/README.md:
--------------------------------------------------------------------------------
1 | # Appendices
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | This project is an ongoing work, and we need your help. li3 users _new_ and _old_ are welcome and encouraged to join in the fun. If you've struggled with something, help us record and share the solution so others can find the way more easily. There are many ways you can help:
4 |
5 | * Code examples to enrich current documentation
6 | * Do lists or tables to help explain concepts
7 | * Rough notes that describe an oft-used process
8 | * Corrections (typos, inaccuracies, etc.)
9 | * Translations
10 |
11 | If you'd like to help, simply [fork the project on GitHub](https://github.com/UnionOfRAD/manual), [open a new issue](https://github.com/UnionOfRAD/manual/issues), or get in contact with one of the core team members. You can give us a shout in channel #li3 on server Freenode, or send an email to anderson.johnd at gmail dot com.
12 |
13 | ## Markup
14 |
15 | We're using GitHub markdown to render the final documents.
16 |
17 | ### Notes
18 |
19 | You add notes using the following markup:
20 |
21 | ```html
22 |
23 | Informational note here.
24 |
25 | ```
26 |
27 | There are 3 classes of notes _caution_ (class is `note-caution`), _informational_ (`note-info`)
28 | and _hints/tips_ (`note-hint`). Any code inside the divs will need to be marked up manually using
29 | `` tags.
30 |
31 | ### Versions
32 |
33 | There will be no separate manuals for minor versions. Instead features available only
34 | in since certain versions are documented as follows:
35 |
36 | ```html
37 |
This feature is available with version 1.1.
38 | ```
39 |
40 | ### Code
41 |
42 | Use tabs to indent the code inside fenced code blocks. This allows us to adjust the spacing when rendering and highlighting the code.
43 |
44 | Language hints may be used for snippets - where its hard to detect the language automatically.
45 |
46 |
47 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Union of RAD http://union-of-rad.org
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | * Neither the name of Lithium, Union of Rad, nor the names of its contributors
13 | may be used to endorse or promote products derived from this software
14 | without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
25 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "li₃: The Definitive Guide"
3 | description: "This is your handbook to building li₃ applications. It takes you through getting started, and provides an overview of all aspects of application-building that are covered by the framework."
4 | ---
5 | # li₃: The Definitive Guide
6 |
7 | So you're interested in writing tight apps with the latest RAD techniques? Welcome to li₃, the most RAD framework for PHP.
8 |
9 | This set of guides is meant for PHP developers trying to get a handle on
10 | what the framework can do, and—more importantly—what it can do for _you_. This set of
11 | guides is meant to give you a world-class tour of the framework. You'll learn
12 | how it implements and you can leverage
13 | [MVC](architecture/mvc),
14 | [data access](models/),
15 | [authentication](common-tasks/simple-authentication),
16 | authorization,
17 | [validation](models/validation),
18 | [internationalization](common-tasks/globalization),
19 | [layouts](views/layouts),
20 | [unit testing](quality-code/testing) and more. In short,
21 | you'll learn all about Rapid Application Development, li₃ style.
22 |
23 | Enough talk. Let's get started.
24 |
25 |
26 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TO DO
2 |
3 | All lists are ordered by priority.
4 |
5 | ## Guides
6 |
7 | - Entites Collections
8 | - Model Schema
9 | - Understanding Context
10 | - Dependency Injection
11 | - Advanced Filtering
12 | - Authorization Tutorial
13 | - HTTP Services
14 | - Adaptable, Environment specific settings
15 | - Example Applications
16 | - Expand Using in External Apps with Wordpress Integration
17 | - Plugins
18 | - Extending Lithium
19 |
20 | # Tutorials
21 |
22 | - Model Tutorial
23 | - Request/Response Tutorial
24 | - AJAX Tutorials - Break down further
25 | - Data Sources Tutorials - MySQL, MongoDB, etc. Break down further
26 | - Add new updated Cloud Services guide for Engine Yard
27 | - Introductory Tutorial - This tutorial covers a basic application with 1 or 2 views, 1 controller, and a basic model with no relationships.
28 | - Authentication Tutorials - Covering OpenID, OAuth, etc. Break down further
29 | - Search Tutorials - Zend_Search_Lucene, Solr, etc. Break down further
30 | - Cloud Services Tutorials - Simpler Cloud API, AWS, etc. Break down further
31 | - Launch Checklist
32 |
33 |
34 |
--------------------------------------------------------------------------------
/assets/img/default_app_welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnionOfRAD/manual/7e8d6f582d7e52444760d9e115578361864f7173/assets/img/default_app_welcome.png
--------------------------------------------------------------------------------
/assets/img/lithium_advent_caching_flow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnionOfRAD/manual/7e8d6f582d7e52444760d9e115578361864f7173/assets/img/lithium_advent_caching_flow.jpg
--------------------------------------------------------------------------------