Add reStructuredText documentation

This commit is contained in:
PJ Dietz 2015-02-27 17:54:57 -05:00
parent a2f6bc1f22
commit fcc5474114
13 changed files with 1898 additions and 2 deletions

3
.gitignore vendored
View File

@ -7,6 +7,9 @@ phpdoc/
# Code coverage report
report/
# Sphinx Documentation
docs/build
# Previewing the README.md
README.html
preview

177
docs/Makefile Normal file
View File

@ -0,0 +1,177 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WellRESTed.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WellRESTed.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/WellRESTed"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WellRESTed"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

242
docs/make.bat Normal file
View File

@ -0,0 +1,242 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
set I18NSPHINXOPTS=%SPHINXOPTS% source
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\WellRESTed.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\WellRESTed.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %BUILDDIR%/..
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %BUILDDIR%/..
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end

196
docs/source/conf.py Normal file
View File

@ -0,0 +1,196 @@
# -*- coding: utf-8 -*-
import sys
import os
from sphinx.highlighting import lexers
from pygments.lexers.web import PhpLexer
lexers['php'] = PhpLexer(startinline=True, linenos=1)
lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1)
primary_domain = 'php'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'WellRESTed'
copyright = u'2015, PJ Dietz'
version = '2.3.0'
release = '2.3.0'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'WellRESTeddoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'WellRESTed.tex', u'WellRESTed Documentation',
u'PJ Dietz', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'wellrested', u'WellRESTed Documentation',
[u'PJ Dietz'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'WellRESTed', u'WellRESTed Documentation',
u'PJ Dietz', 'WellRESTed', 'One line description of project.',
'Miscellaneous'),
]
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

View File

@ -0,0 +1,93 @@
Dependency Injection
====================
Here are a few strategies for how to do dependency injection with WellRESTed.
HandlerInterface::getResponse
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can inject dependencies into your handlers_ by passing them into ``Router::respond`` (or ``Router::getResponse``). This array will propagate through the routes_ to your handler_, possibly gaining additional array members (like variables from a TemplateRoute_) along the way.
Define a handler_ that expects to receive the dependency container as the "container" element of the array passed to ``getResponse``.
.. code-block:: php
Class CatHandler implements \pjdietz\WellRESTed\Interfaces\HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
// Extract the container from the second parameter.
$container = $args["container"];
// Do something with the container, and make a response.
// ...
return $response;
}
}
Create the router. Pass the the container to ``Router::respond`` as the "container" array element.
.. code-block:: php
$container = new MySuperCoolDependencyContainer();
$router = new \pjdietz\WellRESTed\Router();
$router->add("/cats", "CatHandler");
// Pass an array containing the dependencies to Router::respond().
$router->respond(["container" => container]);
Callables
^^^^^^^^^
When using callables to provide handlers_, you have the opportunity to inject dependencies into the handler's constructor.
.. code-block:: php
Class CatHandler implements \pjdietz\WellRESTed\Interfaces\HandlerInterface
{
private $container;
public function __construct($container)
{
$this->container = $container;
}
public function getResponse(RequestInterface $request, array $args = null)
{
// Do something with the $this->container, and make a response.
// ...
return $response;
}
}
Create the router. Pass the the container to the handler upon instantiation.
.. code-block:: php
$container = new MySuperCoolDependencyContainer();
$router = new Router();
$router->add("/cats/", function () use ($container) {
return new CatHandler($container);
});
$router->respond();
For extra fun (and more readable code), you could store the callable that provides the handler in the container. Here's an example using Pimple_).
.. code-block:: php
$c = new Pimple\Container();
$c["catHandler"] = $c->protect(function () use ($c) {
return new CatHandler($c);
});
$router = new Router();
$router->add("/cats/", $c["catHandler"]);
$router->respond();
.. _Handler: Handlers_
.. _Handlers: handlers.html
.. _Pimple: http://pimple.sensiolabs.org
.. _Routes: routes.html
.. _TemplateRoute: routes.html#template-routes

View File

