├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── docs ├── Makefile ├── README.md ├── autoloader.rst ├── conf.py ├── controllers.rst ├── events.rst ├── example.rst ├── getting-started.rst ├── index.rst ├── make.bat ├── pull-driven.rst └── views.rst ├── examples ├── base │ ├── controllers │ │ ├── ErrorController.php │ │ └── IndexController.php │ ├── layouts │ │ └── layout.phtml │ ├── public │ │ ├── .htaccess │ │ └── index.php │ └── views │ │ ├── error │ │ └── error.phtml │ │ ├── index │ │ ├── index.phtml │ │ ├── kindle.phtml │ │ └── raw-post.phtml │ │ └── pull │ │ └── result.phtml └── twig-integration │ ├── .gitignore │ ├── README.md │ ├── composer.json │ ├── composer.lock │ ├── controllers │ └── IndexController.php │ ├── layouts │ └── base.twig │ ├── public │ ├── .htaccess │ └── index.php │ └── views │ ├── cache │ └── .gitentry │ ├── error │ └── error.twig │ └── index │ └── index.twig ├── exts ├── HostnameRouter.php ├── README.md ├── StaticRouter.php └── TwigView.php ├── pack ├── README.md ├── compile ├── composer.json └── composer.lock ├── phpunit.xml ├── src ├── Application.php ├── Bootstrap.php ├── Controller.php ├── Dispatcher.php ├── EventManager.php ├── Layout.php ├── Loader.php ├── Request.php ├── Route.php ├── Router.php └── View.php └── tests ├── ApplicationTest.php ├── BootstrapTest.php ├── ControllerTest.php ├── DispatcherTest.php ├── EventManagerTest.php ├── LayoutTest.php ├── LoaderTest.php ├── RequestTest.php ├── RouteTest.php ├── RouterTest.php ├── ViewTest.php ├── bootstrap.php ├── classes ├── ns │ └── Clazz.php └── pr │ └── Clazz.php ├── controllers ├── AdminController.php ├── AloneController.php ├── ErrorController.php ├── GeneralController.php ├── InitController.php └── ThenController.php ├── layouts ├── alternate.phtml ├── layout.phtml └── title-helper.phtml ├── resources └── default.request.xml ├── views-rewrite ├── base.phtml ├── general │ ├── c.phtml │ ├── d.phtml │ └── partial-eg.phtml └── title.phtml └── views ├── admin └── login.phtml ├── base.phtml ├── general ├── b.phtml ├── c.phtml ├── d.phtml ├── direct.phtml ├── disable-layout.phtml ├── partial-eg.phtml ├── pull-driven.phtml └── title-helper.phtml ├── only-base.phtml ├── partial.phtml ├── pull ├── buffer-out.phtml ├── driven-data.phtml ├── driven.phtml ├── missing-pull-action.phtml └── missing-pull.phtml ├── title.phtml ├── view-mix-test.phtml └── view-test.phtml /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .buildpath 3 | .settings 4 | .DS_Store 5 | res 6 | *.phar 7 | pack/vendor 8 | *.swp 9 | *.swo 10 | docs/_build 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3.3 4 | - 5.3 5 | - 5.4 6 | before_script: 7 | script: phpunit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) <2012> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple MVC [VC] framework 2 | 3 | A simple ***push & pull MVC framework*** heavly inspired to different PHP microframeworks and 4 | PHP MVC framework like ZF1. 5 | 6 | ## Why? 7 | 8 | I want to try out the test-driven development [at least write some tests ;)]. 9 | 10 | Just for my pleasure. 11 | 12 | ## Goals 13 | 14 | * PHPUnit 15 | * Very simple implementation (***only 8 classes*** + autoloader) 16 | * PHP 5.3+ implementation 17 | 18 | ## Features 19 | 20 | * 100% MVC implementation [66% no model support] ;) 21 | * Useful hooks (Fixed events) 22 | * Loop Startup 23 | * Pre Dispatch 24 | * Init Hook 25 | * Post Dispatch 26 | * Loop Shutdown 27 | * View Renderer Switch 28 | * View Helpers 29 | * Partial views 30 | * Two step view (Layout support) 31 | * Controllers stack 32 | * Headers handler 33 | * Event manager (Self designed hooks) 34 | * Router 35 | * Only controller/action names 36 | * Dash URLs support (/a-dash/the-name-of-content) 37 | * Pull Driven requests 38 | * View request data to a controller-action 39 | * Rewritable views 40 | * Different views mount points for rewrite views 41 | 42 | ## Install with Composer 43 | 44 | If you want you can use Composer for install simple-mvc. 45 | Create the `composer.json` 46 | 47 | ```json 48 | { 49 | "require": { 50 | "wdalmut/simple-mvc": "*" 51 | } 52 | } 53 | ``` 54 | 55 | Now you can install the framework 56 | 57 | ```shell 58 | $ curl -s http://getcomposer.org/installer | php 59 | $ php composer.phar install 60 | ``` 61 | 62 | You can use the Composer autoloader 63 | 64 | ```php 65 | =5.3.3" 20 | }, 21 | "autoload": { 22 | "classmap": [ 23 | "src/", 24 | "exts/" 25 | ] 26 | }, 27 | "repositories": [ 28 | { 29 | "type": "vcs", 30 | "url": "https://github.com/wdalmut/simple-mvc" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/simple-mvc.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/simple-mvc.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/simple-mvc" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/simple-mvc" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | You can read the docs directly on: [ReadTheDocs](http://simple-mvc.readthedocs.org/en/latest/index.html) 4 | -------------------------------------------------------------------------------- /docs/autoloader.rst: -------------------------------------------------------------------------------- 1 | Autoloader 2 | ========== 3 | 4 | `simple-mvc` provides two strategies for loading classes for itself 5 | and only one strategy for autoload your classes. 6 | 7 | Classmap 8 | -------- 9 | 10 | The classmap loads only `simple-mvc` classes. If you have a self-designed 11 | autoloader you have to use this strategy for reduce conflicts during 12 | the autoloading process. 13 | 14 | .. code-block:: php 15 | :linenos: 16 | 17 | ClassName.php 58 | class Prefix_ClassName 59 | { 60 | 61 | } 62 | 63 | Namespace example: 64 | 65 | .. code-block:: php 66 | :linenos: 67 | 68 | ClassName.php 72 | class ClassName 73 | { 74 | 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # simple-mvc documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Sep 8 12:59:34 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'simple-mvc' 44 | copyright = u'2012, Walter Dal Mut' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'simple-mvcdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'simple-mvc.tex', u'simple-mvc Documentation', 187 | u'Walter Dal Mut', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'simple-mvc', u'simple-mvc Documentation', 217 | [u'Walter Dal Mut'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'simple-mvc', u'simple-mvc Documentation', 231 | u'Walter Dal Mut', 'simple-mvc', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /docs/controllers.rst: -------------------------------------------------------------------------------- 1 | Controllers 2 | =========== 3 | 4 | The controller section 5 | 6 | Init hook 7 | --------- 8 | 9 | Before any action dispatch the framework executes the `init()` method. 10 | 11 | .. code-block:: php 12 | :linenos: 13 | 14 | view->hello = "hello"; 52 | 53 | 54 | $this->then("/index/next"); 55 | } 56 | 57 | public function nextAction() 58 | { 59 | $this->view->cose = "ciao"; 60 | } 61 | } 62 | 63 | The result is the first view (`index.phtml`) concatenated to the 64 | second view (`next.phtml`). 65 | 66 | Redirects 67 | --------- 68 | 69 | You can handle redirects using the `redirect()` method 70 | 71 | .. code-block:: php 72 | :linenos: 73 | 74 | redirect("/contr/act", 302); 81 | } 82 | } 83 | 84 | Interact with layout and views 85 | ------------------------------ 86 | 87 | You can disable the layout system at any time using the `disableLayout()` 88 | method. 89 | 90 | .. code-block:: php 91 | :linenos: 92 | 93 | disableLayout(); 100 | } 101 | } 102 | 103 | You can disable the view attached to a controller using the `setNoRender()` 104 | method 105 | 106 | .. code-block:: php 107 | :linenos: 108 | 109 | setNoRender(); 116 | } 117 | } 118 | 119 | Change the layout on the fly 120 | ---------------------------- 121 | 122 | If you want to change your layout during an action or a plugin interaction 123 | you can use the resources manager 124 | 125 | .. code-block:: php 126 | :linenos: 127 | 128 | getResource("layout")->setScriptName("full-width.phtml"); 134 | } 135 | } 136 | 137 | Obviously you must use the layout manager. 138 | 139 | Using headers 140 | ------------- 141 | 142 | You can send different headers using `addHeader()` method 143 | 144 | .. code-block:: php 145 | :linenos: 146 | 147 | addHeader("Content-Type", "text/plain"); 153 | } 154 | } 155 | 156 | Change View Renderer 157 | -------------------- 158 | 159 | You can change the view renderer at runtime during an action execution. 160 | 161 | .. code-block:: php 162 | :linenos: 163 | 164 | setRenderer("/use/me"); 170 | } 171 | } 172 | 173 | The framework will use the `use/me.phtml` 174 | 175 | The end. 176 | -------------------------------------------------------------------------------- /docs/events.rst: -------------------------------------------------------------------------------- 1 | Events 2 | ====== 3 | 4 | Events 5 | 6 | * `loop.startup` 7 | * `loop.shutdown` 8 | * `pre.dispatch` 9 | * `post.dispatch` 10 | 11 | Hooks 12 | ----- 13 | 14 | The `loop.startup` and `loop.shutdown` is called once at the start and at the 15 | end of the simple-mvc workflow. 16 | 17 | The `pre.dispatch` and `post.dispatch` is called for every controlled pushed 18 | onto the stack (use the `then()` method). 19 | 20 | Hooks params 21 | ~~~~~~~~~~~~ 22 | 23 | The `loop.startup` and the `loop.shutdown` have the `Application` object as 24 | first parameter. 25 | 26 | The `pre.dispatch` hook has the `Route` object as first parameter and the 27 | `Application` object as second. 28 | 29 | The `post.dispatch` hook has the `Controller` object as first paramter. 30 | 31 | * The router object is useful for modify the application flow. 32 | 33 | .. code-block:: php 34 | :linenos: 35 | 36 | getEventManager()->subscribe("pre.dispatch", function($router, $app) { 38 | // Use a real and better auth system 39 | if ($_SESSION["auth"] !== true) { 40 | $router->setControllerName("admin"); 41 | $router->setActionName("login"); 42 | 43 | $app->getBootstrap("layout")->setScriptName("admin.phtml"); 44 | } 45 | }); 46 | 47 | Create new events 48 | ----------------- 49 | 50 | .. code-block:: php 51 | :linenos: 52 | 53 | getEventManager()->publish("my.hook", array($app)); 56 | 57 | You can use the self-created hook using 58 | 59 | .. code-block:: php 60 | :linenos: 61 | 62 | getEventManager()->subscribe("my.hook", function($app) {/*The body*/}); 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/example.rst: -------------------------------------------------------------------------------- 1 | Examples of usage 2 | ================= 3 | 4 | A simple base app execution 5 | 6 | .. code-block:: php 7 | :linenos: 8 | 9 | run(); 13 | 14 | Execute with bootstrap 15 | ---------------------- 16 | 17 | .. code-block:: php 18 | :linenos: 19 | 20 | bootstrap("say-hello", function(){ 24 | return array('example' => 'ciao'); 25 | }); 26 | 27 | $app->run(); 28 | 29 | Into a controller 30 | 31 | .. code-block:: php 32 | :linenos: 33 | 34 | getResource('example'); 40 | 41 | echo $element["example"]; 42 | } 43 | } 44 | 45 | Controller Forward 46 | ------------------ 47 | 48 | You can pass to another controller using `then()` 49 | 50 | .. code-block:: php 51 | :linenos: 52 | 53 | then("/index/forward"); 60 | } 61 | 62 | public function forwardAction() 63 | { 64 | // append to index or use it directly 65 | } 66 | } 67 | 68 | See `example` folder for a complete working example. 69 | 70 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | The goal is realize a web application in few steps. 5 | 6 | See scripts into `example` for a real example. The base is create 7 | a public folder where your web server dispatch the `index.php`. 8 | 9 | Create out from this folder the `controllers` path or whatever you 10 | want (eg. `ctrs`).:: 11 | 12 | - controllers 13 | - IndexController.php 14 | - public 15 | - .htaccess 16 | - index.php 17 | 18 | In practice you are ready. See the `.htaccess`:: 19 | 20 | RewriteEngine On 21 | RewriteCond %{REQUEST_FILENAME} -s [OR] 22 | RewriteCond %{REQUEST_FILENAME} -l [OR] 23 | RewriteCond %{REQUEST_FILENAME} -d 24 | RewriteRule ^.*$ - [NC,L] 25 | RewriteRule ^.*$ index.php [NC,L] 26 | 27 | The `index.php` is the main app entry point 28 | 29 | .. code-block:: php 30 | :linenos: 31 | 32 | setControllerPath(__DIR__ . '/../controllers'); 40 | $app->run(); 41 | 42 | The controller `IndexController.php` file should be like this 43 | 44 | .. code-block:: php 45 | :linenos: 46 | 47 | TheControllerName 73 | class TheControllerName extends Controller 74 | { 75 | public function theActionNameAction() 76 | { 77 | //the-action-name => theActionName 78 | } 79 | } 80 | 81 | Bootstrap resources 82 | ------------------- 83 | 84 | You can bootstrap resources: 85 | 86 | .. code-block:: php 87 | :linenos: 88 | 89 | bootstrap('my-resource', function() { 92 | return new MyObject(); 93 | }); 94 | 95 | The bootstrap do not executes all hooks (lazy-loading of resources) but execute 96 | it ones only if your application needs it. 97 | 98 | .. code-block:: php 99 | :linenos: 100 | 101 | getResource("my-resource"); 104 | $another = $this->getResource("my-resource"); 105 | 106 | // IT IS TRUE! 107 | var_dump($resource === $another); 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. simple-mvc documentation master file, created by 2 | sphinx-quickstart on Sat Sep 8 12:59:34 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to simple-mvc's documentation! 7 | ====================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | getting-started 15 | autoloader 16 | controllers 17 | views 18 | events 19 | pull-driven 20 | example 21 | 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\simple-mvc.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\simple-mvc.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/pull-driven.rst: -------------------------------------------------------------------------------- 1 | Pull Driven Requests 2 | ==================== 3 | 4 | Typically MVC frameworks are "push" based. In otherwords use mechanisms to "push" data to 5 | a view and not vice-versa. A "pull" framework instead request ("pull") data from a view. 6 | 7 | Pull strategy is useful for example during a `for` statement (not only for that [obviously]...). Look 8 | for an example: 9 | 10 | .. code-block:: php 11 | :linenos: 12 | 13 | users as $user) : ?> 14 | pull("/detail/user/id/{$user->id}"); 17 | ?> 18 |
19 |
name;?> surname; ?>
20 | 21 |
22 | 23 | 24 | `simple-mvc` implementation 25 | --------------------------- 26 | 27 | `simple-mvc` has ***push*** and ***pull*** mechanisms. The *push* is quite simple and a typical 28 | operation. See an example 29 | 30 | .. code-block:: php 31 | :linenos: 32 | 33 | var 39 | $this->view->var = "hello"; 40 | } 41 | } 42 | 43 | The view show the pushed variable 44 | 45 | .. code-block:: php 46 | :linenos: 47 | 48 | var; ?> 49 | 50 | The `pull` strategy is quite similar but use the return statement of a controller to retrive 51 | all the information. Consider in advance that `simple-mvc` doesn't require a valid controller 52 | for retrive a view, that view is mapped directly. See an example 53 | 54 | .. code-block:: php 55 | :linenos: 56 | 57 | 58 |
59 |

Missing controller and action

60 | 61 | pull("/ctr/act"); ?> 62 | 63 | 64 | title; ?> 65 |
66 | 67 | The view require a `pull` operation from a controller named `ctr` and action `act`. See it: 68 | 69 | .. code-block:: php 70 | :linenos: 71 | 72 | title = "The title"; 80 | 81 | // The return type doesn't care... 82 | return $data; 83 | } 84 | } 85 | 86 | You can use a "pull" controller as a normal controller with the attached view, but remember 87 | that when you request for a "pull" operation the view is never considered and the framework 88 | remove it without consider the output, only the `return` statement will be used. 89 | -------------------------------------------------------------------------------- /docs/views.rst: -------------------------------------------------------------------------------- 1 | Views 2 | ===== 3 | 4 | The framework starts without view system. For add view support 5 | you have to add `view` at bootstrap. 6 | 7 | .. code-block:: php 8 | :linenos: 9 | 10 | bootstrap('view', function(){ 14 | $view = new View(); 15 | $view->addViewPath(__DIR__ . '/../views'); 16 | 17 | return $view; 18 | }); 19 | 20 | $app->run(); 21 | 22 | The framework append automatically to a controller the right 23 | view using controller and action name. Tipically you have to 24 | create a folder tree like this: :: 25 | 26 | site 27 | + public 28 | + controllers 29 | - views 30 | - index 31 | - index.phtml 32 | 33 | In this way the system load correctly the controller path and the view 34 | script. 35 | 36 | Layout support 37 | -------------- 38 | 39 | The layout is handled as a simple view that wrap the controller view. 40 | 41 | You need to bootstrap it. The normal layout name is "layout.phtml" 42 | 43 | .. code-block:: php 44 | :linenos: 45 | 46 | bootstrap('layout', function(){ 49 | $layout = new Layout(); 50 | $layout->addViewPath(__DIR__ . '/../layouts'); 51 | 52 | return $layout; 53 | }); 54 | 55 | You can change the layout script name using the setter. 56 | 57 | .. code-block:: php 58 | :linenos: 59 | 60 | setScriptName("base.phtml"); 62 | 63 | View Helpers 64 | ------------ 65 | 66 | If you want to create view helpers during your view bootstrap 67 | add an helper closure. 68 | 69 | .. code-block:: php 70 | :linenos: 71 | 72 | bootstrap('view', function(){ 74 | $view = new View(); 75 | $view->addViewPath(__DIR__ . '/../views'); 76 | 77 | $view->addHelper("now", function(){ 78 | return date("d-m-Y"); 79 | }); 80 | 81 | return $view; 82 | }); 83 | 84 | You can use it into you view as: 85 | 86 | .. code-block:: php 87 | :linenos: 88 | 89 | now()?> 90 | 91 | You can create helpers with many variables 92 | 93 | .. code-block:: php 94 | :linenos: 95 | 96 | addHelper("sayHello", function($name){ 98 | return "Hello {$name}"; 99 | }); 100 | 101 | View system is based using the prototype pattern all of your 102 | helpers attached at bootstrap time existing into all of your 103 | real views. 104 | 105 | Share view helpers 106 | ~~~~~~~~~~~~~~~~~~ 107 | 108 | View helpers are automatically shared with layout. In this way 109 | you can creates global helpers during the bootstrap and interact with 110 | those helpers at action time. 111 | 112 | Pay attention that those helpers are copied. Use `static` scope for 113 | share variables. 114 | 115 | .. code-block:: php 116 | :linenos: 117 | 118 | bootstrap("layout", function(){ 120 | $layout = new Layout(); 121 | $layout->addViewPath(__DIR__ . '/../layouts'); 122 | 123 | 124 | return $layout; 125 | }); 126 | 127 | $app->bootstrap("view", function(){ 128 | $view = new View(); 129 | $view->addViewPath(__DIR__ . '/../views'); 130 | 131 | $view->addHelper("title", function($part = false){ 132 | static $parts = array(); 133 | static $delimiter = ' :: '; 134 | 135 | return ($part === false) ? "".implode($delimiter, $parts)."" : $parts[] = $part; 136 | }); 137 | 138 | return $view; 139 | }); 140 | 141 | From a view you can call the `title()` helper and it appends parts of you 142 | page title. 143 | 144 | Escapes 145 | ------- 146 | 147 | Escape is a default view helper. You can escape variables using the 148 | `escape()` view helper. 149 | 150 | .. code-block:: php 151 | :linenos: 152 | 153 | escape("Ciao -->"); // Ciao --> 155 | 156 | Partials view 157 | ------------- 158 | 159 | Partials view are useful for render section of your view separately. In 160 | `simple-mvc` partials are view helpers. 161 | 162 | .. code-block:: html 163 | :linenos: 164 | 165 | 166 |
167 |
168 | partial("/path/to/view.phtml", array('title' => $this->title));?> 169 |
170 |
171 | 172 | The partial view `/path/to/view.phtml` are located at `view` path. 173 | 174 | .. code-block:: php 175 | :linenos: 176 | 177 | 178 |

title; ?>

179 | 180 | Multiple view scripts paths 181 | --------------------------- 182 | 183 | `simple-mvc` support multiple views scripts paths. In other words you can specify 184 | a single mount point `/path/to/views` after that you can add anther views script path, 185 | this mean that the `simple-mvc` search for a view previously into the second views path 186 | and if it is missing looks for that into the first paths. View paths are threated as 187 | a stack, the latest pushed is the first used. 188 | 189 | During your bootstrap add more view paths 190 | 191 | .. code-block:: php 192 | :linenos: 193 | 194 | $app->bootstrap('view', function(){ 195 | $view = new View(); 196 | $view->addViewPath(__DIR__ . '/../views'); 197 | $view->addViewPath(__DIR__ . '/../views-rewrite'); 198 | 199 | return $view; 200 | }); 201 | 202 | If you have a view named `name.phtml` into `views` folder and now you create the view 203 | named `name.phtml` into `views-rewrite` this one is used instead the original file in 204 | `views` folder. 205 | 206 | Partials and multiple view scripts paths 207 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 208 | 209 | ***Partial views follow the rewrite path strategy***. If you add the partial 210 | view into a rewrite view folder, this view script is choosen instead 211 | the original partial script. 212 | 213 | .. code-block:: php 214 | :linenos: 215 | 216 | partial("my-helper.phtml", array('ciao' => 'hello'))?> 217 | 218 | If `my-helper.phtml` is found in a rewrite point this view is used instead 219 | the original view script. 220 | 221 | The end. 222 | -------------------------------------------------------------------------------- /examples/base/controllers/ErrorController.php: -------------------------------------------------------------------------------- 1 | "; 7 | var_dump($this->getParams()); 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/base/controllers/IndexController.php: -------------------------------------------------------------------------------- 1 | view->title("The index"); 7 | 8 | $this->view->hello = "hello"; 9 | 10 | $this->then("/index/kindle"); 11 | } 12 | 13 | public function kindleAction() 14 | { 15 | $this->view->title("The kindle"); 16 | 17 | $this->view->cose = "ciao"; 18 | } 19 | 20 | public function xmlAction() 21 | { 22 | $this->setNoRender(); 23 | $this->disableLayout(); 24 | 25 | $dom = new DOMDocument("1.0", "UTF-8"); 26 | $element = $dom->createElement("example", "walter"); 27 | $dom->appendChild($element); 28 | 29 | echo $dom->saveXML(); 30 | 31 | $this->addHeader("Content-Type", "text/xml"); 32 | } 33 | 34 | public function userAction() 35 | { 36 | $this->setNoRender(); 37 | 38 | $params = $this->getParams(); 39 | 40 | $obj = new stdClass(); 41 | $obj->name = "User id: {$params["id"]}"; 42 | 43 | return $obj; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/base/layouts/layout.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <?php //echo //$this->title() ?> 4 | 5 | 6 | content ?> 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/base/public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} -s [OR] 3 | RewriteCond %{REQUEST_FILENAME} -l [OR] 4 | RewriteCond %{REQUEST_FILENAME} -d 5 | RewriteRule ^.*$ - [NC,L] 6 | RewriteRule ^.*$ index.php [NC,L] 7 | -------------------------------------------------------------------------------- /examples/base/public/index.php: -------------------------------------------------------------------------------- 1 | setControllerPath(__DIR__ . '/../controllers'); 9 | 10 | $app->bootstrap("view", function(){ 11 | $view = new View(); 12 | $view->setViewPath(__DIR__ . '/../views'); 13 | 14 | $view->addHelper("title", function($part = false){ 15 | static $parts = array(); 16 | static $delimiter = ' :: '; 17 | 18 | return ($part === false) ? implode($delimiter, $parts) : $parts[] = $part; 19 | }); 20 | 21 | return $view; 22 | }); 23 | 24 | $app->bootstrap("layout", function(){ 25 | $layout = new Layout(); 26 | $layout->setViewPath(__DIR__ . '/../layouts'); 27 | 28 | return $layout; 29 | }); 30 | 31 | $app->run(); 32 | -------------------------------------------------------------------------------- /examples/base/views/error/error.phtml: -------------------------------------------------------------------------------- 1 | Error page... -------------------------------------------------------------------------------- /examples/base/views/index/index.phtml: -------------------------------------------------------------------------------- 1 |

hello?>

2 |