@ -0,0 +1,302 @@
Getting Started
===============
This page provides a brief introduction to WellRESTed. We'll start with a `Hello, world!`_, take a quick peek at `using handlers`_, and finally explore reacting to `HTTP methods`_.
Hello, World!
^^^^^^^^^^^^^
Let's start with a very basic "Hello, world!". Here, we will create a router_ that will look for requests to ``/hello`` and respond with "Hello, world!"
.. code-block:: php
<?php
use pjdietz\WellRESTed\Response;
use pjdietz\WellRESTed\Router;
require_once "vendor/autoload.php";
// Create a router.
$router = new Router();
// Add a route that matches the path "/hello" and returns a response.
$router->add("/hello", function () {
$response = new Response();
$response->setStatusCode(200);
$response->setHeader("Content-type", "text/plain");
$response->setBody("Hello, world!");
return $response;
});
// Read the request sent to the server and use it to output a response.
$router->respond();
In this example, we created a router, then added one "route" to it. This route matches requests with the path ``/hello`` and dispatches them to a callable. The callable builds and returns a response.
Reading from the Request
------------------------
This is a good start, but it's not very dynamic. Rather than always responding with the same message, let's respond with a greeting using some information from the request's query.
.. code-block:: php
<?php
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Response;
use pjdietz\WellRESTed\Router;
require_once "vendor/autoload.php";
$router = new Router();
// Add a route that matches the path "/hello" and returns a response.
$router->add("/hello", function (RequestInterface $request) {
// Provide a default.
$name = "world";
// Read from the query.
$query = $request->getQuery();
if (isset($query["name"])) {
$name = $query["name"];
}
$response = new Response();
$response->setStatusCode(200);
$response->setHeader("Content-type", "text/plain");
$response->setBody("Hello, $name!");
return $response;
});
$router->respond();
With this router, requests for ``/hello?name=Molly`` will get the response "Hello, Molly" while requests for ``/hello?name=Oscar`` will get "Hello, Oscar!"
The callable we passed to ``$router->add()`` receives a variable called ``$request`` that represents the request being dispatched. The ``RequestInterface`` provides access to information such as the request method (e.g., "GET", "POST", "PUT", "DELETE"), headers, the entity body, and the query.
Reading from the Path
---------------------
Instead of specifying the name for the greeting in the query, let's modify our API to look for the name in the path so that a request to ``/hello/Molly`` will provide a "Hello, Molly!" response.
.. code-block:: php
<?php
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Response;
use pjdietz\WellRESTed\Router;
require_once "vendor/autoload.php";
$router = new Router();
// Add a route that matches the path "/hello/" followed by a name.
// The route's handler will return a personalized greeting.
$router->add("/hello/{name}", function (RequestInterface $request, array $args) {
// The part of the path where {name} appear will be extracted
// and provided to the callable as the "name" array element.
$name = $args["name"];
$response = new Response();
$response->setStatusCode(200);
$response->setHeader("Content-type", "text/plain");
$response->setBody("Hello, $name!");
return $response;
});
$router->respond();
Notice that the first parameter passed to ``$router->add()`` is now "/hello/{name}". The curly braces define a variable that will match text in that section of the path and provide it to the callable by adding an array element. Since "name" appears inside the braces, "name" will be the key; the matched text ("Molly" for requests to ``/hello/Molly``) will be the value.
To learn more about extracting variables from the path, see TemplateRoutes_ and RegexRoutes_.
Using Handlers
^^^^^^^^^^^^^^
So far, the examples have been limited to building the entire Web service inside a single ``index.php`` file. For an actual site, you'll want to spread your code across many files.
Let's start by replacing the callable we used above with a handler. In WellRESTed, a "handler" is a piece of middleware that takes a request and provides a response. We used callables as informal handlers above, but the more formal approach is to use HandlerInterface_.
.. code-block:: php
<?php
namespace MyApi;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Response;
class HelloHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$name = $args["name"];
$response = new Response();
$response->setStatusCode(200);
$response->setHeader("Content-type", "text/plain");
$response->setBody("Hello, $name!");
return $response;
}
}
When we add the route to the router, we can specify the handler by providing a string containing the handler's fully qualified class name (FQCN). When the router receives a request that matches the ``/hello/{name}`` path, it will instantiate ``MyApi\HelloHandler`` and use the instance to get a response.
Here's the updated ``index.php``
.. code-block:: php
<?php
use pjdietz\WellRESTed\Router;
require_once "vendor/autoload.php";
$router = new Router();
$router->add("/hello/{name}", "\\MyApi\\HelloHandler");
$router->respond();
HTTP Methods
^^^^^^^^^^^^
The examples so far have not touched on HTTP methods—the "Hello, world!" handlers give the same responses for GET, POST, PUT, etc.
Let's set aside "Hello, world!" for now, and imagine tiny RESTful API about cats. We'll start with a ``/cats/`` endpoint that should allow these requests:
``GET /cats/``
Output a list of cat representations in JSON.
``POST /cats/``
Accept a JSON representation of a cat and store it.
``OPTIONS /cats/``
List the methods the endpoint allows.
(We won't actually store any cats here. The example is just to show how the HTTP parts work.)
HandlerInterface
----------------
We can react to the verbs using a class implementing HandlerInterface_ like this:
.. code-block:: php
namespace MyApi;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Response;
class CatHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$response = new Response();
// Determine how to respond based on the request's HTTP method.
$method = $request->getMethod();
if ($method === "GET") {
// Read the list of cats.
$cats = $this->getCats();
$response->setStatusCode(200);
$response->setHeader("Content-type", "application/json");
$response->setBody(json_encode($cats));
} elseif ($method === "POST") {
// Read the cat from the request body.
$cat = json_decode($request->getBody());
// Store it, and read the updated representation.
$newCat = $this->storeCat($cat);
$response->setStatusCode(201);
$response->setHeader("Content-type", "application/json");
$response->setBody(json_encode($newCat));
} elseif ($method === "OPTIONS") {
// List the methods are allowed for this endpoint.
$response->setStatusCode(200);
$response->setHeader("Allow", "GET,HEAD,POST,OPTIONS");
} else {
// Request did not use one of the allowed verbs.
$response->setStatusCode(405);
$response->setHeader("Allow", "GET,HEAD,POST,OPTIONS");
}
return $response;
}
private function getCats()
{
// ...Read cats from storage...
}
private function storeCat($cat)
{
// ...Store cats here...
}
}
Handler Class
-------------
Not bad, but that's a lot of branching. We can clean this up a bit by deriving our ``CatHandler`` from `pjdietz\\WellRESTed\\Handler`__, an abstract class that implements ``HandlerInterface`` and provides protected methods for you to override that correspond with HTTP methods. Here's ``CatHandler`` refactored as a Handler_ subclass.
__ Handler_
.. code-block:: php
<?php
namespace MyApi;
use pjdietz\WellRESTed\Handler;
class CatHandler extends Handler
{
protected function get()
{
// Read the list of cats.
$cats = $this->getCats();
$this->response->setStatusCode(200);
$this->response->setHeader("Content-type", "application/json");
$this->response->setBody(json_encode($cats));
}
protected function post()
{
// Read the cat from the request body.
$cat = json_decode($this->request->getBody());
// Store it, and read the updated representation.
$newCat = $this->storeCat($cat);
$this->response->setStatusCode(201);
$this->response->setHeader("Content-type", "application/json");
$this->response->setBody(json_encode($newCat));
}
protected function getAllowedMethods()
{
return ["GET","HEAD","POST","OPTIONS"];
}
private function getCats()
{
// ...Read cats from storage...
}
private function storeCat($cat)
{
// ...Store cats here...
}
}
Using Handler_, we override the methods for verbs we want to support; all other will automatically respond ``405 Method Not Allowed``. In addition, returning an array of verbs from ``getAllowedMethods`` adds an ``Allow`` header for ``405 Method Not Allowed`` responses, and automatically provides for ``OPTIONS`` support.
Note that when using Handler_, you interact with the request and response through the protected instance members ``$this->request`` and ``$this->response``. The array provided to ``HandlerInterface:getResponse`` as the second argument is available as ``$this->args``.
.. _TemplateRoutes: routes.html#template-routes
.. _RegexRoutes: routes.html#regex-routes
.. _router: router.html
.. _HandlerInterface: handlers.html#handlerinterface
.. _Handler: handlers.html#handler-class