escape("

Escape this output

");?>

-------------------------------------------------------------------------------- /examples/base/views/index/kindle.phtml: -------------------------------------------------------------------------------- 1 |

cose;?>

-------------------------------------------------------------------------------- /examples/base/views/index/raw-post.phtml: -------------------------------------------------------------------------------- 1 |
escape($this->rawBody);?>
-------------------------------------------------------------------------------- /examples/base/views/pull/result.phtml: -------------------------------------------------------------------------------- 1 |

Pull Driven Requests

2 |
3 | pull("/index/user/id/1")->name; ?> 4 |
-------------------------------------------------------------------------------- /examples/twig-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.phar 3 | -------------------------------------------------------------------------------- /examples/twig-integration/README.md: -------------------------------------------------------------------------------- 1 | # Twig Integration 2 | 3 | First of all using composer for download all dependencies 4 | 5 | ```shell 6 | $ curl -s http://getcomposer.org/installer | php 7 | $ php composer.phar update 8 | ``` 9 | 10 | A `vendor` folder will be created with `simple-mvc` and `twig` template engine. 11 | 12 | Now you are ready to see the twig integration. The adapter `SimpleTwigView` 13 | force the view engine to use `twig` instead the original one. -------------------------------------------------------------------------------- /examples/twig-integration/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "twig/twig": "1.*", 4 | "wdalmut/simple-mvc": "0.1.*" 5 | } 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/twig-integration/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "bbc67bb2fca4287c2501e8ad26c05614", 3 | "packages": [ 4 | { 5 | "name": "twig/twig", 6 | "version": "dev-master", 7 | "source": { 8 | "type": "git", 9 | "url": "git://github.com/fabpot/Twig.git", 10 | "reference": "b4d1d62b82e83c6fd3d5a6cd46a186de64275bb4" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://github.com/fabpot/Twig/zipball/b4d1d62b82e83c6fd3d5a6cd46a186de64275bb4", 15 | "reference": "b4d1d62b82e83c6fd3d5a6cd46a186de64275bb4", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "php": ">=5.2.4" 20 | }, 21 | "time": "1347961095", 22 | "type": "library", 23 | "extra": { 24 | "branch-alias": { 25 | "dev-master": "1.10-dev" 26 | } 27 | }, 28 | "installation-source": "source", 29 | "autoload": { 30 | "psr-0": { 31 | "Twig_": "lib/" 32 | } 33 | }, 34 | "license": [ 35 | "BSD-3" 36 | ], 37 | "authors": [ 38 | { 39 | "name": "Fabien Potencier", 40 | "email": "fabien@symfony.com" 41 | }, 42 | { 43 | "name": "Armin Ronacher", 44 | "email": "armin.ronacher@active-4.com" 45 | } 46 | ], 47 | "description": "Twig, the flexible, fast, and secure template language for PHP", 48 | "homepage": "http://twig.sensiolabs.org", 49 | "keywords": [ 50 | "templating" 51 | ] 52 | }, 53 | { 54 | "name": "wdalmut/simple-mvc", 55 | "version": "0.1.2", 56 | "source": { 57 | "type": "git", 58 | "url": "https://github.com/wdalmut/simple-mvc", 59 | "reference": "0.1.2" 60 | }, 61 | "dist": { 62 | "type": "zip", 63 | "url": "https://github.com/wdalmut/simple-mvc/zipball/0.1.2", 64 | "reference": "0.1.2", 65 | "shasum": "" 66 | }, 67 | "require": { 68 | "php": ">=5.3.3" 69 | }, 70 | "time": "2012-09-23 00:00:00", 71 | "type": "library", 72 | "installation-source": "source", 73 | "autoload": { 74 | "classmap": [ 75 | "src/", 76 | "exts/" 77 | ] 78 | }, 79 | "license": [ 80 | "MIT" 81 | ], 82 | "authors": [ 83 | { 84 | "name": "Walter Dal Mut", 85 | "email": "walter.dalmut@gmail.com", 86 | "homepage": "http://walterdalmut.com", 87 | "role": "Developer" 88 | } 89 | ], 90 | "description": "A simple and full stack Push & Pull MVC framework.", 91 | "keywords": [ 92 | "framework", 93 | "microframework", 94 | "mvc", 95 | "micro" 96 | ] 97 | } 98 | ], 99 | "packages-dev": null, 100 | "aliases": [ 101 | 102 | ], 103 | "minimum-stability": "stable", 104 | "stability-flags": [ 105 | 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /examples/twig-integration/controllers/IndexController.php: -------------------------------------------------------------------------------- 1 | view->hello = "hello..."; 7 | 8 | $this->view->texts = new stdClass(); 9 | $this->view->texts->para = "paragraph"; 10 | } 11 | } -------------------------------------------------------------------------------- /examples/twig-integration/layouts/base.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block head %} 5 | 6 | {% block title %}{% endblock %} - My Webpage 7 | {% endblock %} 8 | 9 | 10 |
{% block content %}{% endblock %}
11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/twig-integration/public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} -s [OR] 3 | RewriteCond %{REQUEST_FILENAME} -l [OR] 4 | RewriteCond %{REQUEST_FILENAME} -d 5 | RewriteRule ^.*$ - [NC,L] 6 | RewriteRule ^.*$ index.php [NC,L] -------------------------------------------------------------------------------- /examples/twig-integration/public/index.php: -------------------------------------------------------------------------------- 1 | setControllerPath(__DIR__ . '/../controllers'); 8 | 9 | $app->bootstrap("view", function(){ 10 | $view = new TwigView(); 11 | $view->addViewPath(realpath(__DIR__ . '/../layouts')); 12 | $view->addViewPath(realpath(__DIR__ . '/../views')); 13 | $view->setViewExt(".twig"); 14 | $view->initTwig(__DIR__ . '/../views/cache'); 15 | 16 | return $view; 17 | }); 18 | 19 | $app->run(); 20 | -------------------------------------------------------------------------------- /examples/twig-integration/views/cache/.gitentry: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdalmut/simple-mvc/0657d1f53cfbb44c43f97f1260a0fd5d6a557295/examples/twig-integration/views/cache/.gitentry -------------------------------------------------------------------------------- /examples/twig-integration/views/error/error.twig: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /examples/twig-integration/views/index/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block title %}Index{% endblock %} 4 | {% block head %} 5 | {{ parent() }} 6 | 9 | {% endblock %} 10 | {% block content %} 11 |

Index

12 |

13 | Welcome to Twig example. 14 |

15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /exts/HostnameRouter.php: -------------------------------------------------------------------------------- 1 | _hostname = $hostname; 9 | } 10 | 11 | public function match(Request $request, $route = false) 12 | { 13 | if ($request->getHostname() == $this->_hostname) { 14 | return parent::match($request, new Route()); 15 | } 16 | 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /exts/README.md: -------------------------------------------------------------------------------- 1 | # simple-mvc Extensions 2 | 3 | Just useful extensions 4 | 5 | * HostnameRouter 6 | * A simple hostname router 7 | * StaticRouter 8 | * A simple static router 9 | * SimpleTwigView 10 | * A simple Twig integration 11 | 12 | 13 | -------------------------------------------------------------------------------- /exts/StaticRouter.php: -------------------------------------------------------------------------------- 1 | _path = $path; 11 | $this->_action = $action; 12 | $this->_controller = $controller; 13 | } 14 | 15 | public function match(Request $request, $route = false) 16 | { 17 | if ($request->getUri() == $this->_path) { 18 | $static = new Route(); 19 | $static->setControllerName($this->_controller); 20 | $static->setActionName($this->_action); 21 | $static->merge($route); 22 | 23 | return parent::match($request, $static); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /exts/TwigView.php: -------------------------------------------------------------------------------- 1 | getViewPaths()); 9 | 10 | if ($cache) { 11 | $this->_twig = new Twig_Environment($loader, array( 12 | 'cache' => $cache 13 | )); 14 | } else { 15 | $this->_twig = new Twig_Environment($loader); 16 | } 17 | } 18 | 19 | public function render($filename, $data = false) 20 | { 21 | $template = $this->_twig->loadTemplate($filename); 22 | $data = (is_array($data)) ? array_merge($this->_getData(), $data) : $this->_getData(); 23 | 24 | return $template->render($data); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pack/README.md: -------------------------------------------------------------------------------- 1 | # PHAR Archive 2 | 3 | You can compile the `simple-mvc` phar archive. First of all 4 | download dependencies using Composer. 5 | 6 | ```shell 7 | $ curl -s http://getcomposer.org/installer | php 8 | $ php composer.phar install 9 | ``` 10 | 11 | Now you can run the compile operation 12 | 13 | ```shell 14 | $ ./compile 15 | ``` 16 | 17 | A `simple-mvc.phar` file will be created. Use it into your apps. 18 | 19 | ```php 20 | files() 12 | ->name('*.php') 13 | ->in(__DIR__ . "/../src"); 14 | 15 | $phar = new \Phar("simple-mvc.phar"); 16 | $phar->setSignatureAlgorithm(\Phar::SHA1); 17 | 18 | $phar->startBuffering(); 19 | $phar->setStub(getStub()); 20 | foreach ($iterator as $file) { 21 | $phar->addFile($file->getRealpath(), $file->getBasename()); 22 | echo "\033[34m{$file->getRealpath()}\033[37m -> \033[32m{$file->getBasename()}\033[37m\n"; 23 | } 24 | 25 | $phar->stopBuffering(); 26 | $phar->compressFiles(\Phar::GZ); 27 | unset($phar); 28 | 29 | function getStub() 30 | { 31 | return << 37 | * 38 | * This source file is subject to the MIT license that is bundled 39 | * with this source code in the file LICENSE. 40 | */ 41 | 42 | Phar::mapPhar('simple-mvc.phar'); 43 | 44 | require_once 'phar://simple-mvc.phar/Loader.php'; 45 | Loader::classmap(); 46 | __HALT_COMPILER(); 47 | EOF; 48 | } -------------------------------------------------------------------------------- /pack/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "symfony/finder": "*" 4 | } 5 | } -------------------------------------------------------------------------------- /pack/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "47587ad7333cb232942456948370f378", 3 | "packages": [ 4 | { 5 | "package": "symfony/finder", 6 | "version": "dev-master", 7 | "source-reference": "v2.1.0-BETA1", 8 | "commit-date": "1340118982" 9 | } 10 | ], 11 | "packages-dev": null, 12 | "aliases": [ 13 | 14 | ], 15 | "minimum-stability": "dev", 16 | "stability-flags": [ 17 | 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | ./tests 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | ./tests 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | _bootstrap = ($bootstrap) ? $bootstrap : new Bootstrap(); 15 | $this->_eventManager = ($eventManager) ? $eventManager : new EventManager(); 16 | } 17 | 18 | public function setControllerPath($path) 19 | { 20 | $this->_controllerPath = $path; 21 | } 22 | 23 | public function getControllerPath() 24 | { 25 | return $this->_controllerPath; 26 | } 27 | 28 | public function setEventManager(EventManager $manager) 29 | { 30 | $this->_eventManager = $manager; 31 | } 32 | 33 | public function getEventManager() 34 | { 35 | return $this->_eventManager; 36 | } 37 | 38 | public function bootstrap($name, $hook) 39 | { 40 | $this->_bootstrap->addResource($name, $hook); 41 | } 42 | 43 | public function getBootstrap() 44 | { 45 | return $this->_bootstrap; 46 | } 47 | 48 | public function getRequest() 49 | { 50 | return $this->_request; 51 | } 52 | 53 | public function dispatch(Route $route) 54 | { 55 | $protoView = ($this->getBootstrap()->getResource("view")) ? $this->getBootstrap()->getResource("view") : new View(); 56 | 57 | $controllerPath = $this->getControllerPath(); 58 | $router = $this->_router; 59 | $request = $this->_request; 60 | $protoView->addHelper("pull", function($uri) use ($controllerPath, $router, $request) { 61 | $request = clone $request; 62 | $request->setUri($uri); 63 | $routeObj = $router->match($request); 64 | 65 | $controllerClassName = $routeObj->getControllerName() . "Controller"; 66 | $action = $routeObj->getActionName() . "Action"; 67 | $classPath = realpath($controllerPath . DIRECTORY_SEPARATOR . $controllerClassName . ".php"); 68 | if (file_exists($classPath)) { 69 | require_once $classPath; 70 | 71 | $controller = new $controllerClassName(); 72 | $controller->setParams($routeObj->getParams()); 73 | 74 | if (method_exists($controller, $action)) { 75 | ob_start(); 76 | $controller->init(); 77 | $data = $controller->$action(); 78 | ob_end_clean(); 79 | return $data; 80 | } else { 81 | throw new RuntimeException("Pull operation {$routeObj->getControllerName()} - {$routeObj->getActionName()} failed.", 404); 82 | } 83 | } else { 84 | throw new RuntimeException("Pull operation {$routeObj->getControllerName()} - {$routeObj->getActionName()} failed.", 404); 85 | } 86 | }); 87 | 88 | $dispatcher = new Dispatcher($protoView); 89 | $dispatcher->setRouter($this->_router); 90 | $dispatcher->setRequest($this->_request); 91 | $dispatcher->setEventManager($this->getEventManager()); 92 | $dispatcher->setBootstrap($this->_bootstrap); 93 | $dispatcher->setControllerPath($this->getControllerPath()); 94 | 95 | try { 96 | $this->_page = $dispatcher->dispatch($route); 97 | } catch (Exception $e) { 98 | $errorRoute = new Route(); 99 | $errorRoute->addParams( 100 | array( 101 | 'exception' => $e 102 | ) 103 | ); 104 | 105 | $dispatcher->clearHeaders(); 106 | $dispatcher->addHeader("","",404); 107 | 108 | $errorRoute->setControllerName("error"); 109 | $errorRoute->setActionName("error"); 110 | 111 | $this->_page = $dispatcher->dispatch($errorRoute); 112 | } 113 | 114 | return array('headers' => $dispatcher->getHeaders()); 115 | } 116 | 117 | public function run(Request $request = null) 118 | { 119 | $this->_router = ($this->getBootstrap()->getResource("router")) ? $this->getBootstrap()->getResource("router") : new Router(); 120 | $this->_request = (!$request) ? Request::newHttp() : $request; 121 | 122 | $outputBuffer = ''; 123 | $this->getEventManager()->publish("loop.startup", array($this)); 124 | 125 | $status = $this->dispatch($this->_router->match($this->_request)); 126 | 127 | if (($layout = $this->getBootstrap()->getResource("layout")) instanceof Layout) { 128 | $layout->content = $this->_page; 129 | 130 | $outputBuffer = $layout->render($layout->getScriptName()); 131 | } else { 132 | $outputBuffer = $this->_page; 133 | } 134 | 135 | $this->getEventManager()->publish("loop.shutdown", array($this)); 136 | 137 | $this->sendHeaders($status["headers"]); 138 | echo $outputBuffer; 139 | } 140 | 141 | public function addRequest($uri) 142 | { 143 | $this->_requests[] = $uri; 144 | } 145 | 146 | public function sendHeaders($headers) 147 | { 148 | foreach ($headers as $header) { 149 | header($header["string"], $header["replace"], $header["code"]); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Bootstrap.php: -------------------------------------------------------------------------------- 1 | _bootstrap[$name] = $hook; 13 | } 14 | 15 | public function getResource($name) 16 | { 17 | if (array_key_exists($name, $this->_bootstrap)) { 18 | $b = $this->_bootstrap[$name]; 19 | 20 | if (is_callable($b)) { 21 | $this->_bootstrap[$name] = call_user_func($b); 22 | } 23 | 24 | return $this->_bootstrap[$name]; 25 | } else { 26 | return false; 27 | } 28 | } 29 | 30 | public function testMissingBootstrapResource() 31 | { 32 | $this->assertSame(false, $this->object->getResource("missing")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Controller.php: -------------------------------------------------------------------------------- 1 | _request = $request; 16 | } 17 | 18 | public function getRequest() 19 | { 20 | return $this->_request; 21 | } 22 | 23 | public function setRouter(Router $router) 24 | { 25 | $this->_router = $router; 26 | } 27 | 28 | public function getRouter() 29 | { 30 | return $this->_router; 31 | } 32 | 33 | public function setView($view) 34 | { 35 | $this->view = $view; 36 | } 37 | 38 | public function getView() 39 | { 40 | return $this->view; 41 | } 42 | 43 | public function init(){} 44 | 45 | public function setParams($params) 46 | { 47 | $this->_params = $params; 48 | } 49 | 50 | public function getParams() 51 | { 52 | return $this->_params; 53 | } 54 | 55 | public function getParam($key) 56 | { 57 | return (array_key_exists($key, $this->_params)) ? $this->_params[$key] : false; 58 | } 59 | 60 | public function setRawBody($body) 61 | { 62 | $this->_rawBody = $body; 63 | } 64 | 65 | public function getRawBody() 66 | { 67 | return $this->_rawBody; 68 | } 69 | 70 | public function then($uri) 71 | { 72 | $request = clone $this->_request; 73 | $request->setUri($uri); 74 | 75 | $route = $this->_router->match($request); 76 | $this->_params["dispatcher"]->add($route); 77 | } 78 | 79 | public function clearHeaders() 80 | { 81 | $this->_params["dispatcher"]->clearHeaders(); 82 | } 83 | 84 | public function addHeader($key, $value, $httpCode = 200, $replace = true) 85 | { 86 | $this->_params["dispatcher"]->addHeader($key, $value, $httpCode, $replace); 87 | return $this; 88 | } 89 | 90 | public function getHeaders() 91 | { 92 | return $this->_params["dispatcher"]->getHeaders(); 93 | } 94 | 95 | /** 96 | * Get a resource from bootstrap 97 | * 98 | * @param The name of resource 99 | * @return mixed The resource 100 | */ 101 | public function getResource($name) 102 | { 103 | return $this->_params["dispatcher"] 104 | ->getBootstrap()->getResource($name); 105 | } 106 | 107 | /** 108 | * Using the dispatcher 109 | */ 110 | public function redirect($url, $header=301) 111 | { 112 | $this->disableLayout(); 113 | $this->setNoRender(); 114 | 115 | $this->_params["dispatcher"]->clearHeaders(); 116 | $this->_params["dispatcher"]->addHeader("Location", $url, $header); 117 | } 118 | 119 | public function setRenderer($renderer) 120 | { 121 | $this->_viewScript = $renderer; 122 | } 123 | 124 | public function getViewPath() 125 | { 126 | return ($this->_viewScript) ? $this->_viewScript . ".phtml" : false; 127 | } 128 | 129 | public function disableLayout() 130 | { 131 | $this->_params["dispatcher"]->getBootstrap() 132 | ->addResource("layout", function(){ 133 | return null; 134 | }); 135 | } 136 | 137 | public function setNoRender() 138 | { 139 | $this->view = new View(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Dispatcher.php: -------------------------------------------------------------------------------- 1 | _view = $view; 25 | $this->_actions = array(); 26 | $this->_viewsQueue = array(); 27 | } 28 | 29 | public function setRouter(Router $router) 30 | { 31 | $this->_router = $router; 32 | } 33 | 34 | public function getRouter() 35 | { 36 | return $this->_router; 37 | } 38 | 39 | public function setRequest(Request $request) 40 | { 41 | $this->_request = $request; 42 | } 43 | 44 | public function getRequest() 45 | { 46 | return $this->_request; 47 | } 48 | 49 | public function setControllerPath($path) 50 | { 51 | $this->_controllerPath = $path; 52 | } 53 | 54 | public function getControllerPath() 55 | { 56 | return $this->_controllerPath; 57 | } 58 | 59 | public function setEventManager(EventManager $em) 60 | { 61 | $this->_eventManager = $em; 62 | } 63 | 64 | public function getEventManager() 65 | { 66 | return $this->_eventManager; 67 | } 68 | 69 | public function setBootstrap($bootstrap) 70 | { 71 | $this->_bootstrap = $bootstrap; 72 | } 73 | 74 | public function getBootstrap() 75 | { 76 | return $this->_bootstrap; 77 | } 78 | 79 | public function add(Route $route) 80 | { 81 | $this->_actions[] = $route; 82 | } 83 | 84 | /** 85 | * dispatch an action 86 | */ 87 | public function dispatch(Route $route) 88 | { 89 | do { 90 | $this->getEventManager()->publish("pre.dispatch", array($route, $this)); 91 | 92 | $protoView = $this->_view->cloneThis(); 93 | $controllerClassName = $route->getControllerName() . "Controller"; 94 | $action = $route->getActionName() . "Action"; 95 | $classPath = $this->getControllerPath() . DIRECTORY_SEPARATOR . $controllerClassName . ".php"; 96 | $viewPath = $route->getControllerPath() 97 | . DIRECTORY_SEPARATOR 98 | . $route->getActionPath() 99 | . $protoView->getViewExt(); 100 | 101 | if (!file_exists($classPath)) { 102 | // Use base controller 103 | $controllerClassName = 'Controller'; 104 | } else { 105 | require_once $classPath; 106 | } 107 | 108 | $controller = new $controllerClassName($this); 109 | $controller->setRouter($this->getRouter()); 110 | $controller->setRequest($this->getRequest()); 111 | $controller->setParams( 112 | array_merge( 113 | array( 114 | "dispatcher" => $this, 115 | "bootstrap" => $this->getBootstrap() 116 | ), 117 | $route->getParams())); 118 | $controller->setRawBody(@file_get_contents('php://input')); 119 | 120 | $controller->setView($protoView->cloneThis()); 121 | $controller->view->controllerPath = $this->getControllerPath(); 122 | 123 | if (method_exists($controller, $action)) { 124 | ob_start(); 125 | $controller->init(); 126 | $controller->$action(); 127 | array_push($this->_viewsQueue, ob_get_contents()); 128 | ob_end_clean(); 129 | } else { 130 | if (!file_exists($controller->view->getViewPath() . DIRECTORY_SEPARATOR . $viewPath)) { 131 | throw new RuntimeException("Page not found {$route->getControllerName()} -> {$route->getActionName()}", 404); 132 | } 133 | } 134 | 135 | if ($controller->view->getViewPath()) { 136 | array_push( 137 | $this->_viewsQueue, 138 | $controller->getView()->render( 139 | (($controller->getViewPath() !== false) ? $controller->getViewPath() : $viewPath)) 140 | ); 141 | } 142 | 143 | $this->getEventManager()->publish("post.dispatch", array($this)); 144 | } while(($route = array_shift($this->_actions))); 145 | 146 | return implode("", $this->_viewsQueue); 147 | } 148 | 149 | public function sendHeaders() 150 | { 151 | $headers = $this->getHeaders(); 152 | foreach ($headers as $header) { 153 | header($header["string"], $header["replace"], $header["code"]); 154 | } 155 | } 156 | 157 | public function clearHeaders() 158 | { 159 | $this->_headers = array(); 160 | } 161 | 162 | public function addHeader($key, $value, $httpCode = 200, $replace = true) 163 | { 164 | $this->_headers[] = array('string' => "{$key}:{$value}", "replace" => $replace, "code" => (int)$httpCode); 165 | } 166 | 167 | public function getHeaders() 168 | { 169 | return $this->_headers; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/EventManager.php: -------------------------------------------------------------------------------- 1 | _listeners[$event])) { 13 | $this->_listeners[$event] = array(); 14 | } 15 | 16 | $this->_listeners[$event][] = $listener; 17 | } 18 | 19 | public function publish($event, array $arguments = array()) 20 | { 21 | if ($this->_listeners($event)) { 22 | foreach ($this->_listeners($event) as $listener) { 23 | call_user_func_array($listener, $arguments); 24 | } 25 | } 26 | } 27 | 28 | protected function _listeners($event) 29 | { 30 | if ( 31 | $this->_listeners && is_array($this->_listeners) && 32 | array_key_exists($event, $this->_listeners) 33 | ) { 34 | return $this->_listeners[$event]; 35 | } else { 36 | return array(); 37 | } 38 | } 39 | 40 | public function clear($event = null) 41 | { 42 | if ($event !== null) { 43 | unset($this->_listeners[$event]); 44 | } else { 45 | $this->_listeners = array(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Layout.php: -------------------------------------------------------------------------------- 1 | _scriptName = $name; 14 | } 15 | 16 | public function getScriptName() 17 | { 18 | return $this->_scriptName; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Loader.php: -------------------------------------------------------------------------------- 1 | setUri($uri); 14 | } 15 | 16 | public function setHostname($hostname) 17 | { 18 | $this->_hostname = $hostname; 19 | } 20 | 21 | public function getHostname() 22 | { 23 | return $this->_hostname; 24 | } 25 | 26 | public function setUri($uri) 27 | { 28 | $this->_uri = $uri; 29 | } 30 | 31 | public function getUri() 32 | { 33 | return $this->_uri; 34 | } 35 | 36 | public function setGetParams($params) 37 | { 38 | $this->_getParams = $params; 39 | } 40 | 41 | public function getGetParams() 42 | { 43 | return $this->_getParams; 44 | } 45 | 46 | public function setPostParams($params) 47 | { 48 | $this->_postParams = $params; 49 | } 50 | 51 | public function getPostParams() 52 | { 53 | return $this->_postParams; 54 | } 55 | 56 | public function setParams(array $params) 57 | { 58 | $this->_params = $params; 59 | } 60 | 61 | public function addParam($key, $value) 62 | { 63 | $this->_params[$key] = $value; 64 | } 65 | 66 | public function getParams() 67 | { 68 | return $this->_params; 69 | } 70 | 71 | public function setRawBody($body) 72 | { 73 | $this->_rawBody = $body; 74 | } 75 | 76 | public function getRawBody() 77 | { 78 | return $this->_rawBody; 79 | } 80 | 81 | public static function newHttp() 82 | { 83 | $request = new Request(); 84 | $request->setHostname($_SERVER["SERVER_NAME"]); 85 | $request->setUri($_SERVER["REQUEST_URI"]); 86 | $request->setGetParams($_GET); 87 | $request->setPostParams($_POST); 88 | $request->setRawBody(@file_get_contents('php://input')); 89 | 90 | return $request; 91 | } 92 | 93 | public static function fromXml($path) 94 | { 95 | $xml = simplexml_load_string($path); 96 | $this->setHostname((string)$xml->hostname); 97 | $this->setUri((string)$xml->uri); 98 | 99 | //TODO: end this section 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Route.php: -------------------------------------------------------------------------------- 1 | _route["controller"] = ucfirst($this->_toCamelCase($name)); 12 | $this->_route["controller-clear"] = $name; 13 | } 14 | 15 | public function getControllerName() 16 | { 17 | return $this->_route["controller"]; 18 | } 19 | 20 | public function getControllerPath() 21 | { 22 | return $this->_route["controller-clear"]; 23 | } 24 | 25 | public function getActionName() 26 | { 27 | return $this->_route["action"]; 28 | } 29 | 30 | public function setActionName($name) 31 | { 32 | $this->_route["action"] = $this->_toCamelCase($name); 33 | $this->_route["action-clear"] = $name; 34 | } 35 | 36 | public function getActionPath() 37 | { 38 | return $this->_route["action-clear"]; 39 | } 40 | 41 | /** 42 | * Merge parent route with this 43 | * 44 | * @param Route $route The parent route 45 | * 46 | * @return Route mixed route 47 | */ 48 | public function merge($parent) 49 | { 50 | $merged = clone $this; 51 | if ($parent) { 52 | //Merge with parent route 53 | ($this->getActionName()) ? 54 | $merged->setActionName($this->getActionName()) : 55 | $merged->setActionName($parent->getActioName()); 56 | 57 | ($this->getControllerName()) ? 58 | $merged->setControllerName($this->getControllerName()) : 59 | $merged->setControllerName($parent->getControllerName()); 60 | 61 | $this->addParams(array_merge($parent->getParams(), $this->getParams())); 62 | } 63 | 64 | return $merged; 65 | } 66 | 67 | private function _toCamelCase($part) 68 | { 69 | $pos = 0; 70 | while(($pos = strpos($part, "-", $pos)) !== false && $pos < strlen($part)) { 71 | if ($pos+1 < strlen($part)) { 72 | $part[$pos+1] = strtoupper($part[$pos+1]); 73 | } 74 | ++$pos; 75 | } 76 | return str_replace("-", "", $part); 77 | } 78 | 79 | public function getRoute() 80 | { 81 | return $this->_route; 82 | } 83 | 84 | public function getParams() 85 | { 86 | return $this->_params; 87 | } 88 | 89 | public function addParams($params) 90 | { 91 | $this->_params = array_merge($this->_params, $params); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Router.php: -------------------------------------------------------------------------------- 1 | _childs[$name] = $router; 9 | } 10 | 11 | /** 12 | * Match a request on a route 13 | * 14 | * @param Request the actual request 15 | * @return Route 16 | */ 17 | public function match(Request $request, $route = false) 18 | { 19 | if (count($this->_childs)) { 20 | $route = $this->_match($request, $route); 21 | } 22 | 23 | if (!$route) { 24 | $route = $this->_default($request)->merge($route); 25 | } 26 | 27 | 28 | if (!$route) { 29 | throw new RuntimeException("Missing route..."); 30 | } 31 | 32 | return $route; 33 | } 34 | 35 | protected function _match($request, $route) 36 | { 37 | foreach ($this->_childs as $router) { 38 | $route = $router->match($request, $route); 39 | if ($route instanceOf Route) { 40 | return $route; 41 | } 42 | } 43 | 44 | return false; 45 | } 46 | 47 | private function _filter($parts) 48 | { 49 | $clean = array(); 50 | foreach ($parts as $part) { 51 | $part = trim($part); 52 | if (!empty($part)) { 53 | $clean[] = $part; 54 | } 55 | } 56 | 57 | return $clean; 58 | } 59 | 60 | 61 | protected function _default(Request $request) 62 | { 63 | $route = new Route(); 64 | $uri = $request->getUri(); 65 | 66 | $uri = (strpos($uri, "?") !== false) ? substr($uri, 0, strpos($uri, "?")) : $uri; 67 | $parts = explode("/", $uri); 68 | 69 | $parts = $this->_filter($parts); 70 | 71 | switch (count($parts)) { 72 | case 0: 73 | $route->setControllerName("index"); 74 | $route->setActionName("index"); 75 | break; 76 | case 1: 77 | $route->setControllerName($parts[0]); 78 | $route->setActionName("index"); 79 | array_shift($parts); 80 | break; 81 | default: 82 | $route->setControllerName($parts[0]); 83 | $route->setActionName($parts[1]); 84 | array_shift($parts); 85 | array_shift($parts); 86 | break; 87 | } 88 | 89 | (count($parts) % 2 !== 0) ? array_pop($parts) : false; 90 | 91 | if (count($parts)) { 92 | for ($i=0; $iaddParam($parts[$i], $parts[$i+1]); 94 | } 95 | } 96 | 97 | return $route; 98 | } 99 | 100 | 101 | public function assemble($params, $name = false) 102 | { 103 | if (!$name) { 104 | //Assemble this route 105 | } else { 106 | return $this->_childs[$name]->assemble($params); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/View.php: -------------------------------------------------------------------------------- 1 | _charset; 16 | 17 | $this->addHelper("escape", function($text, $flags = ENT_COMPAT, $charset = null, $doubleEncode = true) use ($cs) { 18 | return htmlspecialchars($text, $flags, $charset ?: $cs, $doubleEncode); 19 | }); 20 | 21 | $this->addHelper("partial", function($path, $data) use ($view) { 22 | $view = $view->cloneThis(); 23 | return $view->render($path, $data); 24 | }); 25 | } 26 | 27 | public function __set($key, $value) 28 | { 29 | $this->_data[$key] = $value; 30 | } 31 | 32 | public function __get($key) 33 | { 34 | return (isset($this->_data[$key])) ? $this->_data[$key] : false; 35 | } 36 | 37 | public function __call($method, $args) 38 | { 39 | if (array_key_exists($method, static::$_helpers)) { 40 | return call_user_func_array(static::$_helpers[$method], $args); 41 | } else { 42 | throw new RuntimeException("Helper view {$method} doesn't exists. Add it using addHelper method."); 43 | } 44 | } 45 | 46 | public function addViewPath($path) 47 | { 48 | if (!is_dir($path)) { 49 | throw new RuntimeException("View path {$path} must be a directory"); 50 | } 51 | $this->_path[] = $path; 52 | } 53 | 54 | public function getViewPaths() 55 | { 56 | return $this->_path; 57 | } 58 | 59 | /** 60 | * @deprecated This method is deprecated use addViewPath() 61 | */ 62 | public function setViewPath($path) 63 | { 64 | if (!is_dir($path)) { 65 | throw new RuntimeException("View path {$path} must be a directory"); 66 | } 67 | $this->_path = array($path); 68 | } 69 | 70 | /** 71 | * @deprecated This method is deprecated in favor of getViewPaths() 72 | */ 73 | public function getViewPath() 74 | { 75 | return (count($this->_path) > 0) ? $this->_path[0] : false; 76 | } 77 | 78 | public function render($filename, $data = false) 79 | { 80 | if($data) { 81 | if (!is_array($data)) { 82 | throw new RuntimeException("You must pass an array to data view."); 83 | } 84 | $this->_data = array_merge($this->_data, $data); 85 | } 86 | 87 | $filename = $this->_selectView($this->getViewPaths(), $filename); 88 | 89 | $rendered = ""; 90 | 91 | ob_start(); 92 | require($filename); 93 | $rendered = ob_get_contents(); 94 | ob_end_clean(); 95 | 96 | return $rendered; 97 | } 98 | 99 | protected function _selectView($paths, $filename) 100 | { 101 | $p = implode(PATH_SEPARATOR, $paths); 102 | 103 | for ($i=count($paths)-1; $i>=0; $i--) { 104 | $f = $paths[$i] . "/" . $filename ; 105 | 106 | if (file_exists($f)) { 107 | return $f; 108 | } 109 | } 110 | 111 | throw new RuntimeException("Unable to get view {$filename} in paths: {$p}"); 112 | } 113 | 114 | public function cloneThis() 115 | { 116 | return clone($this); 117 | } 118 | 119 | public function addHelper($name, $helper) 120 | { 121 | static::$_helpers[$name] = $helper; 122 | } 123 | 124 | public function addHelpers(array $helpers) 125 | { 126 | static::$_helpers = array_merge(self::$_helpers, $helpers); 127 | } 128 | 129 | public function getHelpers() 130 | { 131 | return static::$_helpers; 132 | } 133 | 134 | protected function _getData() 135 | { 136 | return $this->_data; 137 | } 138 | 139 | public function setViewExt($ext) 140 | { 141 | $this->_ext = $ext; 142 | } 143 | 144 | public function getViewExt() 145 | { 146 | return $this->_ext; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tests/ApplicationTest.php: -------------------------------------------------------------------------------- 1 | object = $this->getMock( 23 | "Application", 24 | array('sendHeaders') 25 | ); 26 | $this->object->setControllerPath(__DIR__ . '/controllers'); 27 | $this->object 28 | ->expects($this->any()) 29 | ->method("sendHeaders") 30 | ->will($this->returnValue(null)); 31 | } 32 | 33 | /** 34 | * Tears down the fixture, for example, closes a network connection. 35 | * This method is called after a test is executed. 36 | */ 37 | protected function tearDown() 38 | { 39 | } 40 | 41 | public function testDefaultBootstrapInstance() 42 | { 43 | $this->assertInstanceOf("Bootstrap", $this->object->getBootstrap()); 44 | } 45 | 46 | /** 47 | * @covers Application::bootstrap 48 | * @covers Application::getBootstrap 49 | */ 50 | public function testBootstrap() 51 | { 52 | $this->object->bootstrap("hello", function(){return "ciao";}); 53 | $boot = $this->object->getBootstrap()->getResource("hello"); 54 | 55 | $this->assertEquals($boot, "ciao"); 56 | } 57 | 58 | /** 59 | * Resources must bootstrap onetime 60 | * 61 | * @covers Application::getBootstrap 62 | */ 63 | public function testGetMultipleTimes() 64 | { 65 | $this->object->bootstrap("hello", function(){ 66 | return new View(); 67 | }); 68 | $boot = $this->object->getBootstrap()->getResource("hello"); 69 | $boot2 = $this->object->getBootstrap()->getResource("hello"); 70 | 71 | $this->assertSame($boot, $boot2); 72 | } 73 | 74 | public function testSetGetControllerPath() 75 | { 76 | $this->object->setControllerPath(__DIR__); 77 | 78 | $this->assertEquals(__DIR__, $this->object->getControllerPath()); 79 | } 80 | 81 | public function testSetGetEventManager() 82 | { 83 | $mng = new EventManager(); 84 | $this->object->setEventManager($mng); 85 | 86 | $this->assertSame($mng, $this->object->getEventManager()); 87 | } 88 | 89 | /** 90 | * @expectedException RuntimeException 91 | */ 92 | public function testBootstrapNotCallable() 93 | { 94 | $this->object->bootstrap("up", "not-callable"); 95 | } 96 | 97 | public function testMissingLayout() 98 | { 99 | $request = new Request(); 100 | $request->setUri("/error/error"); 101 | 102 | $this->object->bootstrap("view", function(){ 103 | $v = new View(); 104 | $v->setViewPath(__DIR__ . '/views'); 105 | 106 | return $v; 107 | }); 108 | $this->object->setControllerPath(__DIR__ . '/controllers'); 109 | 110 | ob_start(); 111 | $this->object->run($request); 112 | $content = ob_get_contents(); 113 | ob_end_clean(); 114 | 115 | $this->assertEquals("--> error action <--", $content); 116 | 117 | } 118 | 119 | public function testErrorPages() 120 | { 121 | $request = new Request(); 122 | $request->setUri("/invalid/controller"); 123 | 124 | ob_start(); 125 | $this->object->run($request); 126 | $errorPage = ob_get_contents(); 127 | ob_end_clean(); 128 | 129 | $this->assertEquals("--> error action <--", $errorPage); 130 | } 131 | 132 | public function testInitAction() 133 | { 134 | ob_start(); 135 | $this->object->run(new Request("init/index")); 136 | $initOutput = ob_get_contents(); 137 | ob_end_clean(); 138 | 139 | $this->assertEquals("<-- init -->", $initOutput); 140 | } 141 | 142 | public function testPreDispatchHook() 143 | { 144 | $app = ''; 145 | $this->object->getEventManager()->subscribe("pre.dispatch", function($r, $app){ 146 | $r->setControllerName("admin"); 147 | $r->setActionName("login"); 148 | }); 149 | 150 | ob_start(); 151 | $this->object->run(new Request("/init/index")); 152 | $adminOutput = ob_get_contents(); 153 | ob_end_clean(); 154 | 155 | $this->assertEquals("<-- admin login -->", $adminOutput); 156 | } 157 | 158 | public function testThenMethod() 159 | { 160 | ob_start(); 161 | $this->object->run(new Request("/then/first")); 162 | $thenOutput = ob_get_contents(); 163 | ob_end_clean(); 164 | 165 | $this->assertEquals("first-><-second", $thenOutput); 166 | } 167 | 168 | /** 169 | * @expectedException RuntimeException 170 | */ 171 | public function testMissingAction() 172 | { 173 | $this->object->setControllerPath(__DIR__); 174 | $this->object->run(new Request("/admin/missing-action")); 175 | } 176 | 177 | public function testMissingEventManager() 178 | { 179 | $app = new Application(); 180 | $eventManager = $app->getEventManager(); 181 | $this->assertInstanceOf("EventManager", $eventManager); 182 | } 183 | 184 | public function testLayout() 185 | { 186 | $this->object->bootstrap("layout", function(){ 187 | $l = new Layout(); 188 | $l->setScriptName("layout.phtml"); 189 | $l->setViewPath(__DIR__ . '/layouts'); 190 | return $l; 191 | }); 192 | 193 | ob_start(); 194 | $this->object->run(new Request("/init/index")); 195 | $content = ob_get_contents(); 196 | ob_end_clean(); 197 | 198 | $this->assertEquals("<-- init -->", $content); 199 | } 200 | 201 | public function testLayoutViewHelpersPass() 202 | { 203 | 204 | $this->object->bootstrap('layout', function(){ 205 | $l = new Layout(); 206 | $l->setScriptName("title-helper.phtml"); 207 | $l->setViewPath(__DIR__ . '/layouts'); 208 | 209 | 210 | return $l; 211 | }); 212 | 213 | $this->object->bootstrap('view', function(){ 214 | $v = new View(); 215 | $v->setViewPath(__DIR__ . '/views'); 216 | 217 | $v->addHelper("title", function($part = false){ 218 | static $parts = array(); 219 | static $delimiter = ' :: '; 220 | 221 | return ($part === false) ? implode($delimiter, $parts) : $parts[] = $part; 222 | }); 223 | 224 | return $v; 225 | }); 226 | 227 | ob_start(); 228 | $this->object->run(new Request("/general/title-helper")); 229 | $content = ob_get_contents(); 230 | ob_end_clean(); 231 | 232 | $this->assertEquals("the title helper :: second", $content); 233 | } 234 | 235 | public function testEmptyPullDrivenRequest() 236 | { 237 | $this->object->bootstrap("view", function(){ 238 | $v = new View(); 239 | $v->setViewPath(__DIR__ . '/views'); 240 | 241 | return $v; 242 | }); 243 | 244 | ob_start(); 245 | $this->object->run(new Request("/general/pull-driven")); 246 | $content = ob_get_contents(); 247 | ob_end_clean(); 248 | 249 | $this->assertEquals("

Pull-driven experience

", $content); 250 | } 251 | 252 | public function testCompletelyMissingPullDrivenRequest() 253 | { 254 | $this->object->setControllerPath(null); 255 | $this->object->bootstrap("view", function(){ 256 | $v = new View(); 257 | $v->setViewPath(__DIR__ . '/views'); 258 | 259 | return $v; 260 | }); 261 | 262 | ob_start(); 263 | $this->object->run(new Request("/pull/driven")); 264 | $content = ob_get_contents(); 265 | ob_end_clean(); 266 | 267 | $this->assertEquals("

Complete pull driven

", $content); 268 | } 269 | 270 | public function testCompletelyMissingPullWithDataDrivenRequest() 271 | { 272 | $this->object->bootstrap("view", function(){ 273 | $v = new View(); 274 | $v->setViewPath(__DIR__ . '/views'); 275 | 276 | return $v; 277 | }); 278 | 279 | ob_start(); 280 | $this->object->run(new Request("/pull/driven-data")); 281 | $content = ob_get_contents(); 282 | ob_end_clean(); 283 | 284 | $this->assertEquals("

Controller Data

", $content); 285 | } 286 | 287 | public function testMissingControllerPull() 288 | { 289 | $this->object->bootstrap("view", function(){ 290 | $v = new View(); 291 | $v->setViewPath(__DIR__ . '/views'); 292 | 293 | return $v; 294 | }); 295 | 296 | ob_start(); 297 | $this->object->run(new Request("/pull/missing-pull")); 298 | $content = ob_get_contents(); 299 | ob_end_clean(); 300 | 301 | $this->assertEquals("--> error action <--", $content); 302 | } 303 | 304 | public function testMissingActionPull() 305 | { 306 | $this->object->bootstrap("view", function(){ 307 | $v = new View(); 308 | $v->setViewPath(__DIR__ . '/views'); 309 | 310 | return $v; 311 | }); 312 | 313 | ob_start(); 314 | $this->object->run(new Request("/pull/missing-pull-action")); 315 | $content = ob_get_contents(); 316 | ob_end_clean(); 317 | 318 | $this->assertEquals("--> error action <--", $content); 319 | } 320 | 321 | public function testViewSwitch() 322 | { 323 | $this->object->bootstrap("view", function(){ 324 | $v = new View(); 325 | $v->setViewPath(__DIR__ . '/views'); 326 | 327 | return $v; 328 | }); 329 | 330 | ob_start(); 331 | $this->object->run(new Request("/general/a")); 332 | $content = ob_get_contents(); 333 | ob_end_clean(); 334 | 335 | $this->assertEquals("This is B", $content); 336 | } 337 | 338 | public function testViewRewrited() 339 | { 340 | $this->object->bootstrap("view", function(){ 341 | $v = new View(); 342 | $v->addViewPath(__DIR__ . '/views'); 343 | $v->addViewPath(__DIR__ . '/views-rewrite'); 344 | 345 | return $v; 346 | }); 347 | 348 | ob_start(); 349 | $this->object->run(new Request("/general/c")); 350 | $content = ob_get_contents(); 351 | ob_end_clean(); 352 | 353 | $this->assertEquals("This is C but rewrited", $content); 354 | } 355 | 356 | public function testViewPullRewrited() 357 | { 358 | $this->object->bootstrap("view", function(){ 359 | $v = new View(); 360 | $v->addViewPath(__DIR__ . '/views'); 361 | $v->addViewPath(__DIR__ . '/views-rewrite'); 362 | 363 | return $v; 364 | }); 365 | 366 | ob_start(); 367 | $this->object->run(new Request("/general/d")); 368 | $content = ob_get_contents(); 369 | ob_end_clean(); 370 | 371 | $this->assertEquals("This is D but rewrited", $content); 372 | } 373 | 374 | public function testPartialViewRewrite() 375 | { 376 | $this->object->bootstrap("view", function(){ 377 | $v = new View(); 378 | $v->addViewPath(__DIR__ . '/views'); 379 | $v->addViewPath(__DIR__ . '/views-rewrite'); 380 | 381 | return $v; 382 | }); 383 | 384 | ob_start(); 385 | $this->object->run(new Request("/general/partial-eg")); 386 | $content = ob_get_contents(); 387 | ob_end_clean(); 388 | 389 | $this->assertEquals('

ciao

', $content); 390 | } 391 | 392 | 393 | 394 | public function testBufferOutPullRequest() 395 | { 396 | $this->object->bootstrap("view", function(){ 397 | $v = new View(); 398 | $v->addViewPath(__DIR__ . '/views'); 399 | 400 | return $v; 401 | }); 402 | 403 | ob_start(); 404 | $this->object->run(new Request("/pull/buffer-out")); 405 | $content = ob_get_contents(); 406 | ob_end_clean(); 407 | 408 | $this->assertEquals('

ret from out

', $content); 409 | } 410 | 411 | public function testLoopStartupHook() 412 | { 413 | $loopStartup = 0; 414 | $this->object->getEventManager()->subscribe( 415 | "loop.startup", function ($app) use (&$loopStartup){ 416 | 417 | $loopStartup += 1; 418 | }); 419 | 420 | ob_start(); 421 | $this->object->run(new Request("/")); 422 | ob_end_clean(); 423 | 424 | $this->assertSame(1, $loopStartup); 425 | } 426 | 427 | public function testLoopShutdownHook() 428 | { 429 | $shutdown = 0; 430 | $this->object->getEventManager()->subscribe( 431 | "loop.shutdown", function ($app) use (&$shutdown) { 432 | $shutdown += 1; 433 | } 434 | ); 435 | 436 | ob_start(); 437 | $this->object->run(new Request("/")); 438 | ob_end_clean(); 439 | $this->assertSame(1, $shutdown); 440 | } 441 | 442 | public function testPrePostDispatch() 443 | { 444 | $preDispatch = 0; 445 | $postDispatch = 0; 446 | 447 | $this->object->getEventManager()->subscribe( 448 | "pre.dispatch", function ($router, $app) use (&$preDispatch) { 449 | ++$preDispatch; 450 | var_dump(1); 451 | } 452 | ); 453 | 454 | $this->object->getEventManager()->subscribe( 455 | "post.dispatch", function ($app) use (&$postDispatch) { 456 | ++$postDispatch; 457 | } 458 | ); 459 | 460 | ob_start(); 461 | $this->object->run(new Request("/admin/login")); 462 | ob_end_clean(); 463 | 464 | $this->assertSame(1, $preDispatch); 465 | $this->assertSame(1, $postDispatch); 466 | } 467 | 468 | public function testMultiplePrePostDispatch() 469 | { 470 | $preDispatch = 0; 471 | $postDispatch = 0; 472 | 473 | $this->object->getEventManager()->subscribe( 474 | "pre.dispatch", function ($router, $app) use (&$preDispatch) { 475 | $preDispatch += 1; 476 | } 477 | ); 478 | 479 | $this->object->getEventManager()->subscribe( 480 | "post.dispatch", function ($app) use (&$postDispatch) { 481 | ++$postDispatch; 482 | } 483 | ); 484 | 485 | ob_start(); 486 | $this->object->run(new Request("/then/first")); 487 | ob_end_clean(); 488 | 489 | $this->assertSame(2, $preDispatch); 490 | $this->assertSame(2, $postDispatch); 491 | } 492 | 493 | public function testDisableLayout() 494 | { 495 | $this->object->bootstrap("view", function(){ 496 | $v = new View(); 497 | $v->addViewPath(__DIR__ . '/views'); 498 | 499 | return $v; 500 | }); 501 | 502 | 503 | $this->object->bootstrap("layout", function(){ 504 | $v = new Layout(); 505 | $v->addViewPath(__DIR__ . '/layouts'); 506 | $v->setScriptName("layout"); 507 | 508 | return $v; 509 | }); 510 | 511 | ob_start(); 512 | $this->object->run(new Request("/general/disable-layout")); 513 | $content = ob_get_contents(); 514 | ob_end_clean(); 515 | 516 | $this->assertEquals("Only this view...", trim($content)); 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /tests/BootstrapTest.php: -------------------------------------------------------------------------------- 1 | object = new Bootstrap(); 13 | } 14 | 15 | 16 | /** 17 | * @covers Application::bootstrap 18 | * @covers Application::getBootstrap 19 | */ 20 | public function testBootstrap() 21 | { 22 | $this->object->addResource("hello", function(){return "ciao";}); 23 | $boot = $this->object->getResource("hello"); 24 | 25 | $this->assertEquals($boot, "ciao"); 26 | } 27 | 28 | /** 29 | * Resources must bootstrap onetime 30 | * 31 | * @covers Application::getBootstrap 32 | */ 33 | public function testGetMultipleTimes() 34 | { 35 | $this->object->addResource("hello", function(){ 36 | return new View(); 37 | }); 38 | $boot = $this->object->getResource("hello"); 39 | $boot2 = $this->object->getResource("hello"); 40 | 41 | $this->assertInstanceOf("View", $boot); 42 | 43 | $this->assertSame($boot, $boot2); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /tests/ControllerTest.php: -------------------------------------------------------------------------------- 1 | object = new Controller(); 23 | $dispatcher = new Dispatcher(new View()); 24 | $dispatcher->setBootstrap(new Bootstrap()); 25 | 26 | $this->object->setParams( 27 | array( 28 | 'dispatcher' => $dispatcher 29 | ) 30 | ); 31 | } 32 | 33 | /** 34 | * Tears down the fixture, for example, closes a network connection. 35 | * This method is called after a test is executed. 36 | */ 37 | protected function tearDown() 38 | { 39 | } 40 | 41 | /** 42 | * @covers Controller::setView 43 | * @covers Controller::getView 44 | */ 45 | public function testSetGetView() 46 | { 47 | $view = new View(); 48 | 49 | $this->object->setView($view); 50 | 51 | $v = $this->object->getView(); 52 | 53 | $this->assertSame($view, $v); 54 | } 55 | 56 | /** 57 | * @covers Controller::setParams 58 | * @covers Controller::getParams 59 | */ 60 | public function testSetGetParams() 61 | { 62 | $p = array("ciao"); 63 | $this->object->setParams($p); 64 | 65 | $this->assertSame($p, $this->object->getParams()); 66 | } 67 | 68 | public function testGetParam() 69 | { 70 | $p = array('hello' => "ciao"); 71 | $this->object->setParams($p); 72 | 73 | $this->assertEquals("ciao", $this->object->getParam('hello')); 74 | 75 | $this->assertFalse($this->object->getParam("missing-key")); 76 | } 77 | 78 | public function testAddHeaders() 79 | { 80 | $this->object->addHeader("a", "b"); 81 | $this->object->addHeader("c", "d"); 82 | 83 | $this->assertSame(2, count($this->object->getHeaders())); 84 | 85 | $headers = $this->object->getHeaders(); 86 | 87 | $first = $headers[0]; 88 | $second = $headers[1]; 89 | $this->assertEquals("a:b", $first["string"]); 90 | $this->assertEquals("c:d", $second["string"]); 91 | } 92 | 93 | public function testAddHeaderCode() 94 | { 95 | $this->object->addHeader("a", "b", 500); 96 | $headers = $this->object->getHeaders(); 97 | 98 | $this->assertEquals(500, $headers[0]["code"]); 99 | } 100 | 101 | public function testClearHeaders() 102 | { 103 | $this->object->addHeader("a", "b"); 104 | $this->object->addHeader("c", "d"); 105 | 106 | $this->object->clearHeaders(); 107 | 108 | $this->assertSame(0, count($this->object->getHeaders())); 109 | } 110 | 111 | public function testSetNoRender() 112 | { 113 | $v = new View(); 114 | $v->setViewPath(__DIR__ . '/views'); 115 | $this->object->setView($v); 116 | 117 | $this->object->value = "hello"; 118 | 119 | $this->assertSame($v, $this->object->getView()); 120 | $this->object->setNoRender(); 121 | $this->assertNotSame($v, $this->object->getView()); 122 | } 123 | 124 | public function testRedirectBase() 125 | { 126 | $this->object->addHeader("content-type", "text/html"); 127 | $this->object->redirect("/admin/login"); 128 | $headers = $this->object->getHeaders(); 129 | 130 | $this->assertSame(1, count($headers)); 131 | $redirectHeader = $headers[0]; 132 | 133 | $this->assertSame(301, $redirectHeader["code"]); 134 | } 135 | 136 | public function testRedirectBase302() 137 | { 138 | $this->object->addHeader("content-type", "text/html"); 139 | $this->object->redirect("/admin/login", 302); 140 | $headers = $this->object->getHeaders(); 141 | 142 | $this->assertSame(1, count($headers)); 143 | $redirectHeader = $headers[0]; 144 | 145 | $this->assertSame(302, $redirectHeader["code"]); 146 | } 147 | 148 | public function testGetViewPath() 149 | { 150 | $ctr = new Controller(); 151 | $ctr->setRenderer("a/b"); 152 | $this->assertEquals("a/b.phtml", $ctr->getViewPath()); 153 | } 154 | 155 | public function testEmptyGetViewPath() 156 | { 157 | $ctr = new Controller(); 158 | $ctr->init(); 159 | $this->assertFalse($ctr->getViewPath()); 160 | } 161 | 162 | public function testSetGetRawBody() 163 | { 164 | $this->object->setRawBody("Hello"); 165 | $body = $this->object->getRawBody(); 166 | 167 | $this->assertEquals("Hello", $body); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/DispatcherTest.php: -------------------------------------------------------------------------------- 1 | object = new Dispatcher(new View()); 15 | $this->object->setBootstrap(new Bootstrap()); 16 | $this->object->setEventManager(new EventManager()); 17 | $this->object->setControllerPath(__DIR__ . '/controllers'); 18 | $router = new Router(); 19 | $this->object->setRouter($router); 20 | } 21 | 22 | public function testDispatchARoute() 23 | { 24 | $request = new Request("alone/an"); 25 | $this->object->setRequest($request); 26 | $route = $this->object->getRouter()->match($request); 27 | $content = $this->object->dispatch($route); 28 | 29 | $this->assertEquals("an-action", $content); 30 | } 31 | 32 | /** 33 | * @expectedException RuntimeException 34 | */ 35 | public function testDispatchAnError() 36 | { 37 | $request = new Request("/not/exists-this-action"); 38 | $this->object->setRequest($request); 39 | $route = $this->object->getRouter()->match($request); 40 | $this->object->dispatch($route); 41 | } 42 | 43 | public function testHeaderCodes() 44 | { 45 | $this->object->addHeader("content-type", "text/html", "202"); 46 | 47 | $headers = $this->object->getHeaders(); 48 | $this->assertCount(1, $headers); 49 | $this->assertSame(202, $headers[0]["code"]); 50 | } 51 | 52 | public function testSetGetHeaders() 53 | { 54 | $this->object->addHeader("content-type", "text/html"); 55 | 56 | $headers = $this->object->getHeaders(); 57 | $this->assertCount(1, $headers); 58 | 59 | $this->object->addHeader("content-disposition", "inline;"); 60 | $headers = $this->object->getHeaders(); 61 | $this->assertCount(2, $headers); 62 | 63 | $this->assertStringStartsWith("content-type", $headers[0]["string"]); 64 | $this->assertStringEndsWith("text/html", $headers[0]["string"]); 65 | 66 | $this->assertStringStartsWith("content-disposition", $headers[1]["string"]); 67 | $this->assertStringEndsWith("inline;", $headers[1]["string"]); 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /tests/EventManagerTest.php: -------------------------------------------------------------------------------- 1 | object = new EventManager; 21 | } 22 | 23 | /** 24 | * Tears down the fixture, for example, closes a network connection. 25 | * This method is called after a test is executed. 26 | */ 27 | protected function tearDown() 28 | { 29 | } 30 | 31 | /** 32 | * @covers EventManager::subscribe 33 | */ 34 | public function testSubscribe() 35 | { 36 | $this->object->subscribe('hook', function(){}); 37 | } 38 | 39 | /** 40 | * @expectedException RuntimeException 41 | */ 42 | public function testNotCallable() 43 | { 44 | $this->object->subscribe("hook", "hello-world"); 45 | } 46 | 47 | /** 48 | * @covers EventManager::publish 49 | */ 50 | public function testPublish() 51 | { 52 | $calls = 0; 53 | $this->object->subscribe("hook", function() use (&$calls) { 54 | ++$calls; 55 | }); 56 | 57 | $this->object->publish('hook'); 58 | 59 | $this->assertSame(1, $calls); 60 | } 61 | 62 | /** 63 | * @covers EventManager::publish 64 | */ 65 | public function testCallEmptyListener() 66 | { 67 | $this->object->publish('hook'); 68 | } 69 | 70 | /** 71 | * @covers EventManager::clear 72 | * @todo Implement testClear(). 73 | */ 74 | public function testClear() 75 | { 76 | $calls = 0; 77 | $this->object->subscribe("hook", function() use (&$calls) { 78 | ++$calls; 79 | }); 80 | 81 | $this->object->clear(); 82 | 83 | $this->object->publish("hook"); 84 | 85 | $this->assertSame(0, $calls); 86 | } 87 | 88 | public function testClearSingle() 89 | { 90 | $calls = 0; 91 | $this->object->subscribe("hook", function() use (&$calls) { 92 | ++$calls; 93 | }); 94 | 95 | $malls = 0; 96 | $this->object->subscribe("pook", function() use (&$malls) { 97 | ++$malls; 98 | }); 99 | 100 | $this->object->clear("pook"); 101 | 102 | $this->object->publish("hook"); 103 | $this->object->publish("pook"); 104 | 105 | $this->assertSame(1, $calls); 106 | $this->assertSame(0, $malls); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/LayoutTest.php: -------------------------------------------------------------------------------- 1 | object = new Layout; 23 | $this->object->setViewPath(__DIR__ . '/layouts'); 24 | $this->object->setScriptName("layout.phtml"); 25 | } 26 | 27 | /** 28 | * Tears down the fixture, for example, closes a network connection. 29 | * This method is called after a test is executed. 30 | */ 31 | protected function tearDown() 32 | { 33 | } 34 | 35 | public function testSetGetContent() 36 | { 37 | $this->object->content = 'ciao'; 38 | 39 | $this->assertEquals("ciao", $this->object->content); 40 | } 41 | 42 | public function testBaseLayout() 43 | { 44 | $this->object->content = 'hello-world'; 45 | $this->assertEquals( 46 | "hello-world", 47 | $this->object->render( 48 | $this->object->getScriptName() 49 | ) 50 | ); 51 | } 52 | 53 | public function testAlternateName() 54 | { 55 | $this->object->content = 'hello-world'; 56 | $this->object->setScriptName("alternate.phtml"); 57 | $this->assertEquals( 58 | "

hello-world

", 59 | $this->object->render( 60 | $this->object->getScriptName() 61 | ) 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/LoaderTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(class_exists("Application", true)); 15 | $this->assertTrue(class_exists("Controller", true)); 16 | $this->assertTrue(class_exists("EventManager", true)); 17 | $this->assertTrue(class_exists("Layout", true)); 18 | $this->assertTrue(class_exists("Route", true)); 19 | $this->assertTrue(class_exists("View", true)); 20 | } 21 | 22 | public function testRegisterAutoloader() 23 | { 24 | $this->markTestSkipped(); 25 | set_include_path( 26 | implode( 27 | PATH_SEPARATOR, 28 | array( 29 | __DIR__ . '/classes', 30 | get_include_path() 31 | ) 32 | ) 33 | ); 34 | Loader::register(); 35 | 36 | $this->assertTrue(class_exists("ns\Clazz", true)); 37 | $this->assertTrue(class_exists("pr_Clazz", true)); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/RequestTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped("Need Selenium or PhantomJS..."); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/RouteTest.php: -------------------------------------------------------------------------------- 1 | object = new Route; 22 | } 23 | 24 | /** 25 | * Tears down the fixture, for example, closes a network connection. 26 | * This method is called after a test is executed. 27 | */ 28 | protected function tearDown() 29 | { 30 | } 31 | 32 | /** 33 | * @expectedException RuntimeException 34 | */ 35 | public function testStrangeExplode() 36 | { 37 | $this->markTestSkipped("Check if is router test"); 38 | $this->object->explode(array('controller' => 'ciao', 'action' => 'hello')); 39 | } 40 | 41 | public function testAddParam() 42 | { 43 | $this->markTestSkipped("Check if is request test"); 44 | $this->object->explode("/index/index"); 45 | $this->object->addParams(array("hello" => "ciao")); 46 | $this->object->addParams(array("bella" => 'zi')); 47 | 48 | $p = $this->object->getParams(); 49 | $this->assertEquals(2, count($p)); 50 | $this->assertEquals("ciao", $p["hello"]); 51 | $this->assertEquals("zi", $p["bella"]); 52 | } 53 | 54 | public function testClearGetParams() 55 | { 56 | $this->markTestSkipped("Check if is router test"); 57 | $uri = '/?hello=world'; 58 | $this->object->explode($uri); 59 | $this->assertEquals("Index", $this->object->getControllerName()); 60 | $this->assertEquals("index", $this->object->getActionName()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/RouterTest.php: -------------------------------------------------------------------------------- 1 | _object = new Router(); 16 | } 17 | 18 | public function testDefaultRouting() 19 | { 20 | $request = new Request(); 21 | $request->setUri("/controller/action"); 22 | $route = $this->_object->match($request); 23 | 24 | $this->assertEquals("Controller", $route->getControllerName()); 25 | $this->assertEquals("action", $route->getActionName()); 26 | } 27 | 28 | public function testDefaultRoutingComplexNaming() 29 | { 30 | $request = new Request(); 31 | $request->setUri("/controller-name/action-name-hello"); 32 | $route = $this->_object->match($request); 33 | 34 | $this->assertEquals("ControllerName", $route->getControllerName()); 35 | $this->assertEquals("actionNameHello", $route->getActionName()); 36 | } 37 | 38 | public function testChain() 39 | { 40 | $request = new Request(); 41 | $request->setHostname("t.test.local"); 42 | $request->setUri("/"); 43 | 44 | $router = new Router(); 45 | $hostnameRouter = new HostnameRouter("t.test.local"); 46 | $hostnameRouter->addChild("hello", new StaticRouter("/", "Hello", "world")); 47 | 48 | $router->addChild("hostname", $hostnameRouter); 49 | 50 | $route = $router->match($request); 51 | 52 | $this->assertEquals("Hello", $route->getControllerName()); 53 | } 54 | 55 | public function testSlashExplode() 56 | { 57 | $routeObj = $this->_object->match(new Request("/")); 58 | 59 | $route = $routeObj->getRoute(); 60 | $params = $routeObj->getParams(); 61 | 62 | $this->assertEquals("Index", $route["controller"]); 63 | $this->assertEquals("index", $route["action"]); 64 | 65 | $this->assertInternalType("array", $params); 66 | $this->assertSame(0, count($params)); 67 | } 68 | 69 | public function testOnlyControllerExplode() 70 | { 71 | $routeObj = $this->_object->match(new Request("/home")); 72 | 73 | $route = $routeObj->getRoute(); 74 | $params = $routeObj->getParams(); 75 | 76 | $this->assertEquals("Home", $route["controller"]); 77 | $this->assertEquals("index", $route["action"]); 78 | 79 | $this->assertInternalType("array", $params); 80 | $this->assertSame(0, count($params)); 81 | } 82 | 83 | public function testControllerActionExplode() 84 | { 85 | $routeObj = $this->_object->match(new Request("/admin/home")); 86 | 87 | $route = $routeObj->getRoute(); 88 | $params = $routeObj->getParams(); 89 | 90 | $this->assertEquals("Admin", $route["controller"]); 91 | $this->assertEquals("home", $route["action"]); 92 | 93 | $this->assertInternalType("array", $params); 94 | $this->assertSame(0, count($params)); 95 | } 96 | 97 | public function testParamsControllerActionExplode() 98 | { 99 | $r = new Request("/walk/on/area/bar/status/inlove"); 100 | $routeObj = $this->_object->match($r); 101 | 102 | $route = $routeObj->getRoute(); 103 | $params = $r->getParams(); 104 | 105 | $this->assertEquals("Walk", $route["controller"]); 106 | $this->assertEquals("on", $route["action"]); 107 | 108 | $this->assertInternalType("array", $params); 109 | $this->assertSame(2, count($params)); 110 | 111 | $keys = array_keys($params); 112 | $this->assertEquals("area", $keys[0]); 113 | $this->assertEquals("status", $keys[1]); 114 | 115 | $this->assertEquals("bar", $params["area"]); 116 | $this->assertEquals("inlove", $params["status"]); 117 | } 118 | 119 | public function testUnbalancedParamsExplode() 120 | { 121 | $r = new Request("/walk/on/area/bar/status"); 122 | $routeObj = $this->_object->match($r); 123 | 124 | $route = $routeObj->getRoute(); 125 | $params = $r->getParams(); 126 | 127 | $this->assertEquals("Walk", $route["controller"]); 128 | $this->assertEquals("on", $route["action"]); 129 | 130 | $this->assertInternalType("array", $params); 131 | $this->assertSame(1, count($params)); 132 | 133 | $keys = array_keys($params); 134 | $this->assertEquals("area", $keys[0]); 135 | 136 | $this->assertEquals("bar", $params["area"]); 137 | } 138 | 139 | public function testComplexControllerName() 140 | { 141 | $r = new Request("/walk-on-files/hello-sunny-day/param/ok-this"); 142 | $routeObj = $this->_object->match($r); 143 | 144 | $route = $routeObj->getRoute(); 145 | $params = $r->getParams(); 146 | 147 | $this->assertEquals("WalkOnFiles", $route["controller"]); 148 | $this->assertEquals("helloSunnyDay", $route["action"]); 149 | 150 | $params = $r->getParams(); 151 | $this->assertEquals("ok-this", $params["param"]); 152 | } 153 | 154 | public function testClearLongGetParams() 155 | { 156 | $uri = '/?hello=world&titti=totti'; 157 | $route = $this->_object->match(new Request($uri)); 158 | $this->assertEquals("Index", $route->getControllerName()); 159 | $this->assertEquals("index", $route->getActionName()); 160 | } 161 | 162 | public function testClearGet2Params() 163 | { 164 | $uri = '/account?hello=world'; 165 | $route = $this->_object->match(new Request($uri)); 166 | $this->assertEquals("Account", $route->getControllerName()); 167 | $this->assertEquals("index", $route->getActionName()); 168 | } 169 | 170 | public function testClearGet3Params() 171 | { 172 | $uri = '/admin/account-super?hello=world'; 173 | $route = $this->_object->match(new Request($uri)); 174 | $this->assertEquals("Admin", $route->getControllerName()); 175 | $this->assertEquals("accountSuper", $route->getActionName()); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tests/ViewTest.php: -------------------------------------------------------------------------------- 1 | object = new View; 21 | } 22 | 23 | /** 24 | * Tears down the fixture, for example, closes a network connection. 25 | * This method is called after a test is executed. 26 | */ 27 | protected function tearDown() 28 | { 29 | } 30 | 31 | /** 32 | * @covers View::__set 33 | * @covers View::__get 34 | */ 35 | public function testSetAndGet() 36 | { 37 | $this->object->string = "ok"; 38 | $this->assertSame("ok", $this->object->string); 39 | 40 | $this->object->integer = 100; 41 | $this->assertSame(100, $this->object->integer); 42 | 43 | $arr = array(); 44 | $arr["ciao"] = "hello"; 45 | $this->object->arr = $arr; 46 | 47 | $this->assertInternalType("array", $this->object->arr); 48 | $this->assertEquals(1, count($this->object->arr)); 49 | $this->assertEquals("hello", $this->object->arr["ciao"]); 50 | 51 | $this->assertSame($arr, $this->object->arr); 52 | 53 | $obj = new stdClass(); 54 | $obj->value = "ciao"; 55 | 56 | $this->object->obj = $obj; 57 | 58 | $this->assertInstanceOf("stdClass", $this->object->obj); 59 | $this->assertSame($obj, $this->object->obj); 60 | } 61 | 62 | /** 63 | * @covers View::setViewPath 64 | */ 65 | public function testSetViewPath() 66 | { 67 | // Remove the following lines when you implement this test. 68 | $this->object->setViewPath(dirname(__FILE__)); 69 | 70 | $this->assertEquals(dirname(__FILE__), $this->object->getViewPath()); 71 | } 72 | 73 | /** 74 | * @expectedException RuntimeException 75 | */ 76 | public function testSetInvalidViewPath() 77 | { 78 | $this->object->setViewPath(dirname(__FILE__) . '/ViewTest.php'); 79 | } 80 | 81 | /** 82 | * @expectedException RuntimeException 83 | */ 84 | public function testSetMissingViewPath() 85 | { 86 | $this->object->setViewPath(dirname(__FILE__) . '/hidden-views'); 87 | } 88 | 89 | /** 90 | * @covers View::render 91 | */ 92 | public function testRender() 93 | { 94 | $this->object->setViewPath(dirname(__FILE__) . '/views'); 95 | $this->object->value = "ciao-mondo"; 96 | $text = $this->object->render("view-test.phtml"); 97 | 98 | $this->assertEquals("

ciao-mondo

", $text); 99 | } 100 | 101 | public function testMixData() 102 | { 103 | $this->object->setViewPath(dirname(__FILE__) . '/views'); 104 | $this->object->value = "hello"; 105 | $exec = $this->object->render("view-mix-test.phtml", array("value2" => 'hello')); 106 | 107 | $this->assertEquals("

hello

hello

", $exec); 108 | } 109 | 110 | /** 111 | * @expectedException RuntimeException 112 | */ 113 | public function testMixNonArrayData() 114 | { 115 | $this->object->setViewPath(dirname(__FILE__) . '/views'); 116 | $this->object->value = "hello"; 117 | $exec = $this->object->render("view-test.phtml", 'hello'); 118 | } 119 | 120 | /** 121 | * @expectedException RuntimeException 122 | */ 123 | public function testMissingTemplate() 124 | { 125 | $exec = $this->object->render("missing-view-test.phtml"); 126 | } 127 | 128 | public function testEmptyGet() 129 | { 130 | $false = $this->object->missingKey; 131 | 132 | $this->assertFalse($false); 133 | } 134 | 135 | public function testGetViewPath() 136 | { 137 | $this->object->setViewPath(dirname(__FILE__)); 138 | 139 | $path = $this->object->getViewPath(); 140 | 141 | $this->assertEquals(dirname(__FILE__), $path); 142 | } 143 | 144 | public function testViewParamEscape() 145 | { 146 | $ret = $this->object->escape("

ciao

"); 147 | $this->assertEquals("<p>ciao</p>", $ret); 148 | } 149 | 150 | public function testViewChars() 151 | { 152 | $ret = $this->object->p = "èàòùìç@"; 153 | $this->assertEquals("èàòùìç@", $this->object->p); 154 | $this->assertEquals("èàòùìç@", $this->object->escape("èàòùìç@")); 155 | $this->assertEquals(""", $this->object->escape("\"", ENT_QUOTES)); 156 | $this->assertEquals("\"'", $this->object->escape("\"'", ENT_NOQUOTES)); 157 | } 158 | 159 | public function testViewHelpers() 160 | { 161 | $this->object->addHelper("example", function(){ 162 | return "walter"; 163 | }); 164 | 165 | $this->assertEquals("walter", $this->object->example()); 166 | } 167 | 168 | public function testViewHelpersOneParam() 169 | { 170 | $this->object->addHelper("example", function($param){ 171 | return "walter-{$param}"; 172 | }); 173 | 174 | $this->assertEquals("walter-stringa", $this->object->example("stringa")); 175 | } 176 | 177 | public function testViewHelpersMultipleParams() 178 | { 179 | $this->object->addHelper("example", function($param, $app, $def){ 180 | return "walter-{$param}-{$def}-{$app}"; 181 | }); 182 | 183 | $this->assertEquals("walter-vg-#!-_9", $this->object->example("vg", "_9", "#!")); 184 | } 185 | 186 | public function testCloneView() 187 | { 188 | $v = new View(); 189 | $v->setViewPath(__DIR__ . "/views"); 190 | $v2 = $v->cloneThis(); 191 | 192 | $this->assertNotSame($v, $v2); 193 | $v2->value = "hello"; 194 | $this->assertEquals("

hello

", $v2->render("view-test.phtml")); 195 | } 196 | 197 | /** 198 | * @expectedException RuntimeException 199 | */ 200 | public function testMissingHelperView() 201 | { 202 | $this->object->now(); 203 | } 204 | 205 | public function testPartialViewHelper() 206 | { 207 | $v = new View(); 208 | $v->setViewPath(__DIR__ . '/views'); 209 | 210 | $html = $v->render("base.phtml"); 211 | $this->assertEquals("

mondo

", $html); 212 | } 213 | 214 | public function testRewritePoint() 215 | { 216 | $v = new View(); 217 | $v->addViewPath(__DIR__ . '/views'); 218 | $v->addViewPath(__DIR__ . '/views-rewrite'); 219 | 220 | $html = $v->render("base.phtml"); 221 | $this->assertEquals("
REWRITED!
", $html); 222 | } 223 | 224 | /** 225 | * @expectedException RuntimeException 226 | */ 227 | public function testWrongViewPath() 228 | { 229 | $this->object->addViewPath(__DIR__ . '/missing-point'); 230 | } 231 | 232 | public function testMissingRewrite() 233 | { 234 | $v = new View(); 235 | $v->addViewPath(__DIR__ . '/views'); 236 | $v->addViewPath(__DIR__ . '/views-rewrite'); 237 | 238 | $html = $v->render("only-base.phtml"); 239 | $this->assertEquals("
Only base
", $html); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | "; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/controllers/AloneController.php: -------------------------------------------------------------------------------- 1 | error action <--"; 7 | $this->setNoRender(); 8 | } 9 | } -------------------------------------------------------------------------------- /tests/controllers/GeneralController.php: -------------------------------------------------------------------------------- 1 | disableLayout(); 11 | } 12 | 13 | public function pullAction() 14 | { 15 | return array('title' => 'ok'); 16 | } 17 | 18 | public function directAction() 19 | { 20 | 21 | } 22 | 23 | public function pullDataAction() 24 | { 25 | $clazz = new stdClass(); 26 | 27 | $clazz->title = 'Controller Data'; 28 | 29 | return $clazz; 30 | } 31 | 32 | public function aAction() 33 | { 34 | $this->view->b = "B"; 35 | $this->setRenderer("/general/b"); 36 | } 37 | 38 | public function cAction() 39 | { 40 | $this->view->c = "C"; 41 | } 42 | 43 | public function outAction() 44 | { 45 | echo "opssssss!"; 46 | return "ret from out"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/controllers/InitController.php: -------------------------------------------------------------------------------- 1 | "; 7 | } 8 | 9 | public function indexAction() 10 | { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /tests/controllers/ThenController.php: -------------------------------------------------------------------------------- 1 | "; 7 | $this->then("/then/second"); 8 | } 9 | 10 | public function secondAction() 11 | { 12 | echo "<-second"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/layouts/alternate.phtml: -------------------------------------------------------------------------------- 1 |

content?>

-------------------------------------------------------------------------------- /tests/layouts/layout.phtml: -------------------------------------------------------------------------------- 1 | content?> -------------------------------------------------------------------------------- /tests/layouts/title-helper.phtml: -------------------------------------------------------------------------------- 1 | <?php echo $this->title()?> -------------------------------------------------------------------------------- /tests/resources/default.request.xml: -------------------------------------------------------------------------------- 1 | 2 | / 3 | t.test.local 4 | 5 | value 6 | 7 | 8 | value 9 | 10 | 11 | value 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/views-rewrite/base.phtml: -------------------------------------------------------------------------------- 1 |
REWRITED!
-------------------------------------------------------------------------------- /tests/views-rewrite/general/c.phtml: -------------------------------------------------------------------------------- 1 | This is c?> but rewrited -------------------------------------------------------------------------------- /tests/views-rewrite/general/d.phtml: -------------------------------------------------------------------------------- 1 | This is D but rewrited -------------------------------------------------------------------------------- /tests/views-rewrite/general/partial-eg.phtml: -------------------------------------------------------------------------------- 1 | partial("title.phtml", array("ciao" => 'ciao')); -------------------------------------------------------------------------------- /tests/views-rewrite/title.phtml: -------------------------------------------------------------------------------- 1 |

ciao?>

-------------------------------------------------------------------------------- /tests/views/admin/login.phtml: -------------------------------------------------------------------------------- 1 | The login -------------------------------------------------------------------------------- /tests/views/base.phtml: -------------------------------------------------------------------------------- 1 |
partial("partial.phtml", array("ciao" => 'mondo'));?>
-------------------------------------------------------------------------------- /tests/views/general/b.phtml: -------------------------------------------------------------------------------- 1 | This is b?> -------------------------------------------------------------------------------- /tests/views/general/c.phtml: -------------------------------------------------------------------------------- 1 | Original C view -------------------------------------------------------------------------------- /tests/views/general/d.phtml: -------------------------------------------------------------------------------- 1 | This is D -------------------------------------------------------------------------------- /tests/views/general/direct.phtml: -------------------------------------------------------------------------------- 1 |

pull("/general/pull");?>

-------------------------------------------------------------------------------- /tests/views/general/disable-layout.phtml: -------------------------------------------------------------------------------- 1 | Only this view... 2 | -------------------------------------------------------------------------------- /tests/views/general/partial-eg.phtml: -------------------------------------------------------------------------------- 1 | partial("title.phtml", array("ciao" => 'ciao')); -------------------------------------------------------------------------------- /tests/views/general/pull-driven.phtml: -------------------------------------------------------------------------------- 1 |

Pull-driven experience

-------------------------------------------------------------------------------- /tests/views/general/title-helper.phtml: -------------------------------------------------------------------------------- 1 | title("the title helper"); 3 | $this->title("second"); -------------------------------------------------------------------------------- /tests/views/only-base.phtml: -------------------------------------------------------------------------------- 1 |
Only base
-------------------------------------------------------------------------------- /tests/views/partial.phtml: -------------------------------------------------------------------------------- 1 |

ciao; ?>

-------------------------------------------------------------------------------- /tests/views/pull/buffer-out.phtml: -------------------------------------------------------------------------------- 1 |

pull("/general/out");?>

-------------------------------------------------------------------------------- /tests/views/pull/driven-data.phtml: -------------------------------------------------------------------------------- 1 |

pull("/general/pull-data")->title;?>

-------------------------------------------------------------------------------- /tests/views/pull/driven.phtml: -------------------------------------------------------------------------------- 1 |

Complete pull driven

-------------------------------------------------------------------------------- /tests/views/pull/missing-pull-action.phtml: -------------------------------------------------------------------------------- 1 | pull("/missing/action");?> -------------------------------------------------------------------------------- /tests/views/pull/missing-pull.phtml: -------------------------------------------------------------------------------- 1 | pull("/general/missing-action");?> -------------------------------------------------------------------------------- /tests/views/title.phtml: -------------------------------------------------------------------------------- 1 |

ciao?>

-------------------------------------------------------------------------------- /tests/views/view-mix-test.phtml: -------------------------------------------------------------------------------- 1 |

value?>

value2?>

-------------------------------------------------------------------------------- /tests/views/view-test.phtml: -------------------------------------------------------------------------------- 1 |

value?>

--------------------------------------------------------------------------------