234
docs/source/handlers.rst Normal file
View File

@ -0,0 +1,234 @@
Handlers
========
Handlers serve as bridges between requests and responses. A handler is where you will do things such as retrieve a representation to send as the response or read a representation submitted with a request.
HandlerInterface
^^^^^^^^^^^^^^^^
The HandlerInterface_ is used throughout WellRESTed. It's a very simple and has just one method:
.. code-block:: php
/**
* Return the handled response.
*
* @param \pjdietz\WellRESTed\RequestInterface $request The request to respond to.
* @param array|null $args Optional additional arguments.
* @return \pjdietz\WellRESTed\ResponseInterface The handled response.
*/
public function getResponse(RequestInterface $request, array $args = null);
To create a handler, define a class that implements this interface. Use the ``getResponse`` method to inspect the request and return an appropriate response.
.. code-block:: php
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Response;
class HelloHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$response = new Response();
$response->setStatusCode(200);
$response->setHeader("Content-type", "text/plain");
$response->setBody("Hello, world!");
return $response;
}
}
Request
-------
``getResponse()`` receives two arguments when it is called. The first argument represents the request. The handler can use this to read information such as the HTTP method, headers, query parameters, and entity body.
Here's a handler that reads a representation sent with a ``POST`` request.
.. code-block:: php
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Response;
class DogHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$response = new Response();
// Read the request method.
$method = $request->getMethod();
if ($method === "POST") {
// Read the request body.
$dog = json_decode($request->getBody());
// ...Store the representation...
// Provide the response.
$response = new Response();
$response->setStatusCode(201);
$response->setHeader("Content-type", "application/json");
$response->setBody(json_encode($newDog);
} elseif ($method === "OPTIONS") {
// List the methods are allowed for this endpoint.
$response->setStatusCode(200);
$response->setHeader("Allow", "POST,OPTIONS");
} else {
// Request did not use one of the allowed verbs.
$response->setStatusCode(405);
$response->setHeader("Allow", "POST,OPTIONS");
}
return $response;
}
}
Array
-----
The second argument is an array of extra data such as variables from a TemplateRoute_ or captures from a RegexRoute_.
Suppose you want an endpoint that will represent one specific cat by ID which the user can read using a ``GET`` request. The endpoint should match requests for paths such as ``/cats/123`` or ``/cats/2``.
Use a TemplateRoute_ to extract the ID from the path.
.. code-block:: php
// Use a TemplateRoute to extract the ID from the path.
$router->add("/cats/{id}", "\\MyApi\\CatItemHandler");
The extracted ID will be made available to the handler as an array element in the second argument sent to ``getResponse``.
.. code-block:: php
namesapce MyApi;
Class CatItemHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$response = new Response();
// Determine how to respond based on the request's HTTP method.
$method = $rqst->getMethod();
if ($method === "GET") {
// Lookup the cat using the ID from the path.
$cat = $this->getCatById($args["id"]));
if ($cat) {
// Respond with a representation.
$response->setStatusCode(200);
$response->setHeader("Content-type", "application/json");
$response->setBody(json_encode($cat));
} else {
// Or, a Not Found error if there's no cat with that ID.
$response->setStatusCode(404);
}
} elseif ($method === "OPTIONS") {
// User wants to know what verbs are allowed for this endpoint.
$response->setStatusCode(200);
$response->setHeader("Allow", "GET,HEAD,OPTIONS");
} else {
// User did not use one of the allowed verbs.
$response->setStatusCode(405);
$response->setHeader("Allow", "GET,HEAD,OPTIONS");
}
return $response;
}
private getCatById($id)
{
// ... Lookup the cat from storage and return it.
}
}
Handler Class
^^^^^^^^^^^^^
When you write your handlers, you can either write them from scratch and implement ``HandlerInterface`` as we did above, or you can extend the abstract ``Handler`` class which provides some convenient features such as instance members for the request and response and methods for the most common HTTP verbs.
Instance Members
----------------
============ ==================== ===================================================================
Member Type Description
============ ==================== ===================================================================
``args`` ``array`` Hash to supplement the request; usually path variables.
``request`` ``RequestInterface`` The HTTP request to respond to.
``response`` ``Response`` The HTTP response to send based on the request.
============ ==================== ===================================================================
HTTP Verbs
----------
Most of the action takes place inside the methods called in response to specific HTTP verbs. For example, to handle a ``GET`` request, implement the ``get`` method.
.. code-block:: php
class CatsCollectionHandler extends \pjdietz\WellRESTed\Handler
{
protected function get()
{
// Read some cats from storage.
// ...read these an array as the variable $cats.
// Set the values for the instance's response member. This is what the
// Router will eventually output to the client.
$this->response->setStatusCode(200);
$this->response->setHeader("Content-Type", "application/json");
$this->response->setBody(json_encode($cats));
}
}
Implement the methods that you want to support. If you don't want to support ``POST``, don't implement it. The default behavior is to respond with ``405 Method Not Allowed`` for most verbs.
The methods available to implement are:
=========== =========== ===========================================
HTTP Verb Method Default Behavior
=========== =========== ===========================================
``GET`` ``get`` 405 Method Not Allowed
``HEAD`` ``head`` Call ``get``, then clean the response body
``POST`` ``post`` 405 Method Not Allowed
``PUT`` ``put`` 405 Method Not Allowed
``DELETE`` ``delete`` 405 Method Not Allowed
``PATCH`` ``patch`` 405 Method Not Allowed
``OPTIONS`` ``options`` Add ``Allow`` header, if able
=========== =========== ===========================================
OPTIONS and Allow
-----------------
If you wish to provide a list of verbs that the endpoint supports, you can do this by redefining ``getAllowedMethods`` and returning an array of verbs. The handler will use this list to provide an ``Allow`` header when responding to ``OPTIONS`` requests or to requests for verbs the handler does not allow.
.. code-block:: php
protected function getAllowedMethods()
{
return ["GET", "HEAD", "POST", "OPTIONS"];
}
An ``OPTIONS`` request handled by this handler will now get a response similar to this:
.. code-block:: http
HTTP/1.1 200 OK
Allow: GET, HEAD, POST, OPTIONS
A ``POST`` request's response will look like this:
.. code-block:: http
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, POST, OPTIONS
.. _Dependency Injection: dependency-injection.html
.. _TemplateRoute: routes.html#template-routes
.. _RegexRoute: routes.html#regex-routes
.. _Routers: router.html

17
docs/source/index.rst Normal file
View File

@ -0,0 +1,17 @@
WellRESTed
==========
WellRESTed is a micro-framework for creating RESTful Web services in PHP. It provides a lightweight yet powerful routing system and classes to make working with HTTP requests and responses clean and easy.
Contents:
.. toctree::
:maxdepth: 4
overview
getting-started
router
routes
handlers
dependency-injection
web-server-configuration

56
docs/source/overview.rst Normal file
View File

@ -0,0 +1,56 @@
Overview
========
Installation
^^^^^^^^^^^^
The recommended method for installing WellRESTed is to use the PHP dependency manager Composer_. Add an entry for WellRESTed in your project's ``composer.json`` file.
.. code-block:: js
{
"require": {
"pjdietz/wellrested": "~2.3"
}
}
Requirements
^^^^^^^^^^^^
- PHP 5.3.0
- `PHP cURL`_ for making requests with the ``Client`` class (Optional)
.. note::
While WellRESTed will work on PHP 5.3.0, the unit tests require 5.4.0 as
some make use of the PHP built-in web server.
License
^^^^^^^
Licensed using the `MIT license <http://opensource.org/licenses/MIT>`_.
The MIT License (MIT)
Copyright (c) 2015 PJ Dietz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
.. _Composer: http://getcomposer.org/
.. _PHP cURL: http://php.net/manual/en/book.curl.php

341
docs/source/router.rst Normal file
View File

@ -0,0 +1,341 @@
Router
======
A router organizes the components of a site by associating URI paths with handlers_. When the router receives a request, it examines the request's URI, determines which "route" matches, and dispatches the associated handler_. The handler_ is then responsible for reacting to the request and providing a response.
A typical WellRESTed Web service will have a single point of entry (usually ``/index.php``) that the Web server directs all traffic to. This script instantiates a ``Router``, populates it with routes_, and dispatches the request. Here's an example:
.. code-block:: php
<?php
use pjdietz\WellRESTed\Response;
use pjdietz\WellRESTed\Router;
require_once "vendor/autoload.php";
// Create a new router.
$router = new Router();
// Populate the router with routes.
$router->add(
["/", "\\MyApi\\RootHandler"],
["/cats/", "\\MyApi\\CatHandler"],
["/dogs/*", "\\MyApi\\DogHandler"],
["/guinea-pigs/{id}", "\\MyApi\\GuineaPigHandler"],
["~/hamsters/([0-9]+)~", "\\MyApi\\HamsterHandler"]
);
// Output a response based on the request sent to the server.
$router->respond();
Adding Routes
^^^^^^^^^^^^^
Use the ``Router::add`` method to associate a URI path with a handler_.
Here we are specifying that requests for the root path ``/`` should be handled by the class with the name ``MyApi\RootHandler``.
.. code-block:: php
$router->add("/", "\\MyApi\\RootHandler");
You can add routes individually, or you can add multiple routes at once. When adding multiple routes, pass a series of arrays to ``Router::add`` where each array's first item is the path and the second is the handler_.
.. code-block:: php
$router->add(
["/", "\\MyApi\\RootHandler"],
["/cats/", "\\MyApi\\CatHandler"],
["/dogs/*", "\\MyApi\\DogHandler"],
["/guinea-pigs/{id}", "\\MyApi\\GuineaPigHandler"],
["~/hamsters/([0-9]+)~", "\\MyApi\\HamsterHandler"]
);
.. note::
WellRESTed provides several types of routes including routes that match paths by regular expressions and routes that match by URI templates. See Routes_ to learn more about the different types of routes available.
Specifying Handlers
^^^^^^^^^^^^^^^^^^^
When the router finds a route that matches the request, it dispatches the associated handler_ (typically a class that implements HandlerInterface_). When adding routes (or `error handlers`_), you can specify the handler_ to dispatch in a number of ways.
.. _error handlers: `Error Handling`_
Fully Qualified Class Name (FQCN)
---------------------------------
Specify a class by FQCN by passing a string as the second parameter.
.. code-block:: php
$router->add("/cats/{id}", "\\MyApi\\CatHandler");
Handlers_ specified by FQCN are not instantiated (or even autoloaded) immediately. The router waits until it identifies that a request should be dispatched to a handler specified by FQCN. Then, it creates an instance of the specified class. Finally, the router calls the handler instance's ``getResponse`` method (declared in HandlerInterface_) and outputs the returned response.
Because the instantiation and autoloading are delayed, a router with 100 routes_ will still only autoload and instantiate one handler_ class throughout any individual request-response cycle.
Callable
--------
You can also use a callable to instantiate and return the handler_.
.. code-block:: php
$router->add("/cats/{id}", function () {
return new \MyApi\CatItemHandler();
});
This still delays instantiation, but gives you some added flexibility. For example, you could define a handler_ class that receives some configuration upon construction.
.. code-block:: php
$container = new MySuperCoolDependencyContainer();
$router->add("/cats/{id}", function () use ($container) {
return new \MyApi\CatItemHandler($container);
});
This is one approach to `dependency injection`_.
You can also return a response directly from a callable. The callables actually serve as informal handlers_ and receive the same arguments as ``HandlerInterface::getResponse``.
.. code-block:: php
$router->add("/hello/{name}", function ($rqst, $args) {
$name = $args["name"];
$response = new \pjdietz\WellRESTed\Response();
$response->setStatusCode(200);
$response->setBody("Hello, $name!");
return $response;
});
Instance
--------
The simplest way to use a handler_ is to instantiate it yourself and pass the instance.
.. code-block:: php
$router->add("/cats/{id}", new \MyApi\CatItemHandler());
This is easy, but has a significant disadvantage over the other options because each handler_ used this way will be autoloaded and instantiated, even though only one handler_ will actually be used for a given request-response cycle. You may find this approach useful for testing, but avoid if for production code.
Error Handling
^^^^^^^^^^^^^^
Use ``Router::setErrorHandler`` to provide responses for a specific status codes. The first argument is the integer status code; the second is a handler_, provided in one of the forms listed in the `Specifying Handlers`_ section.
.. code-block:: php
$router->setErrorHandler(400, "\\MyApi\\BadRequestHandler");
$router->setErrorHandler(401, function () {
return new \MyApi\UnauthorizedHandler();
});
$router->setErrorHandler(403, function () {
$response = new \pjdietz\WellRESTed\Response(403);
$response->setBody("YOU SHALL NOT PASS!");
return $response;
});
$router->setErrorHandler(404, new \MyApi\NotFoundHandler());
You can also set multiple error handlers_ at once by passing a hash array to ``Router::setErrorHandlers``. The hash array must have integer keys representing status codes and handlers_ as values.
.. code-block:: php
$router->setErrorHandlers([
400 => "\\MyApi\\BadRequestHandler",
401 => function () {
return new \MyApi\UnauthorizedHandler();
},
403 => function () {
$response = new \pjdietz\WellRESTed\Response(403);
$response->setBody("YOU SHALL NOT PASS!");
return $response;
},
404 => new \MyApi\NotFoundHandler()
]);
.. note::
Only one error handler_ may be registered for a given status code. A subsequent call to set the handler for a given status code will replace the previous handler with the new handler.
Registering a ``404`` handler_ will set the default behavior for when no routes in the router match. A request for ``/birds/`` using the following router will provide a response with a ``404 Not Found`` status and a message body of "I can't find anything at /birds/".
.. code-block:: php
$router = new \pjdietz\WellRESTed\Router();
$router->add(
["/cats/", $catHandler],
["/dogs/", $dogHandler]
);
$router->setErrorHandler(404, function ($rqst, $args) {
$resp = new \pjdietz\WellRESTed\Response(404);
$resp->setBody("I can't find anything at " . $rqst->getPath());
return $resp;
})
$router->respond();
HTTP Exceptions
^^^^^^^^^^^^^^^
When things go wrong, you can return responses with error codes from any of you handlers_, or you can throw an ``HttpException``. The router will catch any exceptions of this type and provide a response with the corresponding status code.
.. code-block:: php
$router->add("/cats/{catId}", function ($rqst, $args) {
// Find a cat in the cat repository.
$catProvider = new CatProvider();
$cat = $catProvider->getCatById($args["catId");
// Throw a NotFoundException if $cat is null.
if (is_null($cat)) {
throw new \pjdietz\WellRESTed\Exceptions\HttpExceptions\NotFoundException();
}
// Do cat stuff and return a response...
// ...
});
The HttpExceptions are all in the ``\pjdietz\WellRESTed\Exceptions\HttpExceptions`` namespace and all inherit from ``HttpException``. Here's the list of exceptions and their status codes.
=========== =========
Status Code Exception
=========== =========
400 BadRequestException
401 UnauthorizedException
403 ForbiddenException
404 NotFoundException
405 MethodNotAllowed
409 ConflictException
410 GoneException
500 HttpException
=========== =========
If you need to trigger an error other than these, throw ``HttpException`` and set the code, and optionally, the message.
.. code-block:: php
throw new \pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException("Request Timeout", 408);
Nested Routers
^^^^^^^^^^^^^^
For large sites, you may want to break your router into multiple subrouters. Since ``Router`` implements HandlerInterface_, you can use ``Router`` instances as handlers_. Here are a couple patterns for using subrouters.
Using Router Subclasses
-----------------------
One way to build subrouters is by subclassing ``Router`` for each subsection of your API. By subclassing, you can define a router that populates itself with routes on instantiation, and is able to be instantiated by a top-level router.
Here's a top-level router that directs traffic starting with ``/cats/`` to ``MyApi\CatRouter`` and traffic starting with ``/dogs/`` to ``MyApi\DogRouter``.
.. code-block:: php
$router = new \pjdietz\WellRESTed\Router();
$router->add(
["/cats/*", "\\MyApi\\CatRouter"],
["/dogs/*", "\\MyApi\\DogRouter"]
);
Here are router subclasses that contain only routes beginning with the expected prefixes.
.. code-block:: php
namesapce MyApi;
class CatRouter extends \pjdietz\WelRESTed\Router
{
public function __construct()
{
parent::__construct();
$ns = __NAMESPACE__;
$this->add([
"/cats/", "$ns\\CatRootHandler",
"/cats/{id}", "$ns\\CatItemHandler",
// ... other handles related to cats...
]);
}
}
.. code-block:: php
namesapce MyApi;
class DogRouter extends \pjdietz\WelRESTed\Router
{
public function __construct()
{
parent::__construct();
$ns = __NAMESPACE__;
$this->add([
"/dogs/", "$ns\\DogRootHandler",
"/dogs/{group}/", "$ns\\DogGroupHandler",
"/dogs/{group}/{breed}", "$ns\\DogBreedHandler",
// ... other handles related to dogs...
]);
}
}
With this setup, the top-level router will autoload and instantiate a ``CatHandler`` or ``DogHandler`` only if the request matches, then dispatch the request to the newly instantiated subrouter.
Using a Dependency Container
----------------------------
A second approach to subrouters is to use a dependency container such a Pimple_. A container like Pimple allows you to create "providers" that instantiate and return instances of your various routers and handlers_ as needed. As with the subclassing patten, this pattern delays autoloading and instantiating the classes until they are actually used.
.. code-block:: php
$c = new Pimple\Container();
// Create a provider for the top-level router.
// This will return an instance.
$c["router"] = (function ($c) {
$router = new \pjdietz\WellRESTed\Router();
$router->add(
["/cats/*", $c["catRouter"]],
["/dogs/*", $c["dogRouter"]]
);
return $router;
});
// Create "protected" providers for the subrouters.
// These will return callables that will return the routers when called.
$c["catRouter"] = $c->protect(function () use ($c) {
$router = new \pjdietz\WellRESTed\Router();
$router->add(
"/cats/", $c["catRootHandler"],
"/cats/{id}", $c["catItemHandler"],
// ... other handles related to cats...
]);
return $router;
});
$c["dogRouter"] = $c->protect(function () use ($c) {
$router = new \pjdietz\WellRESTed\Router();
$router->add(
"/dogs/", $c["dogRootHandler"],
"/dogs/{group}/", $c["dogGroupHandler"],
"/dogs/{group}/{breed}", $c["dogBreedHandler"],
// ... other handles related to dogs...
]);
return $router;
});
// ... Handlers like catRootHandler have protected providers as well.
See `Dependency Injection`_ for more information.
.. _Dependency Injection: dependency-injection.html
.. _handler: Handlers_
.. _Handlers: handlers.html
.. _HandlerInterface: handlers.html#handlerinterface
.. _Pimple: http://pimple.sensiolabs.org
.. _Requests: requests.html
.. _Responses: responses.html
.. _Routes: routes.html
.. _Specifying Handlers: #specifying-handlers

189
docs/source/routes.rst Normal file
View File

@ -0,0 +1,189 @@
Routes
======
WellRESTed provides a number of types of routes to match a request's path to a handler_ that will provide a response.
`Static Routes`_
Match request paths exactly
`Prefix Routes`_
Match beginnings of paths
`Template Routes`_
Match against URI templates
`Regex Routes`_
Match against regular expressions
Static Routes
^^^^^^^^^^^^^
A static route maps to an exact path. The router will always check for a static route first before trying any other route types.
The following will match all requests to ``/cats/``.
.. code-block:: php
$router->add("/cats/", $catHandler);
A router can only have one static route for a given path, so adding a duplicate static route will replace the first.
.. code-block:: php
$router->add("/cats/", $catHandler);
$router->add("/cats/", $dogHandler);
// Requests for /cats/ will now be dispatched to $dogHandler.
Prefix Routes
^^^^^^^^^^^^^
You can also create prefix routes which will match any path that *begins* with a given prefix. To create a prefix route, include an asterisk (``*``) at the end of the path. The following will match all requests beginning with ``/cats/``, including ``/cats/``, ``/cats/maine-coon``, ``/cats/persian``, etc.
.. code-block:: php
$router->add("/cats/*", $catHandler));
Prefix routes are evaluated after static routes. So, given this router…
.. code-block:: php
$router->add("/cats/", $catRootHandler);
$router->add("/cats/*", $catSubHandler);
…requests for ``/cats/`` will be dispatched to ``$catRootHandler``, while ``/cats/maine-coon``, ``/cats/persian``, etc. will be dispatched to ``$catSubHandler``.
Finding the Best Match
----------------------
In the event that multiple prefix routes match the request, the router uses the longest match to determine the best match. Give this router…
.. code-block:: php
$router->add("/dogs/*", $dogHandler);
$router->add("/dogs/sporting/*", $sportingHandler);
…requests to ``/dogs/sporting/flat-coated-retriever`` will be dispatched to ``$sportingHandler``, while requests to ``/dogs/herding/boarder-collie`` will be dispatched to ``$dogHandler``.
Template Routes
^^^^^^^^^^^^^^^
Template routes allow the router to extract parts of the path and make them available to the handler_. To create a template route, include one or more variables in the path.
.. code-block:: php
$router->add("/dogs/{group}/{breed}", $dogHandler);
When the router gets a request for ``/dogs/herding/australian-shepherd``, it will dispatch ``$dogHandler`` and pass this array:
.. code-block:: php
[
"group" => "herding",
"breed" => "australian-shepherd"
]
The name of each variable (the part between the curly braces) becomes an array key while the extracted text becomes the corresponding value.
Default Variable Pattern
------------------------
By default, the variables will match digits, upper- and lowercase letters, hyphens, and underscores. To change this, pass a partial regular expression to ``Router::add`` as the third parameter.
The following will restrict the route to match only when the variable matches digits.
.. code-block:: php
$router->add("/cats/{catId}", $catHandler, "[0-9]+");
Variable Pattern Map
--------------------
For more specificity, you can provide an array mapping variables to their patterns as the fourth parameter. (The third parameter will server as the default for any variables not included in the map).
This will restrict ``{dogId}`` to be digits, while leaving ``{group}`` and ``{breed}`` to be matched by the default.
.. code-block:: php
$router->add("/dogs/{group}/{breed}/{dogId}", $dogHandler, null, [
"dogId" => "[0-9]+"
]);
Pattern Constants
-----------------
The ``TemplateRoute`` class provides a few of these patterns as class constants.
=============== ===================== ===========
Constant Regex Description
=============== ===================== ===========
``RE_SLUG`` ``[0-9a-zA-Z\-_]+`` **(Default)** "URL-friendly" characters
``RE_NUM`` ``[0-9]+`` Digits only
``RE_ALPHA`` ``[a-zA-Z]+`` Letters only
``RE_ALPHANUM`` ``[0-9a-zA-Z]+`` Letters and digits
=============== ===================== ===========
You can use the class constants or provide your own strings.
This will restrict ``{dogId}`` to be digits, and restrict ``{group}`` and ``{breed}`` to lowercase letters and hyphens.
.. code-block:: php
$router->add("/dogs/{group}/{breed}/{dogId}", $dogHandler, "[a-z\-]", [
"dogId" => TemplateRoute::RE_NUM,
]);
Regex Routes
^^^^^^^^^^^^
If template routes don't give you enough control, you can make a route that matches a regular expression.
.. code-block:: php
$router->add("~/cat/[0-9]+~", $catHandler);
This will match ``/cat/102``, ``/cat/999``, etc.
Capture Groups
--------------
To make this more useful, add a capture group. A regex route makes captures available to the dispatched handler the same way template route makes matched variables available.
So this route…
.. code-block:: php
$router->add("~/cat/([0-9]+)~", $catHandler);
…with the path ``/cat/99``, creates this array of matches:
.. code-block:: php
[
0 => "/cat/99",
1 => "99"
]
(Note that the entire matched path will always be the ``0`` item, and captured groups will begin at ``1``.)
You can also used named capture groups like this:
.. code-block:: php
$router->add("~/cat/(?<catId>[0-9]+)~", $catHandler);
The path ``/cat/99`` creates this array of matches:
.. code-block:: php
[
0 => "/cat/99",
1 => "99",
"catId" => "99"
]
Delimiter
---------
To prevent the route from interpreting your regular expression as a really weird path, use a character other than `/` as the regular expression delimiter_. For example, `~` or `#`.
.. _delimiter: http://php.net/manual/en/regexp.reference.delimiters.php
.. _handler: Handlers_
.. _Handlers: handlers.html

View File

@ -0,0 +1,46 @@
Web Server Configuration
========================
You will typically want to have all traffic on your site directed to a single script that dispatches the router. Here are basic setups for doing this in Nginx_ and Apache_.
Nginx
^^^^^
.. code-block:: nginx
server {
listen 80;
server_name your.hostname.here;
root /your/sites/document/root;
index index.php index.html;
charset utf-8;
# Attempt to serve actual files first.
# If no file exists, send to /index.php
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
}
Apache
^^^^^^
.. code-block:: apache
RewriteEngine on
RewriteBase /
# Send all requests to non-regular files and directories to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.+$ index.php [L,QSA]

View File

@ -2,14 +2,14 @@
<phpdoc>
<title>WellRESTed</title>
<parser>
<target>docs</target>
<target>phpdoc</target>
<markers>
<item>TODO</item>
<item>FIXME</item>
</markers>
</parser>
<transformer>
<target>docs</target>
<target>phpdoc</target>
</transformer>
<transformations>
<template name="responsive" />