├── .gitignore ├── .htaccess ├── .travis.yml ├── README.md ├── box.json ├── build ├── .gitignore └── bottle.phar ├── docs ├── Makefile ├── _exts │ └── sensio │ │ ├── __init__.py │ │ └── sphinx │ │ ├── __init__.py │ │ ├── bestpractice.py │ │ ├── configurationblock.py │ │ ├── php.py │ │ ├── phpcode.py │ │ └── refinclude.py ├── conf.py ├── contributing.rst ├── index.rst ├── locale │ └── fr │ │ └── LC_MESSAGES │ │ ├── contributing.mo │ │ ├── contributing.po │ │ ├── index.mo │ │ ├── index.po │ │ ├── quickstart.mo │ │ └── quickstart.po ├── make.bat └── quickstart.rst ├── index.php ├── phpunit.xml.dist ├── pip-requirements.txt ├── src ├── bottle.php └── bottle │ ├── core.php │ ├── exception.php │ ├── forbidden │ └── exception.php │ ├── notfound │ └── exception.php │ ├── request.php │ ├── response.php │ ├── route.php │ └── view.php ├── tests ├── bootstrap.php ├── classes │ ├── bottleTest.php │ ├── bottleTestCase.php │ ├── curlTest.php │ └── viewTest.php ├── fixtures │ ├── .htaccess │ ├── curl.php │ ├── index.php │ ├── subdir_with_éééé │ │ ├── .htaccess │ │ └── index.php │ └── views │ │ ├── global-context.html │ │ ├── simpleview.html │ │ ├── varview1.html │ │ ├── varview2.html │ │ ├── varview3.html │ │ ├── varview4.html │ │ └── view.html └── utils.php └── views ├── .htaccess ├── mul.php └── restricted.php /.gitignore: -------------------------------------------------------------------------------- 1 | docs/_build 2 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteRule ^(.*)$ index.php [QSA,L] -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.4 4 | - 5.5 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Bottle Web Framework 2 | 3 | [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) 4 | 5 | [![Build Status](https://secure.travis-ci.org/nergal/php-bottle.png)](https://secure.travis-ci.org/nergal/php-bottle/) 6 | [![Documentation Status](https://readthedocs.org/projects/php-bottle/badge/?version=latest)](https://readthedocs.org/projects/php-bottle/?badge=latest) 7 | 8 | PHP Bottle - PHP micro-framework inspired by minimalistic PyBottle 9 | 10 | 11 | 12 | ## Exampe 13 | require_once "bottle.php"; 14 | 15 | /** 16 | * @route /hello/:name 17 | */ 18 | function hello($name) { 19 | return "

Hello, {$name}!

"; 20 | } 21 | 22 | /** 23 | * @route /mul/:num 24 | * @view /views/mul.html 25 | */ 26 | function mul($num) { 27 | return array('result' => $num * $num); 28 | } 29 | 30 | 31 | ## Licence (MIT) 32 | Copyright (c) 2010, Marcel Hellkamp. 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in 42 | all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 50 | THE SOFTWARE. 51 | 52 | ## Additional information for users from Russia and Belarus 53 | * Russia has [illegally annexed Crimea in 2014](https://en.wikipedia.org/wiki/Annexation_of_Crimea_by_the_Russian_Federation) and [brought the war in Donbas](https://en.wikipedia.org/wiki/War_in_Donbas) followed by [full-scale invasion of Ukraine in 2022](https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine). 54 | * Russia has brought sorrow and devastations to millions of Ukrainians, killed hundreds of innocent people, damaged thousands of buildings, and forced several million people to flee. 55 | * [Putin khuylo!](https://en.wikipedia.org/wiki/Putin_khuylo!) 56 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": "src", 3 | "main": "src/bottle.php", 4 | "output": "build/bottle.phar", 5 | "shebang": false, 6 | "stub": true 7 | } 8 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /build/bottle.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nergal/php-bottle/5554efc597d3b07d30cde828d6a7e1d71aae43bd/build/bottle.phar -------------------------------------------------------------------------------- /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 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PHP-Bottle.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PHP-Bottle.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PHP-Bottle" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PHP-Bottle" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_exts/sensio/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nergal/php-bottle/5554efc597d3b07d30cde828d6a7e1d71aae43bd/docs/_exts/sensio/__init__.py -------------------------------------------------------------------------------- /docs/_exts/sensio/sphinx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nergal/php-bottle/5554efc597d3b07d30cde828d6a7e1d71aae43bd/docs/_exts/sensio/sphinx/__init__.py -------------------------------------------------------------------------------- /docs/_exts/sensio/sphinx/bestpractice.py: -------------------------------------------------------------------------------- 1 | from docutils.parsers.rst import Directive, directives 2 | from docutils import nodes 3 | from string import upper 4 | from sphinx.util.compat import make_admonition 5 | from sphinx import addnodes 6 | from sphinx.locale import _ 7 | 8 | class bestpractice(nodes.Admonition, nodes.Element): 9 | pass 10 | 11 | class BestPractice(Directive): 12 | has_content = True 13 | required_arguments = 0 14 | optional_arguments = 1 15 | final_argument_whitespace = True 16 | option_spec = {} 17 | 18 | def run(self): 19 | ret = make_admonition( 20 | bestpractice, self.name, [_('Best Practice')], self.options, 21 | self.content, self.lineno, self.content_offset, self.block_text, 22 | self.state, self.state_machine) 23 | if self.arguments: 24 | argnodes, msgs = self.state.inline_text(self.arguments[0], 25 | self.lineno) 26 | para = nodes.paragraph() 27 | para += argnodes 28 | para += msgs 29 | ret[0].insert(1, para) 30 | 31 | return ret 32 | 33 | def visit_bestpractice_node(self, node): 34 | self.body.append(self.starttag(node, 'div', CLASS=('admonition best-practice'))) 35 | self.set_first_last(node) 36 | 37 | def depart_bestpractice_node(self, node): 38 | self.depart_admonition(node) 39 | 40 | def setup(app): 41 | app.add_node(bestpractice, html=(visit_bestpractice_node, depart_bestpractice_node)) 42 | app.add_directive('best-practice', BestPractice) 43 | -------------------------------------------------------------------------------- /docs/_exts/sensio/sphinx/configurationblock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :copyright: (c) 2010-2012 Fabien Potencier 4 | :license: MIT, see LICENSE for more details. 5 | """ 6 | 7 | from docutils.parsers.rst import Directive, directives 8 | from docutils import nodes 9 | 10 | class configurationblock(nodes.General, nodes.Element): 11 | pass 12 | 13 | class ConfigurationBlock(Directive): 14 | has_content = True 15 | required_arguments = 0 16 | optional_arguments = 0 17 | final_argument_whitespace = True 18 | option_spec = {} 19 | formats = { 20 | 'html': 'HTML', 21 | 'xml': 'XML', 22 | 'php': 'PHP', 23 | 'yaml': 'YAML', 24 | 'jinja': 'Twig', 25 | 'html+jinja': 'Twig', 26 | 'jinja+html': 'Twig', 27 | 'php+html': 'PHP', 28 | 'html+php': 'PHP', 29 | 'ini': 'INI', 30 | 'php-annotations': 'Annotations', 31 | 'php-standalone': 'Standalone Use', 32 | 'php-symfony': 'Framework Use', 33 | } 34 | 35 | def __init__(self, *args): 36 | Directive.__init__(self, *args) 37 | env = self.state.document.settings.env 38 | config_block = env.app.config.config_block 39 | 40 | for language in config_block: 41 | self.formats[language] = config_block[language] 42 | 43 | def run(self): 44 | env = self.state.document.settings.env 45 | 46 | node = nodes.Element() 47 | node.document = self.state.document 48 | self.state.nested_parse(self.content, self.content_offset, node) 49 | 50 | entries = [] 51 | for i, child in enumerate(node): 52 | if isinstance(child, nodes.literal_block): 53 | # add a title (the language name) before each block 54 | #targetid = "configuration-block-%d" % env.new_serialno('configuration-block') 55 | #targetnode = nodes.target('', '', ids=[targetid]) 56 | #targetnode.append(child) 57 | if 'language' in child: 58 | language = child['language'] 59 | else: 60 | language = env.app.config.highlight_language 61 | 62 | innernode = nodes.emphasis(self.formats[language], self.formats[language]) 63 | 64 | para = nodes.paragraph() 65 | para += [innernode, child] 66 | 67 | entry = nodes.list_item('') 68 | entry.append(para) 69 | entries.append(entry) 70 | 71 | resultnode = configurationblock() 72 | resultnode.append(nodes.bullet_list('', *entries)) 73 | 74 | return [resultnode] 75 | 76 | def visit_configurationblock_html(self, node): 77 | self.body.append(self.starttag(node, 'div', CLASS='configuration-block')) 78 | 79 | def depart_configurationblock_html(self, node): 80 | self.body.append('\n') 81 | 82 | def visit_configurationblock_latex(self, node): 83 | pass 84 | 85 | def depart_configurationblock_latex(self, node): 86 | pass 87 | 88 | def setup(app): 89 | app.add_node(configurationblock, 90 | html=(visit_configurationblock_html, depart_configurationblock_html), 91 | latex=(visit_configurationblock_latex, depart_configurationblock_latex)) 92 | app.add_directive('configuration-block', ConfigurationBlock) 93 | app.add_config_value('config_block', {}, 'env') 94 | -------------------------------------------------------------------------------- /docs/_exts/sensio/sphinx/php.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :copyright: (c) 2010-2012 Fabien Potencier 4 | :license: MIT, see LICENSE for more details. 5 | """ 6 | 7 | from sphinx import addnodes 8 | from sphinx.domains import Domain, ObjType 9 | from sphinx.locale import l_, _ 10 | from sphinx.directives import ObjectDescription 11 | from sphinx.domains.python import py_paramlist_re as js_paramlist_re 12 | from sphinx.roles import XRefRole 13 | from sphinx.util.nodes import make_refnode 14 | from sphinx.util.docfields import Field, GroupedField, TypedField 15 | 16 | def setup(app): 17 | app.add_domain(PHPDomain) 18 | 19 | class PHPXRefRole(XRefRole): 20 | def process_link(self, env, refnode, has_explicit_title, title, target): 21 | # basically what sphinx.domains.python.PyXRefRole does 22 | refnode['php:object'] = env.temp_data.get('php:object') 23 | if not has_explicit_title: 24 | title = title.lstrip('\\') 25 | target = target.lstrip('~') 26 | if title[0:1] == '~': 27 | title = title[1:] 28 | ns = title.rfind('\\') 29 | if ns != -1: 30 | title = title[ns+1:] 31 | if target[0:1] == '\\': 32 | target = target[1:] 33 | refnode['refspecific'] = True 34 | return title, target 35 | 36 | class PHPDomain(Domain): 37 | """PHP language domain.""" 38 | name = 'php' 39 | label = 'PHP' 40 | # if you add a new object type make sure to edit JSObject.get_index_string 41 | object_types = { 42 | } 43 | directives = { 44 | } 45 | roles = { 46 | 'func': PHPXRefRole(fix_parens=True), 47 | 'class': PHPXRefRole(), 48 | 'data': PHPXRefRole(), 49 | 'attr': PHPXRefRole(), 50 | } 51 | initial_data = { 52 | 'objects': {}, # fullname -> docname, objtype 53 | } 54 | 55 | def clear_doc(self, docname): 56 | for fullname, (fn, _) in self.data['objects'].items(): 57 | if fn == docname: 58 | del self.data['objects'][fullname] 59 | 60 | def find_obj(self, env, obj, name, typ, searchorder=0): 61 | if name[-2:] == '()': 62 | name = name[:-2] 63 | objects = self.data['objects'] 64 | newname = None 65 | if searchorder == 1: 66 | if obj and obj + '\\' + name in objects: 67 | newname = obj + '\\' + name 68 | else: 69 | newname = name 70 | else: 71 | if name in objects: 72 | newname = name 73 | elif obj and obj + '\\' + name in objects: 74 | newname = obj + '\\' + name 75 | return newname, objects.get(newname) 76 | 77 | def resolve_xref(self, env, fromdocname, builder, typ, target, node, 78 | contnode): 79 | objectname = node.get('php:object') 80 | searchorder = node.hasattr('refspecific') and 1 or 0 81 | name, obj = self.find_obj(env, objectname, target, typ, searchorder) 82 | if not obj: 83 | return None 84 | return make_refnode(builder, fromdocname, obj[0], name, contnode, name) 85 | 86 | def get_objects(self): 87 | for refname, (docname, type) in self.data['objects'].iteritems(): 88 | yield refname, refname, type, docname, refname, 1 89 | -------------------------------------------------------------------------------- /docs/_exts/sensio/sphinx/phpcode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :copyright: (c) 2010-2012 Fabien Potencier 4 | :license: MIT, see LICENSE for more details. 5 | """ 6 | 7 | import re 8 | 9 | from docutils import nodes, utils 10 | 11 | from sphinx.util.nodes import split_explicit_title 12 | 13 | def php_namespace_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 14 | text = utils.unescape(text) 15 | env = inliner.document.settings.env 16 | has_explicit_title, title, namespace = split_explicit_title(text) 17 | 18 | if len(re.findall(r'[^\\]\\[^\\]', rawtext)) > 0: 19 | env.warn(env.docname, 'backslash not escaped in %s' % rawtext, lineno) 20 | 21 | full_url = build_url('namespace', namespace, None, None, inliner) 22 | 23 | if not has_explicit_title: 24 | name = namespace.lstrip('\\') 25 | ns = name.rfind('\\') 26 | if ns != -1: 27 | name = name[ns+1:] 28 | title = name 29 | list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=namespace)] 30 | pnode = nodes.literal('', '', *list) 31 | return [pnode], [] 32 | 33 | def php_class_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 34 | text = utils.unescape(text) 35 | env = inliner.document.settings.env 36 | has_explicit_title, title, full_class = split_explicit_title(text) 37 | backslash = full_class.rfind('\\') 38 | namespace = full_class[:backslash] 39 | class_name = full_class[backslash+1:] 40 | 41 | if len(re.findall(r'[^\\]\\[^\\]', rawtext)) > 0: 42 | env.warn(env.docname, 'backslash not escaped in %s' % rawtext, lineno) 43 | 44 | full_url = build_url('class', namespace, class_name, None, inliner) 45 | 46 | if not has_explicit_title: 47 | class_name = full_class.lstrip('\\') 48 | ns = class_name.rfind('\\') 49 | if ns != -1: 50 | class_name = class_name[ns+1:] 51 | title = class_name 52 | list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class)] 53 | pnode = nodes.literal('', '', *list) 54 | return [pnode], [] 55 | 56 | def php_method_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 57 | text = utils.unescape(text) 58 | env = inliner.document.settings.env 59 | has_explicit_title, title, class_and_method = split_explicit_title(text) 60 | 61 | ns = class_and_method.rfind('::') 62 | full_class = class_and_method[:ns] 63 | method = class_and_method[ns+2:] 64 | backslash = full_class.rfind('\\') 65 | namespace = full_class[:backslash] 66 | class_name = full_class[backslash+1:] 67 | 68 | if len(re.findall(r'[^\\]\\[^\\]', rawtext)) > 0: 69 | env.warn(env.docname, 'backslash not escaped in %s' % rawtext, lineno) 70 | 71 | full_url = build_url('method', namespace, class_name, method, inliner) 72 | 73 | if not has_explicit_title: 74 | title = method + '()' 75 | list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class + '::' + method + '()')] 76 | pnode = nodes.literal('', '', *list) 77 | return [pnode], [] 78 | 79 | def php_phpclass_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 80 | text = utils.unescape(text) 81 | has_explicit_title, title, full_class = split_explicit_title(text) 82 | 83 | full_url = 'http://php.net/manual/en/class.%s.php' % full_class.lower() 84 | 85 | if not has_explicit_title: 86 | title = full_class 87 | list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class)] 88 | pnode = nodes.literal('', '', *list) 89 | return [pnode], [] 90 | 91 | def php_phpmethod_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 92 | text = utils.unescape(text) 93 | has_explicit_title, title, class_and_method = split_explicit_title(text) 94 | 95 | ns = class_and_method.rfind('::') 96 | full_class = class_and_method[:ns] 97 | method = class_and_method[ns+2:] 98 | 99 | full_url = 'http://php.net/manual/en/%s.%s.php' % (full_class.lower(), method.lower()) 100 | 101 | if not has_explicit_title: 102 | title = full_class + '::' + method + '()' 103 | list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class)] 104 | pnode = nodes.literal('', '', *list) 105 | return [pnode], [] 106 | 107 | def php_phpfunction_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 108 | text = utils.unescape(text) 109 | has_explicit_title, title, full_function = split_explicit_title(text) 110 | 111 | full_url = 'http://php.net/manual/en/function.%s.php' % full_function.replace('_', '-').lower() 112 | 113 | if not has_explicit_title: 114 | title = full_function 115 | list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_function)] 116 | pnode = nodes.literal('', '', *list) 117 | return [pnode], [] 118 | 119 | def setup(app): 120 | app.add_config_value('api_url', {}, 'env') 121 | app.add_config_value('api_url_pattern', None, 'env') 122 | app.add_config_value('namespace_separator', '/', 'env') 123 | app.add_role('namespace', php_namespace_role) 124 | app.add_role('class', php_class_role) 125 | app.add_role('method', php_method_role) 126 | app.add_role('phpclass', php_phpclass_role) 127 | app.add_role('phpmethod', php_phpmethod_role) 128 | app.add_role('phpfunction', php_phpfunction_role) 129 | 130 | def build_url(role, namespace, class_name, method, inliner): 131 | env = inliner.document.settings.env 132 | 133 | if namespace is None: 134 | namespace = '' 135 | if class_name is None: 136 | class_name = '' 137 | if method is None: 138 | method = '' 139 | 140 | if ('namespace_separator' in env.app.config): 141 | namespace = namespace.replace('\\', env.app.config.namespace_separator) 142 | else: 143 | namespace = namespace.replace('\\', '/') 144 | 145 | if (env.app.config.api_url_pattern is None): 146 | fqcn = '%(namespace)s{class}/%(class)s{/class}{method}/%(class)s{/method}' 147 | api_url_pattern = env.app.config.api_url.replace('%s', fqcn) 148 | api_url_pattern += '.html{method}#method_%(method)s{/method}' 149 | else: 150 | api_url_pattern = str(env.app.config.api_url_pattern) 151 | 152 | api_url_pattern = api_url_pattern.replace('{'+role+'}', '') 153 | api_url_pattern = api_url_pattern.replace('{/'+role+'}', '') 154 | 155 | for unused_role in ('namespace', 'class', 'method'): 156 | api_url_pattern = re.sub(r'{'+unused_role+'}.*?{/'+unused_role+'}', '', api_url_pattern) 157 | 158 | try: 159 | full_url = api_url_pattern % {'namespace': namespace, 'class': class_name, 'method': method} 160 | except (TypeError, ValueError): 161 | env.warn(env.docname, 'unable to expand %s api_url with base ' 162 | 'URL %r, please make sure the base contains \'%%s\' ' 163 | 'exactly once' % (role, api_url_pattern)) 164 | full_url = api_url_pattern + full_class 165 | 166 | return full_url 167 | -------------------------------------------------------------------------------- /docs/_exts/sensio/sphinx/refinclude.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :copyright: (c) 2010-2012 Fabien Potencier 4 | :license: MIT, see LICENSE for more details. 5 | """ 6 | 7 | from docutils.parsers.rst import Directive, directives 8 | from docutils import nodes 9 | 10 | class refinclude(nodes.General, nodes.Element): 11 | pass 12 | 13 | class RefInclude(Directive): 14 | has_content = False 15 | required_arguments = 1 16 | optional_arguments = 0 17 | final_argument_whitespace = False 18 | option_spec = {} 19 | 20 | def run(self): 21 | document = self.state.document 22 | 23 | if not document.settings.file_insertion_enabled: 24 | return [document.reporter.warning('File insertion disabled', 25 | line=self.lineno)] 26 | 27 | env = self.state.document.settings.env 28 | target = self.arguments[0] 29 | 30 | node = refinclude() 31 | node['target'] = target 32 | 33 | return [node] 34 | 35 | def process_refinclude_nodes(app, doctree, docname): 36 | env = app.env 37 | for node in doctree.traverse(refinclude): 38 | docname, labelid, sectname = env.domaindata['std']['labels'].get(node['target'], 39 | ('','','')) 40 | 41 | if not docname: 42 | return [document.reporter.error('Unknown target name: "%s"' % node['target'], 43 | line=self.lineno)] 44 | 45 | resultnode = None 46 | dt = env.get_doctree(docname) 47 | for n in dt.traverse(nodes.section): 48 | if labelid in n['ids']: 49 | node.replace_self([n]) 50 | break 51 | 52 | def setup(app): 53 | app.add_node(refinclude) 54 | app.add_directive('include-ref', RefInclude) 55 | app.connect('doctree-resolved', process_refinclude_nodes) 56 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PHP-Bottle documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Mar 8 22:00:27 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = 'PHP-Bottle' 48 | copyright = '2015, Nergal, Gordon' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = '0.1.0' 56 | # The full version, including alpha/beta/rc tags. 57 | release = '0.1.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all 74 | # documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | # If true, keep warnings as "system message" paragraphs in the built documents. 95 | #keep_warnings = False 96 | 97 | 98 | # -- Options for HTML output ---------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'default' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | #html_theme_path = [] 111 | 112 | # The name for this set of Sphinx documents. If None, it defaults to 113 | # " v documentation". 114 | #html_title = None 115 | 116 | # A shorter title for the navigation bar. Default is the same as html_title. 117 | #html_short_title = None 118 | 119 | # The name of an image file (relative to this directory) to place at the top 120 | # of the sidebar. 121 | #html_logo = None 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | #html_favicon = None 127 | 128 | # Add any paths that contain custom static files (such as style sheets) here, 129 | # relative to this directory. They are copied after the builtin static files, 130 | # so a file named "default.css" will overwrite the builtin "default.css". 131 | html_static_path = ['_static'] 132 | 133 | # Add any extra paths that contain custom files (such as robots.txt or 134 | # .htaccess) here, relative to this directory. These files are copied 135 | # directly to the root of the documentation. 136 | #html_extra_path = [] 137 | 138 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 139 | # using the given strftime format. 140 | #html_last_updated_fmt = '%b %d, %Y' 141 | 142 | # If true, SmartyPants will be used to convert quotes and dashes to 143 | # typographically correct entities. 144 | #html_use_smartypants = True 145 | 146 | # Custom sidebar templates, maps document names to template names. 147 | #html_sidebars = {} 148 | 149 | # Additional templates that should be rendered to pages, maps page names to 150 | # template names. 151 | #html_additional_pages = {} 152 | 153 | # If false, no module index is generated. 154 | #html_domain_indices = True 155 | 156 | # If false, no index is generated. 157 | #html_use_index = True 158 | 159 | # If true, the index is split into individual pages for each letter. 160 | #html_split_index = False 161 | 162 | # If true, links to the reST sources are added to the pages. 163 | #html_show_sourcelink = True 164 | 165 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 166 | #html_show_sphinx = True 167 | 168 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 169 | #html_show_copyright = True 170 | 171 | # If true, an OpenSearch description file will be output, and all pages will 172 | # contain a tag referring to it. The value of this option must be the 173 | # base URL from which the finished HTML is served. 174 | #html_use_opensearch = '' 175 | 176 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 177 | #html_file_suffix = None 178 | 179 | # Output file base name for HTML help builder. 180 | htmlhelp_basename = 'PHP-Bottledoc' 181 | 182 | 183 | # -- Options for LaTeX output --------------------------------------------- 184 | 185 | latex_elements = { 186 | # The paper size ('letterpaper' or 'a4paper'). 187 | #'papersize': 'letterpaper', 188 | 189 | # The font size ('10pt', '11pt' or '12pt'). 190 | #'pointsize': '10pt', 191 | 192 | # Additional stuff for the LaTeX preamble. 193 | #'preamble': '', 194 | } 195 | 196 | # Grouping the document tree into LaTeX files. List of tuples 197 | # (source start file, target name, title, 198 | # author, documentclass [howto, manual, or own class]). 199 | latex_documents = [ 200 | ('index', 'PHP-Bottle.tex', 'PHP-Bottle Documentation', 201 | 'Nergal, Gordon', 'manual'), 202 | ] 203 | 204 | # The name of an image file (relative to this directory) to place at the top of 205 | # the title page. 206 | #latex_logo = None 207 | 208 | # For "manual" documents, if this is true, then toplevel headings are parts, 209 | # not chapters. 210 | #latex_use_parts = False 211 | 212 | # If true, show page references after internal links. 213 | #latex_show_pagerefs = False 214 | 215 | # If true, show URL addresses after external links. 216 | #latex_show_urls = False 217 | 218 | # Documents to append as an appendix to all manuals. 219 | #latex_appendices = [] 220 | 221 | # If false, no module index is generated. 222 | #latex_domain_indices = True 223 | 224 | 225 | # -- Options for manual page output --------------------------------------- 226 | 227 | # One entry per manual page. List of tuples 228 | # (source start file, name, description, authors, manual section). 229 | man_pages = [ 230 | ('index', 'php-bottle', 'PHP-Bottle Documentation', 231 | ['Nergal, Gordon'], 1) 232 | ] 233 | 234 | # If true, show URL addresses after external links. 235 | #man_show_urls = False 236 | 237 | 238 | # -- Options for Texinfo output ------------------------------------------- 239 | 240 | # Grouping the document tree into Texinfo files. List of tuples 241 | # (source start file, target name, title, author, 242 | # dir menu entry, description, category) 243 | texinfo_documents = [ 244 | ('index', 'PHP-Bottle', 'PHP-Bottle Documentation', 245 | 'Nergal, Gordon', 'PHP-Bottle', 'One line description of project.', 246 | 'Miscellaneous'), 247 | ] 248 | 249 | # Documents to append as an appendix to all manuals. 250 | #texinfo_appendices = [] 251 | 252 | # If false, no module index is generated. 253 | #texinfo_domain_indices = True 254 | 255 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 256 | #texinfo_show_urls = 'footnote' 257 | 258 | # If true, do not generate a @detailmenu in the "Top" node's menu. 259 | #texinfo_no_detailmenu = False 260 | 261 | sys.path.append(os.path.abspath('_exts')) 262 | 263 | # ajouter PhpLexer 264 | from sphinx.highlighting import lexers 265 | from pygments.lexers.web import PhpLexer 266 | 267 | # ... 268 | # ajoute les extensions à la liste des extensions 269 | extensions = ['sensio.sphinx.refinclude', 270 | 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode'] 271 | 272 | # active la coloration syntaxique par défaut pour le code PHP qui n'est pas 273 | # entre ```` 274 | lexers['php'] = PhpLexer(startinline=True) 275 | lexers['php-annotations'] = PhpLexer(startinline=True) 276 | 277 | # ajoute PHP comme domaine principal 278 | primary_domain = 'php' 279 | 280 | language = 'fr' 281 | locale_dirs = ['locale/'] 282 | gettext_combat = True 283 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing to PHP-Bottle 2 | ========================== 3 | 4 | As a free software, PHP-Bottle depends on you to be a living project in order to 5 | help developers. 6 | 7 | PHP-Bottle is currently a tiny baby project, and does not have a dedicated 8 | website yet. Still, you can use this documentation site to learn how to use it 9 | and how to make it better. 10 | 11 | 12 | Get the Sources 13 | --------------- 14 | 15 | The PHP-Bottle `development repository`_ and the `issue tracker`_ are both hosted 16 | at Github. If you plan to contribute, it is a good idea to create an account 17 | there and fork the main repository. This way your changes and ideas are visible 18 | to other developers and can be discussed openly. Even without an account, you 19 | can clone the repository or just download the latest development version as a 20 | source archive. 21 | 22 | - git: git clone git://github.com/nergal/php-bottle.git 23 | - git/https: git clone https://github.com/nergal/php-bottle.git 24 | - Download: `Development branch`_ as tar archive or zip file. 25 | 26 | 27 | 28 | .. _issue tracker: https://github.com/nergal/php-bottle/issues 29 | .. _development repository: https://github.com/nergal/php-bottle 30 | .. _Development branch: https://github.com/nergal/php-bottle/archive/master.zip 31 | 32 | 33 | Submitting Patches 34 | ------------------ 35 | 36 | The best way to get your changes integrated into the main development branch is 37 | to fork the main repository at Github, create a new feature-branch, apply your 38 | changes and send a pull-request. Further down this page is a small collection of 39 | git workflow examples that may guide you. In any case, please follow some basic 40 | rules: 41 | 42 | - **Documentation**: Tell us what your patch does. Comment your code. If you 43 | introduced a new feature, add to the documentation so others can learn about 44 | it. 45 | - **Test**: Write tests to prove that your code works as expected and does not 46 | break anything. If you fixed a bug, write at least one test-case that triggers 47 | the bug. Make sure that all tests pass before you submit a patch. 48 | - **One patch at a time**: Only fix one bug or add one feature at a time. 49 | Design your patches so that they can be applyed as a whole. Keep your patches 50 | clean, small and focused. Sync with upstream: If the upstream/master branch 51 | changed while you were working on your patch, rebase or pull to make sure that 52 | your patch still applies without conflicts. 53 | 54 | Building the Documentation 55 | -------------------------- 56 | 57 | You need a recent version of `Sphinx`_ to build the documentation. The 58 | recommended way is to install **virtualenv** using your distribution package 59 | repository and install sphinx manually to get an up-to-date version. 60 | 61 | .. _Sphinx: http://sphinx-doc.org/ 62 | 63 | .. code-block:: bash 64 | 65 | # Install prerequisites 66 | which virtualenv || sudo apt-get install python-virtualenv 67 | virtualenv --no-site-dependencies venv 68 | ./venv/pip install -U sphinx sphinx-intl 69 | 70 | # Clone or download bottle from github 71 | git clone https://github.com/nergal/php-bottle.git 72 | 73 | # Activate build environment 74 | source ./venv/bin/activate 75 | 76 | # Build HTML docs 77 | cd php-bottle/docs 78 | make html 79 | 80 | Now, for convenience, you can move to the *_build/html* folder, and run a 81 | development server: 82 | 83 | .. code-block:: bash 84 | 85 | python3 -m http.server 86 | 87 | Then open a web browser to http://localhost:8000 . 88 | 89 | This documentation is multilingual, so you can also work on translations. You 90 | will need Poedit_ or any software that can edit .po files. 91 | 92 | .. _Poedit: http://poedit.net/ 93 | 94 | If you want to write translations on an existing language, or even add a new 95 | translation, you first have to build .po files using the following commands: 96 | 97 | .. code-block:: bash 98 | 99 | make gettext 100 | sphinx-intl update -l fr -p _build/locale 101 | 102 | Don’t forget to replace “fr” with your own language! 103 | 104 | You now have to complete the translation process directly in Poedit. The files 105 | to edit are in the *locale/fr/\*.po* dir (remember, use your own language code). 106 | 107 | Poedit can generate .mo files by its own. If you can’t, for any reason, compile 108 | your .po files into Poedit, there is a command for that: 109 | 110 | .. code-block:: bash 111 | 112 | sphinx-intl build 113 | 114 | You can compile the Sphinx documentation for your language with the following 115 | command: 116 | 117 | .. code-block:: bash 118 | 119 | make SPHINXOPTS="-Dlanguage=fr" html 120 | 121 | **If** you added a new language, you will have to open an issue on Github, 122 | asking for creation of that lang. In the meantime, you are free to submit your 123 | translations with pull-requests. 124 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. PHP-Bottle documentation master file, created by 2 | sphinx-quickstart on Sun Mar 8 22:00:27 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. _index: 7 | 8 | Welcome to PHP-Bottle's documentation! 9 | ====================================== 10 | 11 | PHP Bottle - PHP micro-framework inspired by minimalistic PyBottle_. 12 | 13 | .. _PyBottle: http://bottle.py.org 14 | 15 | Its goal is to provide a lightweight stack to quickly build modern web 16 | applications in PHP. It provides (M)VC approach, dynamic routing, support for 17 | any kind of templating, and it’s very agnostic. 18 | 19 | Here is an example of what you’ll have to write in order to get a dynamic web 20 | application with PHP-Bottle: 21 | 22 | .. code-block:: php 23 | 24 | getParam('password') == 'bottleisacoolframework'; 35 | } 36 | 37 | /** 38 | * @route / 39 | */ 40 | function index() { 41 | return 'Welcome on the Bottle index page!'; 42 | } 43 | 44 | /** 45 | * @route /hello/:name 46 | */ 47 | function hello($name) { 48 | return "

Hello, {$name}!

"; 49 | } 50 | 51 | /** 52 | * @route /mul/:num 53 | * @view /views/mul.php 54 | */ 55 | function mul($num) { 56 | return ['result' => $num * $num]; 57 | } 58 | 59 | /** 60 | * @route /restricted 61 | * @requires authenticated 62 | * @view /views/restricted.php 63 | */ 64 | function restricted() { 65 | return ['status' => 'OK']; 66 | } 67 | 68 | Contents: 69 | 70 | .. toctree:: 71 | :maxdepth: 2 72 | 73 | quickstart 74 | installation 75 | routing 76 | reference/request 77 | reference/response 78 | reference/templating 79 | contributing 80 | -------------------------------------------------------------------------------- /docs/locale/fr/LC_MESSAGES/contributing.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nergal/php-bottle/5554efc597d3b07d30cde828d6a7e1d71aae43bd/docs/locale/fr/LC_MESSAGES/contributing.mo -------------------------------------------------------------------------------- /docs/locale/fr/LC_MESSAGES/contributing.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: PHP-Bottle 0.1.0\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2015-03-10 11:44+0100\n" 7 | "PO-Revision-Date: 2015-03-10 11:59+0100\n" 8 | "Last-Translator: Damien Nicolas \n" 9 | "Language-Team: PHP-Bottle \n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Language: fr\n" 14 | "X-Generator: Poedit 1.5.5\n" 15 | 16 | #: ../../contributing.rst:2 17 | msgid "Contributing to PHP-Bottle" 18 | msgstr "Contribuer à PHP-Bottle" 19 | 20 | #: ../../contributing.rst:4 21 | msgid "" 22 | "As a free software, PHP-Bottle depends on you to be a living project in " 23 | "order to help developers." 24 | msgstr "" 25 | "En tant que logiciel libre, PHP-Bottle dépend de vous pour vivre et parvenir " 26 | "à son but d’aider les développeurs." 27 | 28 | #: ../../contributing.rst:7 29 | msgid "" 30 | "PHP-Bottle is currently a tiny baby project, and does not have a dedicated " 31 | "website yet. Still, you can use this documentation site to learn how to use " 32 | "it and how to make it better." 33 | msgstr "" 34 | "PHP-Bottle est encore un tout petit projet, et ne bénéficie pas encore d’un " 35 | "site web. Néanmoins, vous pouvez utiliser cette documentation pour apprendre " 36 | "à l’utiliser et l’améliorer." 37 | 38 | #: ../../contributing.rst:13 39 | msgid "Get the Sources" 40 | msgstr "Obtenir les sources" 41 | 42 | #: ../../contributing.rst:15 43 | msgid "" 44 | "The PHP-Bottle `development repository`_ and the `issue tracker`_ are both " 45 | "hosted at Github. If you plan to contribute, it is a good idea to create an " 46 | "account there and fork the main repository. This way your changes and ideas " 47 | "are visible to other developers and can be discussed openly. Even without an " 48 | "account, you can clone the repository or just download the latest " 49 | "development version as a source archive." 50 | msgstr "" 51 | "Le `dépôt de travail`_ et le `gestionnaire de tickets`_ sont tous deux " 52 | "hébergés sur Github. Si vous souhaitez contribuer, c’est une bonne idée d’y " 53 | "créer un compte et de forker le dépôt principal. De cette façon, vos " 54 | "modifications seront visibles aux autres développeurs et pourront être " 55 | "discutées publiquement. Même sans compte, vous pouvez cloner le dépôt, ou " 56 | "simplement télécharger l’archive de la dernière version." 57 | 58 | #: ../../contributing.rst:22 59 | msgid "git: git clone git://github.com/nergal/php-bottle.git" 60 | msgstr "git: git clone git://github.com/nergal/php-bottle.git" 61 | 62 | #: ../../contributing.rst:23 63 | msgid "git/https: git clone https://github.com/nergal/php-bottle.git" 64 | msgstr "git/https: git clone https://github.com/nergal/php-bottle.git" 65 | 66 | #: ../../contributing.rst:24 67 | msgid "Download: `Development branch`_ as tar archive or zip file." 68 | msgstr "Download: `Development branch`_ as tar archive or zip file." 69 | 70 | #: ../../contributing.rst:34 71 | msgid "Submitting Patches" 72 | msgstr "Soumettre des patches" 73 | 74 | #: ../../contributing.rst:36 75 | msgid "" 76 | "The best way to get your changes integrated into the main development branch " 77 | "is to fork the main repository at Github, create a new feature-branch, apply " 78 | "your changes and send a pull-request. Further down this page is a small " 79 | "collection of git workflow examples that may guide you. In any case, please " 80 | "follow some basic rules:" 81 | msgstr "" 82 | "La meilleure façon de voir vos changement intégrés dans la branche de " 83 | "développement principale est de forker le dépôt principal sur Github, créer " 84 | "une nouvelle branche liée à une fonctionnalité, et d’envoyer une pull-" 85 | "request. Vous trouverez des exemples de *workflows* Git qui pourraient vous " 86 | "aider, plus bas dans cette page. Dans tous les cas, veuillez respecter ces " 87 | "règles basiques :" 88 | 89 | #: ../../contributing.rst:42 90 | msgid "" 91 | "**Documentation**: Tell us what your patch does. Comment your code. If you " 92 | "introduced a new feature, add to the documentation so others can learn about " 93 | "it." 94 | msgstr "" 95 | "**Documentation** : indiquez ce que fait votre patch. Commentez votre code. " 96 | "Si vous introduisez une nouvelle fonctionnalité, ajoutez-la à la " 97 | "documentation afin que d’autres puissent apprendre son fonctionnement." 98 | 99 | #: ../../contributing.rst:45 100 | msgid "" 101 | "**Test**: Write tests to prove that your code works as expected and does not " 102 | "break anything. If you fixed a bug, write at least one test-case that " 103 | "triggers the bug. Make sure that all tests pass before you submit a patch." 104 | msgstr "" 105 | "**Tests** : écrivez des tests pour prouver que votre code fonctionne comme " 106 | "prévu et ne casse rien. Si vous avez corrigé un bug, écrivez au moins un " 107 | "test traitant le cas de figure qui provoquait le bug. Assurez-vous que tous " 108 | "les tests passent avant de soumettre un patche." 109 | 110 | #: ../../contributing.rst:48 111 | msgid "" 112 | "**One patch at a time**: Only fix one bug or add one feature at a time. " 113 | "Design your patches so that they can be applyed as a whole. Keep your " 114 | "patches clean, small and focused. Sync with upstream: If the upstream/" 115 | "master branch changed while you were working on your patch, rebase or pull " 116 | "to make sure that your patch still applies without conflicts." 117 | msgstr "" 118 | "**Un patch à la fois** : ne corrigez qu’un bug, ou n’ajoutez qu’une " 119 | "fonctionnalité, à la fois. Concevez vous patches pour qu’ils puissent être " 120 | "appliqués comme un tout. Maintenez leur code propre, succint et concentré " 121 | "sur l’essentiel. Soyez à jour par rapport à l’upstream : si la branche " 122 | "upstream/master change alors que vous travailliez sur votre patch, faites un " 123 | "*rebase* ou un *pull* pour vous assurez que votre patch s’applique toujours " 124 | "sans conflits." 125 | 126 | #: ../../contributing.rst:55 127 | msgid "Building the Documentation" 128 | msgstr "Construire la Documentation" 129 | 130 | #: ../../contributing.rst:57 131 | msgid "" 132 | "You need a recent version of `Sphinx`_ to build the documentation. The " 133 | "recommended way is to install **virtualenv** using your distribution package " 134 | "repository and install sphinx manually to get an up-to-date version." 135 | msgstr "" 136 | "Il vous faut une version récente de `Sphinx`_ pour construire la " 137 | "documentation. Nous vous recommandons d’installer **Virtualenv** via le " 138 | "gestionnaire de paquets de votre distribution, et d’installer Sphinx ensuite " 139 | "pour bénéficier d’une version à jour." 140 | 141 | #: ../../contributing.rst:80 142 | msgid "" 143 | "Now, for convenience, you can move to the *_build/html* folder, and run a " 144 | "development server:" 145 | msgstr "" 146 | "Maintenant, pour plus de praticité, vous pouvez vous déplacer dans le " 147 | "dossier *_build/html*, et lancer un serveur de développement." 148 | 149 | #: ../../contributing.rst:87 150 | msgid "Then open a web browser to http://localhost:8000 ." 151 | msgstr "" 152 | "Maintenant, ouvrez un navigateur web, et faites-le pointer sur http://" 153 | "localhost:8000 ." 154 | 155 | #: ../../contributing.rst:89 156 | msgid "" 157 | "This documentation is multilingual, so you can also work on translations. " 158 | "You will need Poedit_ or any software that can edit .po files." 159 | msgstr "" 160 | "Cette documentation est multilingue, donc vous pouvez également travailler " 161 | "sur les traductions. Vous aurez besoin pour cela de Poedit_, ou de tout " 162 | "logiciel capable d’éditer des fichiers .po ." 163 | 164 | #: ../../contributing.rst:94 165 | msgid "" 166 | "If you want to write translations on an existing language, or even add a new " 167 | "translation, you first have to build .po files using the following commands:" 168 | msgstr "" 169 | "Si vous voulez écrire des traductions sur une langue déjà présente, ou bien " 170 | "ajouter une langue, vous aurez tout d’abord besoin de construire les " 171 | "fichiers .po via les commandes suivantes :" 172 | 173 | #: ../../contributing.rst:102 174 | msgid "Don’t forget to replace “fr” with your own language!" 175 | msgstr "N’oubliez pas de remplacer « fr » par votre propre langue !" 176 | 177 | #: ../../contributing.rst:104 178 | msgid "" 179 | "You now have to complete the translation process directly in Poedit. The " 180 | "files to edit are in the *locale/fr/\\*.po* dir (remember, use your own " 181 | "language code)." 182 | msgstr "" 183 | "Il vous reste à compléter le processus de traduction directement " 184 | "dans Poedit. Les fichiers à traduire sont dans le dossier *locale/fr/\\*.po* " 185 | "(n’oubliez pas d’utiliser votre propre code de langue)." 186 | 187 | #: ../../contributing.rst:107 188 | msgid "" 189 | "Poedit can generate .mo files by its own. If you can’t, for any reason, " 190 | "compile your .po files into Poedit, there is a command for that:" 191 | msgstr "" 192 | "Poedit pout générer des fichiers .mo tout seul. Si vous ne pouvez pas, pour " 193 | "quelque raison que ce soit, compiler vos fichiers. mo, il existe une " 194 | "commande pour ça :" 195 | 196 | #: ../../contributing.rst:114 197 | msgid "" 198 | "You can compile the Sphinx documentation for your language with the " 199 | "following command:" 200 | msgstr "" 201 | "Vous pouvez compiler la documentation Sphinx pour votre langue via la " 202 | "commande suivante :" 203 | 204 | #: ../../contributing.rst:121 205 | msgid "" 206 | "**If** you added a new language, you will have to open an issue on Github, " 207 | "asking for creation of that lang. In the meantime, you are free to submit " 208 | "your translations with pull-requests." 209 | msgstr "" 210 | "**Si** vous avez ajouté une nouvelle langue, vous devrez ouvrir un ticket " 211 | "sur Github pour demander l’intégration de cette langue. Entre-temps, vous " 212 | "êtes libre de soumettre vos traductions par des pull-requests." 213 | -------------------------------------------------------------------------------- /docs/locale/fr/LC_MESSAGES/index.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nergal/php-bottle/5554efc597d3b07d30cde828d6a7e1d71aae43bd/docs/locale/fr/LC_MESSAGES/index.mo -------------------------------------------------------------------------------- /docs/locale/fr/LC_MESSAGES/index.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: PHP-Bottle 0.1.0\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2015-03-09 21:57+0100\n" 7 | "PO-Revision-Date: 2015-03-09 22:10+0100\n" 8 | "Last-Translator: Damien Nicolas \n" 9 | "Language-Team: PHP-Bottle \n" 10 | "Language: fr\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "X-Generator: Poedit 1.5.5\n" 15 | 16 | #: ../../index.rst:9 17 | msgid "Welcome to PHP-Bottle's documentation!" 18 | msgstr "Bienvenue dans la documentation de PHP-Bottle !" 19 | 20 | #: ../../index.rst:11 21 | msgid "PHP Bottle - PHP micro-framework inspired by minimalistic PyBottle_." 22 | msgstr "" 23 | "PHP-Bottle — micro-framework PHP inspiré par le minimaliste PyBottle_." 24 | 25 | #: ../../index.rst:15 26 | msgid "" 27 | "Its goal is to provide a lightweight stack to quickly build modern web " 28 | "applications in PHP. It provides (M)VC approach, dynamic routing, support " 29 | "for any kind of templating, and it’s very agnostic." 30 | msgstr "" 31 | "Son but est de fournir une pile logicielle légère pour construire rapidement" 32 | " des applications web modernes en PHP. Il fournit une approche (M)VC, du " 33 | "routage dynamique, du support pour tout type de templating, et est très " 34 | "agnostique." 35 | 36 | #: ../../index.rst:19 37 | msgid "" 38 | "Here is an example of what you’ll have to write in order to get a dynamic " 39 | "web application with PHP-Bottle:" 40 | msgstr "" 41 | "Voici un exemple de ce que vous auriez à écrire pour obtenir une application" 42 | " web dynamique avec PHP-Bottle :" 43 | 44 | #: ../../index.rst:68 45 | msgid "Contents:" 46 | msgstr "Contenu :" 47 | -------------------------------------------------------------------------------- /docs/locale/fr/LC_MESSAGES/quickstart.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nergal/php-bottle/5554efc597d3b07d30cde828d6a7e1d71aae43bd/docs/locale/fr/LC_MESSAGES/quickstart.mo -------------------------------------------------------------------------------- /docs/locale/fr/LC_MESSAGES/quickstart.po: -------------------------------------------------------------------------------- 1 | # 2 | msgid "" 3 | msgstr "" 4 | "Project-Id-Version: PHP-Bottle 0.1.0\n" 5 | "Report-Msgid-Bugs-To: \n" 6 | "POT-Creation-Date: 2015-03-09 21:57+0100\n" 7 | "PO-Revision-Date: 2015-12-14 11:01+0100\n" 8 | "Last-Translator: Damien Nicolas \n" 9 | "Language-Team: PHP-Bottle \n" 10 | "Language: fr\n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=UTF-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "X-Generator: Poedit 1.8.5\n" 15 | 16 | #: ../../quickstart.rst:2 17 | msgid "Quickstart" 18 | msgstr "Guide de démarrage rapide" 19 | 20 | #: ../../quickstart.rst:4 21 | msgid "" 22 | "This short guide will show you how to start a PHP-Bottle project. It should " 23 | "take only a few minutes to make you understand the basics." 24 | msgstr "" 25 | "Ce court guide vous présentera la création d’un projet PHP-Bottle. Il ne " 26 | "devrait prendre que quelques minutes pour vous faire comprendre les bases." 27 | 28 | #: ../../quickstart.rst:8 29 | msgid "Installation" 30 | msgstr "Installation" 31 | 32 | #: ../../quickstart.rst:10 33 | msgid "" 34 | "You first need a working PHP5 environment. An HTTP server is not required " 35 | "for development." 36 | msgstr "" 37 | "Il vous faut tout d’abord un environnement PHP5 fonctionnel. Un serveur HTTP " 38 | "n’est pas requis pour le développement." 39 | 40 | #: ../../quickstart.rst:13 41 | msgid "Create a directory for your application (let’s call it *projectDir*)" 42 | msgstr "Créez un dossier pour votre application (appelons-le *dossierProjet*)" 43 | 44 | #: ../../quickstart.rst:14 45 | msgid "" 46 | "Fetch the last PHP-Bottle archive here: https://github.com/nergal/php-bottle/" 47 | "raw/master/build/bottle.phar and put it in the root of *projectDir*" 48 | msgstr "" 49 | "Récupérez l’archive PHP-Bottle ici : https://github.com/nergal/php-bottle/" 50 | "raw/master/build/bottle.phar et placez-le à la racine de votre " 51 | "*dossierProjet*" 52 | 53 | #: ../../quickstart.rst:17 54 | msgid "" 55 | "Fetch the *.htaccess* file that will enable URL rewriting here: https://" 56 | "github.com/nergal/php-bottle/raw/master/.htaccess and put it near the " 57 | "*bottle.phar* file." 58 | msgstr "" 59 | "Récupérez le fichier *.htaccess* qui permettra la réécriture d’URL ici : " 60 | "https://github.com/nergal/php-bottle/raw/master/.htaccess et placez-le près " 61 | "de votre fichier *bottle.phar*." 62 | 63 | #: ../../quickstart.rst:20 64 | msgid "Create a *views* directory in *projectDir*" 65 | msgstr "Créez un dossier *views* dans *dossierProjet*" 66 | 67 | #: ../../quickstart.rst:22 68 | msgid "You’re done! Everything is ready to write your first PHP-Bottle page!" 69 | msgstr "" 70 | "C’est fait ! Tout est prêt pour que vous puissiez écrire votre première page " 71 | "avec PHP-Bottle !" 72 | 73 | #: ../../quickstart.rst:25 74 | msgid "First page" 75 | msgstr "Première page" 76 | 77 | #: ../../quickstart.rst:27 78 | msgid "" 79 | "Okay, last step was too complicated, I got that. So let’s make things " 80 | "simpler: just copy that in a *index.php* file:" 81 | msgstr "" 82 | "Ok, la dernière étape était trop compliquée, j’ai saisi. Alors baissons d’un " 83 | "cran la difficulté : copiez simplement ça dans un fichier *index.php* :" 84 | 85 | #: ../../quickstart.rst:45 86 | msgid "" 87 | "Well, was that too hard? We’re ready to test that. Open a terminal, move to " 88 | "*projectDir*, and type:" 89 | msgstr "" 90 | "Bien, c’était si compliqué ? Nous sommes prêts à tester ça. Ouvrez un " 91 | "terminal, déplacez-vous dans *dossierProjet*, et tapez :" 92 | 93 | #: ../../quickstart.rst:48 94 | msgid "$ php -S localhost:8000" 95 | msgstr "$ php -S localhost:8000" 96 | 97 | #: ../../quickstart.rst:50 98 | msgid "" 99 | "**Don’t close the terminal yet!** We’ve just started a development HTTP " 100 | "server. Yup, it was embedded in PHP all that time (well, since `PHP " 101 | "5.4.0`__). Now, just open a web browser to the following URL: http://" 102 | "localhost:8000 . You should see a page welcoming you. Great, that’s a basic " 103 | "hello world, and we didn’t have to write a frawemork to do that!" 104 | msgstr "" 105 | "**Ne fermez pas encore votre terminal !** Nous avons tout juste démarré un " 106 | "serveur de développement PHP. Ouaip, c’était inclus dans PHP depuis tout ce " 107 | "temps (enfin, depuis `PHP 5.4.0`__). Maintenant, il vous suffit d’ouvrir un " 108 | "navigateur web et de charger l’URL http://localhost:8000 . Vous devriez voir " 109 | "une page vous accueillant en anglais. Génial, c’est un bête *hello world*, " 110 | "et il n’était pas nécessaire de développer un framework pour ça !" 111 | 112 | #: ../../quickstart.rst:58 113 | msgid "But let’s make something cooler. Like **dynamic URLs**!" 114 | msgstr "" 115 | "Mais nous allons faire quelque chose de plus cool. Comme des **URLs " 116 | "dynamiques** !" 117 | 118 | #: ../../quickstart.rst:61 119 | msgid "Dynamic URLs" 120 | msgstr "URLs Dynamiques" 121 | 122 | #: ../../quickstart.rst:63 123 | msgid "" 124 | "Did you notice the funny comment just before the *index* function? That’s a " 125 | "**decorator**, but some weird frameworks call that an **annotation**. We’ll " 126 | "keep the first one." 127 | msgstr "" 128 | "Avez-vous remarqué le drôle de commentaire juste au-dessus de la fonction " 129 | "*index* ? C’est un **décorateur**, mais certains frameworks bizarres " 130 | "appellent ça une **annotation**. On s’en tiendra à la première version." 131 | 132 | #: ../../quickstart.rst:67 133 | msgid "" 134 | "That decorator tells PHP-Bottle when to call that function: in this case, " 135 | "when an URL matching “/” is found. Try this: change that line, replacing “/” " 136 | "by “my/dynamic/url”. Refresh the page (no need to restart the development " 137 | "server we started before), and don’t fear: you just met a 404 error." 138 | msgstr "" 139 | "Ce décorateur indique à PHP-Bottle quand appeler cette fonction : en " 140 | "l’occurrence, quand une URL correspondant à « / » est trouvée. Essayez ça : " 141 | "modifiez cette ligne, et remplacez « / » par « /mon/url/dynamique ». " 142 | "Rechargez la page (il n’y a pas besoin de redémarrer le serveur de " 143 | "développement que nous avons lancé plus tôt), et ne paniquez pas : vous " 144 | "venez de rencontrer une erreur 404." 145 | 146 | #: ../../quickstart.rst:72 147 | msgid "" 148 | "Why? Because no function (we call these *controllers*) has been registered " 149 | "for the “/” URL. In other words, the requested page is not found." 150 | msgstr "" 151 | "Pourquoi ? Parce qu’aucune fonction (qu’on appellera **contrôleur**) n’a été " 152 | "enregistré pour traiter l’URL « / ». En d’autres termes, la page demandée " 153 | "est introuvable." 154 | 155 | #: ../../quickstart.rst:75 156 | msgid "" 157 | "But what if you try to get to http://localhost:8000/my/dynamic/url ? Right, " 158 | "your welcome message is here. We changed the *route* decorator, and it " 159 | "literally changed the URL of our page." 160 | msgstr "" 161 | "Mais que se passe-t-il si vous allez sur la page http://localhost:8000/mon/" 162 | "url/dynamique ? Bien, notre message d’accueil est de nouveau là. Nous avons " 163 | "modifié le décorateur *route*, et il a littéralement modifié l’URL de notre " 164 | "page." 165 | 166 | #: ../../quickstart.rst:79 167 | msgid "" 168 | "For the next step, we should go back to an index page routed on the */* URL. " 169 | "So go back to the “First page” example." 170 | msgstr "" 171 | "Pour l’étape suivante, il vaut mieux repartir sur l’ancienne version de la " 172 | "page d’index, où celle-ci était routée sur l’URL */*. Alors repartons sur " 173 | "l’exemple « Première page »." 174 | 175 | #: ../../quickstart.rst:83 176 | msgid "Multiple pages" 177 | msgstr "Plusieurs pages" 178 | 179 | #: ../../quickstart.rst:85 180 | msgid "Add this to the end of your *index.php* file:" 181 | msgstr "Ajoutez ceci à la fin de votre fichier *index.php* :" 182 | 183 | #: ../../quickstart.rst:96 184 | msgid "" 185 | "Look closely at the defined *route* for this controller: it contains a word " 186 | "prefixed by a colon. That means the “:name” is a variable, a dynamic part of " 187 | "a (already dynamic) URL. You can call whatever URL beginning by “/hello/”, " 188 | "and the rest will be in the $name variable. Try it, with http://" 189 | "localhost:8000/hello/world , then replace “world” by your own name." 190 | msgstr "" 191 | "Regardez attentivement la *route* définie pour ce contrôleur. Elle contient " 192 | "un mot préfixé d’un deux-points. Cela signifie que « :name » est une " 193 | "variable, une partie dynamique d’une URL (elle-même déjà dynamique). Vous " 194 | "pouvez appeler n’importe quelle URL commençant par « /hello/ », et le reste " 195 | "sera passé dans votre variable $name. Essayez ça, avec http://localhost:8000/" 196 | "hello/world, puis remplacez « world » par votre propre nom." 197 | 198 | #: ../../quickstart.rst:102 199 | msgid "" 200 | "You can see that the “:name” part of the URL is indeed given to the " 201 | "*hello()* function as the *$name* argument." 202 | msgstr "" 203 | "Vous voyez que la partie « :name » de l’URL est effectivement passée en " 204 | "paramètre à la fonction *hello()* en tant que *$name*." 205 | 206 | #: ../../quickstart.rst:105 207 | msgid "" 208 | "A route can define as many variables as you wish, if you add them as params " 209 | "for your controller." 210 | msgstr "" 211 | "Une route peut définir autant de variables que vous le souhaitez, si vous " 212 | "les ajoutez également comme paramètres à votre contrôleur." 213 | 214 | #: ../../quickstart.rst:109 215 | msgid "Views" 216 | msgstr "Vues" 217 | 218 | #: ../../quickstart.rst:111 219 | msgid "" 220 | "Remember the :ref:`main page `, telling that PHP-Bottle was a (M)VC " 221 | "framework? Well, we’ve seen the C(ontroller), let’s see the V(iew)." 222 | msgstr "" 223 | "Vous vous souvenez de la :ref:`page d’accueil`, indiquant que PHP-" 224 | "Bottle était un framework (M)VC ? Et bien, nous avons vu le C(ontrôleur), " 225 | "voyons la V(ue)." 226 | 227 | #: ../../quickstart.rst:114 228 | msgid "" 229 | "A view is the “how do I show it” part of an application, opposing to a " 230 | "controller, which is the “what do I show”." 231 | msgstr "" 232 | "Une vue représente la partie « comment est-ce que je l’affiche » d’une " 233 | "application, en opposition au contrôleur, qui est plutôt le « qu’est-ce que " 234 | "j’affiche »." 235 | 236 | #: ../../quickstart.rst:117 237 | msgid "So basically, a view is a code meant only to display something." 238 | msgstr "" 239 | "Donc, basiquement, une vue est une portion de code uniquement destinée à " 240 | "afficher quelque chose." 241 | 242 | #: ../../quickstart.rst:119 243 | msgid "" 244 | "PHP-Bottle has chosen to use a well-known template engine to do that: PHP." 245 | msgstr "" 246 | "PHP-Bottle a choisi d’utiliser un moteur de templates bien connu : PHP." 247 | 248 | #: ../../quickstart.rst:121 249 | msgid "" 250 | "In order to use views, you first have to declare one in your decorators. " 251 | "Here is an example:" 252 | msgstr "" 253 | "Pour pouvoir utiliser les vues, vous devez tout d’abord en déclarer dans vos " 254 | "décorateurs. Voici un exemple :" 255 | 256 | #: ../../quickstart.rst:134 257 | msgid "" 258 | "The @view decorator links to the position of the view file, relative to the " 259 | "index.php file. You can use whatever extension you like for the view." 260 | msgstr "" 261 | "Le décorateur @view pointe vers la position du fichier de vue, relativement " 262 | "au fichier index.php. Vous pouvez utiliser n’importe quelle extension pour " 263 | "la vue." 264 | 265 | #: ../../quickstart.rst:137 266 | msgid "" 267 | "The second step, after the @view decorator, is to return an associative " 268 | "array in your controller. Keys of this array will be extracted into vars " 269 | "available in your view. So, in */views/view-test.php*, you can display the " 270 | "way you want the *$var* variable, as defined in the controller. Write in */" 271 | "views/view-test.php*:" 272 | msgstr "" 273 | "La seconde étape, après avoir ajouté un décorateur @view, est de retourner " 274 | "un tableau associatif dans votre contrôleur. Les clés de ce tableau seront " 275 | "extraites dans des variables disponibles dans votre vue. Donc, dans */views/" 276 | "view-test.php*, vous pouvez afficher de la façon dont vous voulez la " 277 | "variable *$var*, telle que définie dans votre contrôleur. Écrivez ceci dans " 278 | "*/views/view-test.php* :" 279 | 280 | #: ../../quickstart.rst:146 281 | msgid "" 282 | "Now, open your browser to http://localhost:8000/view-test and observe. You " 283 | "see a

tag, saying “Hello world”. The last part of this sentence was " 284 | "given by the controller. It could as well be a dynamic value." 285 | msgstr "" 286 | "Maintenant, ouvrez votre navigateur sur http://localhost:8000/view-test et " 287 | "observez. Vous avez une balise

contenant “Hello world”. La dernière " 288 | "partie de cette phrase a été fournie par le contrôleur. Il aurait tout aussi " 289 | "bien pu s’agir d’une valeur dynamique." 290 | -------------------------------------------------------------------------------- /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. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PHP-Bottle.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PHP-Bottle.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | This short guide will show you how to start a PHP-Bottle project. It should take 5 | only a few minutes to make you understand the basics. 6 | 7 | Installation 8 | ------------ 9 | 10 | You first need a working PHP5 environment. An HTTP server is not required for 11 | development. 12 | 13 | #. Create a directory for your application (let’s call it *projectDir*) 14 | #. Fetch the last PHP-Bottle archive here: 15 | https://github.com/nergal/php-bottle/raw/master/build/bottle.phar and put it 16 | in the root of *projectDir* 17 | #. Fetch the *.htaccess* file that will enable URL rewriting here: 18 | https://github.com/nergal/php-bottle/raw/master/.htaccess and put it near the 19 | *bottle.phar* file. 20 | #. Create a *views* directory in *projectDir* 21 | 22 | You’re done! Everything is ready to write your first PHP-Bottle page! 23 | 24 | First page 25 | ---------- 26 | 27 | Okay, last step was too complicated, I got that. So let’s make things simpler: 28 | just copy that in a *index.php* file: 29 | 30 | .. code-block:: php 31 | 32 | `, telling that PHP-Bottle was a (M)VC framework? Well, 112 | we’ve seen the C(ontroller), let’s see the V(iew). 113 | 114 | A view is the “how do I show it” part of an application, opposing to a 115 | controller, which is the “what do I show”. 116 | 117 | So basically, a view is a code meant only to display something. 118 | 119 | PHP-Bottle has chosen to use a well-known template engine to do that: PHP. 120 | 121 | In order to use views, you first have to declare one in your decorators. Here is 122 | an example: 123 | 124 | .. code-block:: php 125 | 126 | /** 127 | * @route /view-test 128 | * @view /views/view-test.php 129 | */ 130 | function view_test() { 131 | return ['var' => 'world']; 132 | } 133 | 134 | The @view decorator links to the position of the view file, relative to the 135 | index.php file. You can use whatever extension you like for the view. 136 | 137 | The second step, after the @view decorator, is to return an associative array in 138 | your controller. Keys of this array will be extracted into vars available in 139 | your view. So, in */views/view-test.php*, you can display the way you want the 140 | *$var* variable, as defined in the controller. Write in */views/view-test.php*: 141 | 142 | .. code-block:: php+html 143 | 144 |

Hello

145 | 146 | Now, open your browser to http://localhost:8000/view-test and observe. You see a 147 |

tag, saying “Hello world”. The last part of this sentence was given by the 148 | controller. It could as well be a dynamic value. 149 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | getParam('password') == 'bottleisacoolframework'; 9 | } 10 | 11 | /** 12 | * @route / 13 | */ 14 | function index() { 15 | return 'Welcome on the Bottle index page!'; 16 | } 17 | 18 | /** 19 | * @route /hello/:name 20 | */ 21 | function hello($name) { 22 | return "

Hello, {$name}!

"; 23 | } 24 | 25 | /** 26 | * @route /mul/:num 27 | * @view /views/mul.php 28 | */ 29 | function mul($num) { 30 | return ['result' => $num * $num]; 31 | } 32 | 33 | /** 34 | * @route /restricted 35 | * @requires authenticated 36 | * @view /views/restricted.php 37 | */ 38 | function restricted() { 39 | return ['status' => 'OK']; 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pip-requirements.txt: -------------------------------------------------------------------------------- 1 | Jinja2==2.7.3 2 | MarkupSafe==0.23 3 | Pygments==2.0.2 4 | Sphinx==1.2.3 5 | docutils==0.12 6 | polib==1.0.6 7 | six==1.9.0 8 | sphinx-intl==0.9.5 9 | -------------------------------------------------------------------------------- /src/bottle.php: -------------------------------------------------------------------------------- 1 | 49 | .bottle-framework-exception {background-color: #f0f5ff;padding: 5px;border: 1px solid #d5e5ee;font-size:12px;font-family: Tahoma, Arial;} 50 | .bottle-framework-exception h1 {background-color: #cce;margin: 0px;padding: 5px;font-size:13px;} 51 | .bottle-framework-exception h1 span {color: #555;} 52 | .bottle-framework-exception h1.right {float:right;margin-right: 5px;font-weight:normal} 53 | .bottle-framework-trace {list-style-type: none;padding: 0px;border: 1px solid #cce;border-bottom: 0px;} 54 | .bottle-framework-trace li {border-bottom: 1px solid #cce;background-color: #fff;padding: 3px 2px;}'; 55 | 56 | $type = 'Unknown exception'; 57 | switch ($e->getCode()) { 58 | case E_USER_ERROR: 59 | case E_ERROR: 60 | $type = 'Error'; 61 | break; 62 | case E_USER_WARNING: 63 | case E_WARNING: 64 | $type = 'Warning'; 65 | break; 66 | case E_USER_NOTICE: 67 | case E_NOTICE: 68 | $type = 'Notice'; 69 | break; 70 | } 71 | 72 | $html.= '
'; 73 | $html.= '

in file ' . $e->getFile() . ':' . $e->getLine() . '

'; 74 | $html.= '

' . $type . ': ' . $e->getMessage() . '

'; 75 | $html.= '
    '; 76 | 77 | $trace = $e->getTrace(); 78 | foreach ($trace as $key => $iteration) { 79 | $file = (isset($iteration['line']) ? ($iteration['file'] . ':' . $iteration['line']) : ''); 80 | $function = (isset($iteration['class']) ? $iteration['class'] : ''); 81 | $function.= (isset($iteration['type']) ? $iteration['type'] : ''); 82 | $function.= $iteration['function'] . '()'; 83 | $html.= '
  1. #' . $key . ' ' . $function . ' in ' . $file . '
  2. '; 84 | } 85 | $html.= '
'; 86 | 87 | if ($e instanceof Bottle_Forbidden_Exception) { 88 | header("HTTP/1.0 403 Forbidden"); 89 | } elseif ($e instanceof Bottle_NotFound_Exception) { 90 | header("HTTP/1.0 404 Not Found"); 91 | } else { 92 | header("HTTP/1.0 500 Internal Server Error"); 93 | } 94 | echo $html; 95 | } 96 | 97 | /** 98 | * Errors handler 99 | * 100 | * @param integer $errno 101 | * @param string $errstr 102 | * @param string $errfile 103 | * @param string $errline 104 | * @return void 105 | */ 106 | public function errorHandler($errno, $errstr, $errfile, $errline) { 107 | $exception = new ErrorException($errstr, 0, $errno, $errfile, $errline); 108 | return $this->exceptionHandler($exception); 109 | } 110 | 111 | /** 112 | * Fatal error handler 113 | * 114 | * @todo некорректно работает - отстреливаются пред. обработчики 115 | * @return void 116 | */ 117 | public function shutdownHandler() { 118 | $last_error = error_get_last(); 119 | if ($last_error['type'] === E_ERROR OR $last_error['type'] === E_USER_ERROR) { 120 | ob_clean(); 121 | $this->errorHandler( 122 | $last_error['type'], 123 | $last_error['message'], 124 | $last_error['file'], 125 | $last_error['line'] 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * Setting up global handlers 132 | * 133 | * @construct 134 | * @return void 135 | */ 136 | public function __construct() { 137 | set_exception_handler(array($this, 'exceptionHandler')); 138 | set_error_handler(array($this, 'errorHandler')); 139 | 140 | register_shutdown_function(array($this, 'shutdownHandler')); 141 | spl_autoload_register(array($this, 'autoload'), TRUE, TRUE); 142 | 143 | Bottle_Core::start(); 144 | } 145 | } 146 | 147 | // creating the global $request and $response objects 148 | $request = $response = null; 149 | new Bottle; 150 | -------------------------------------------------------------------------------- /src/bottle/core.php: -------------------------------------------------------------------------------- 1 | isUserDefined()) { 32 | $docline = $controller->getDocComment(); 33 | 34 | if (preg_match('#^( |\t)*\*( )?@route (?P.+?)$#umsi', $docline, $matches)) { 35 | 36 | $route = new Bottle_Route($controller->getName()); 37 | $route->setMask($matches['route']); 38 | $route->bindController($controller); 39 | 40 | $controllers_list[$controller->getName()] = $route->getMask(); 41 | 42 | 43 | if ($route->isServed($request->uri())) { 44 | if (preg_match('#^( |\t)*\*( )?@view (?P.+?)$#umsi', $docline, $matches)) { 45 | $view = new Bottle_View($matches['view']); 46 | $response->setView($view); 47 | $views_list[] = $view; 48 | } 49 | 50 | /* 51 | * optional controller condition support 52 | * decorator param may be a single word (function 53 | * name), or a function name followed by an argument 54 | * list (separated by spaces). If an argument starts 55 | * with a $, and the controller also have an 56 | * argument with the same name, that value will be 57 | * passed in the function. 58 | */ 59 | if (preg_match('#^( |\t)*\*( )?@requires (?P.+?)$#umsi', $docline, $matches)) { 60 | // checking if the condition function has params 61 | if(strpos($matches['condition'], ' ')) { 62 | $condition_parts = explode(' ', $matches['condition']); 63 | $condition_name = array_shift($condition_parts); 64 | $route->setCondition($condition_name, $condition_parts); 65 | } else { 66 | if(!function_exists($matches['condition'])) { 67 | throw new Bottle_Exception('Unknown condition: '.$matches['condition']. 68 | ' for controller '.$controller->getName()); 69 | } 70 | $route->setCondition($matches['condition']); 71 | } 72 | } 73 | 74 | $request->setRouter($route); 75 | //break; 76 | } else { 77 | // fetching all views for the url() function 78 | if (preg_match('#^( |\t)*\*( )?@view (?P.+?)$#umsi', $docline, $matches)) { 79 | $view = new Bottle_View($matches['view']); 80 | $views_list[] = $view; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | // giving the route list to each view 89 | foreach($views_list as $view) { 90 | 91 | $view->setRoutes($controllers_list); 92 | } 93 | 94 | 95 | $response->dispatch($request); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/bottle/exception.php: -------------------------------------------------------------------------------- 1 | _docroot = $docroot; 58 | // truncating GET params 59 | $uri = substr(urldecode($_SERVER['REQUEST_URI']), strlen($docroot)-1); 60 | if(strpos($uri, '?') != false) { 61 | $uri = substr($uri, 0, strpos($uri, '?')); 62 | } 63 | $this->_uri = $uri; 64 | $this->_params = $_REQUEST; 65 | 66 | // getting request headers 67 | $this->headers = $this->parseHeaders($_SERVER); 68 | } 69 | 70 | /** 71 | * Current URL 72 | * 73 | * @return string 74 | */ 75 | public function uri() { 76 | return $this->_uri; 77 | } 78 | 79 | /** 80 | * Setter for routing 81 | * 82 | * @param Bottle_Router $route 83 | * @return void 84 | */ 85 | public function setRouter(Bottle_Route $route) { 86 | $this->route = $route; 87 | } 88 | 89 | /** 90 | * Getter for routing 91 | * 92 | * @return Bottle_Route 93 | */ 94 | public function getRoute() { 95 | return $this->route; 96 | } 97 | 98 | /** 99 | * Getter for all GET/POST params 100 | * 101 | * @return array 102 | */ 103 | public function getParams() { 104 | return $this->_params; 105 | } 106 | 107 | /** 108 | * Getter for one param, optionnally returning a given default value 109 | * 110 | * @param string $name 111 | * @param string $default optional 112 | * @return string|false false is returned if the param does not exists and 113 | * no default value is given 114 | */ 115 | public function getParam($name, $default = false) { 116 | if(isset($this->_params[$name])) { 117 | return $this->_params[$name]; 118 | } else { 119 | return $default; 120 | } 121 | } 122 | 123 | /** 124 | * getter for docroot, which is the URL prefix for Bottle 125 | * 126 | * @return string 127 | */ 128 | public function getDocroot() { 129 | return $this->_docroot; 130 | } 131 | 132 | /** 133 | * Request headers parser 134 | * Returns an associative array of the headers found in $_SERVER, in 135 | * (normally) correct case. 136 | * 137 | * The original function is found here: 138 | * http://us2.php.net/manual/fr/function.apache-request-headers.php#70810 139 | * 140 | * @param array $server 141 | * @return array 142 | */ 143 | protected function parseHeaders($server) { 144 | $arh = array(); 145 | $rx_http = '/\AHTTP_/'; 146 | foreach($server as $key => $val) { 147 | if( preg_match($rx_http, $key) ) { 148 | $arh_key = preg_replace($rx_http, '', $key); 149 | $rx_matches = array(); 150 | // do some nasty string manipulations to restore the original letter case 151 | // this should work in most cases 152 | $rx_matches = explode('_', $arh_key); 153 | if( count($rx_matches) > 0 and strlen($arh_key) > 2 ) { 154 | foreach($rx_matches as $ak_key => $ak_val) { 155 | $rx_matches[$ak_key] = ucfirst(strtolower($ak_val)); 156 | } 157 | $arh_key = implode('-', $rx_matches); 158 | 159 | // exception for the DNT header, which must stay uppercase 160 | if($arh_key == 'Dnt') { 161 | $arh_key = strtoupper($arh_key); 162 | } 163 | 164 | } 165 | 166 | $arh[$arh_key] = $val; 167 | } 168 | } 169 | return( $arh ); 170 | } 171 | 172 | /** 173 | * getter for all headers 174 | * 175 | * @return array 176 | */ 177 | public function getHeaders(){ 178 | return $this->headers; 179 | } 180 | 181 | 182 | /** 183 | * getter for a given header 184 | * 185 | * @param string $header_name 186 | * @return string|null 187 | */ 188 | public function getHeader($header_name) { 189 | if(isset($this->headers[$header_name])) { 190 | return $this->headers[$header_name]; 191 | } else { 192 | return null; 193 | } 194 | } 195 | 196 | /** 197 | * helper method to know if we’re on an AJAX request 198 | * 199 | * @return bool 200 | */ 201 | public function isAjax(){ 202 | return $this->getHeader('X-Requested-With') == 'XMLHttpRequest'; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/bottle/response.php: -------------------------------------------------------------------------------- 1 | route !== NULL) { 38 | $controller = $request->route->getController(); 39 | $parameters = $request->route->getParameters(); 40 | $condition = $request->route->getCondition(); 41 | if($condition) { 42 | // calling the condition function 43 | $result = $condition[0]($request, $condition[1]); 44 | if(!$result) { 45 | throw new Bottle_Forbidden_Exception('Forbidden'); 46 | } 47 | } 48 | 49 | $body = $controller->invokeArgs($parameters); 50 | 51 | // if the controller returns an array, we try to call a 52 | // "global_context" function, which will add context to $body 53 | if(is_array($body)) { 54 | if(function_exists('global_context')) { 55 | $context = global_context($request); 56 | if(!is_array($context)) { 57 | throw new Bottle_Exception('global_context function must return an array'); 58 | } 59 | $body = array_merge($context, $body); 60 | } 61 | } 62 | 63 | $this->setBody($body); 64 | } else { 65 | throw new Bottle_NotFound_Exception('No route found'); 66 | } 67 | 68 | $this->send(); 69 | } 70 | 71 | /** 72 | * Sets the response content 73 | * 74 | * @param string|array body 75 | * @return void 76 | */ 77 | public function setBody($body) { 78 | $this->_body = $body; 79 | } 80 | 81 | /** 82 | * Adds data to content 83 | * 84 | * @param string $body 85 | * @return void 86 | */ 87 | public function appendBody($body) { 88 | // todo: what if body is an array? 89 | $this->_body.= $body; 90 | } 91 | 92 | /** 93 | * Returns the response's content 94 | * 95 | * @return string|array 96 | */ 97 | public function getBody() { 98 | return $this->_body; 99 | } 100 | 101 | /** 102 | * Sending the request to the client 103 | * 104 | * @return void 105 | */ 106 | public function send() { 107 | // TODO: Caching 108 | foreach($this->_headers as $header_name => $header_value) { 109 | header($header_name.': '.$header_value); 110 | } 111 | if(!$this->_sendBody) { 112 | return; 113 | } 114 | // TODO: Response code treatment 115 | $body = $this->getBody(); 116 | $view = $this->getView(); 117 | 118 | // $body can be an array if the @view annotation is used 119 | 120 | if (is_array($body) AND $view !== NULL) { 121 | $view->bind($body); 122 | echo $view->render(TRUE); 123 | } else { 124 | echo $body; 125 | } 126 | } 127 | 128 | /** 129 | * Response view wrapper assignment 130 | * 131 | * @param Bottle_View $view 132 | * @return void 133 | */ 134 | public function setView(Bottle_View $view) { 135 | $this->_view = $view; 136 | } 137 | 138 | /** 139 | * Getter for the response view wrapper 140 | * 141 | * @return Bottle_View 142 | */ 143 | public function getView() { 144 | return $this->_view; 145 | } 146 | 147 | /** 148 | * Getter for all response headers 149 | * 150 | * @return array 151 | */ 152 | public function getHeaders() { 153 | return $this->_headers; 154 | } 155 | 156 | /** 157 | * Getter for a single response header, returns false if it does not exist. 158 | * 159 | * @param string $header 160 | * 161 | * @return string|false 162 | */ 163 | public function getHeader($header) { 164 | if(isset($this->_headers[$header])) { 165 | return $this->_headers[$header]; 166 | } else { 167 | return false; 168 | } 169 | } 170 | 171 | /** 172 | * Setter for a response header. 173 | * 174 | * @param string $header the name of the header 175 | * @param string $value 176 | * 177 | */ 178 | public function setHeader($header, $value) { 179 | $this->_headers[$header] = $value; 180 | } 181 | 182 | /** 183 | * delete a response header 184 | * 185 | * @param string $header 186 | */ 187 | public function deleteHeader($header) { 188 | if(isset($this->_headers[$header])) { 189 | unset($this->_headers[$header]); 190 | } 191 | } 192 | 193 | /** 194 | * sends a Location header, redirecting to a given URL or route. 195 | * 196 | * @param array|string $location 197 | * @return null 198 | */ 199 | public function redirect($location) { 200 | if(is_array($location)) { 201 | $url = $this->getView()->url($location[0], $location[1]); 202 | } elseif($location[0] != '/') { 203 | $url = $this->getView()->url($location); 204 | } else { 205 | $url = $location; 206 | } 207 | $this->setHeader('Location', $url); 208 | $this->_sendBody = false; 209 | return null; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/bottle/route.php: -------------------------------------------------------------------------------- 1 | _mask = $route; 43 | } 44 | 45 | /** 46 | * Returns the routing mask 47 | * 48 | * @return string 49 | */ 50 | public function getMask() { 51 | return $this->_mask; 52 | } 53 | 54 | /** 55 | * Sets the reflection controller 56 | * 57 | * @param ReflectionFunction $instance 58 | * @return void 59 | */ 60 | public function bindController(ReflectionFunction $instance) { 61 | $this->_controller = $instance; 62 | } 63 | 64 | /** 65 | * Returns the controller ReflectionFunction object 66 | * 67 | * @return ReflectionFunction 68 | */ 69 | public function getController() { 70 | return $this->_controller; 71 | } 72 | 73 | /** 74 | * Setting query parameters 75 | * 76 | * @param string $name 77 | * @param mixed $value 78 | * @return void 79 | */ 80 | public function setParameter($name, $value) { 81 | $this->_parameters[$name] = $value; 82 | } 83 | 84 | /** 85 | * Select all query params 86 | * 87 | * @return array 88 | */ 89 | public function getParameters() { 90 | return $this->_parameters; 91 | } 92 | 93 | /** 94 | * Adds a condition function 95 | * 96 | * @param string $function_name 97 | * @param array $params optional array of values that will be passed to the 98 | * function. Values beginning with a "$" will be replaced with the 99 | * controller parameter with the same name. 100 | * @return void 101 | */ 102 | public function setCondition($function_name, $params = []) { 103 | $this->_condition = [$function_name, $params]; 104 | 105 | foreach($this->getParameters() as $key => $value) { 106 | // checking if the parameter name is found in the condition 107 | $param_key = array_search('$'.$key, $this->_condition[1]); 108 | if($param_key !== false) { 109 | $this->_condition[1][$param_key] = $value; 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * Gets the condition 116 | * @return array 117 | */ 118 | public function getCondition() { 119 | return $this->_condition; 120 | } 121 | 122 | /** 123 | * Can the current route serve the given URL? 124 | * 125 | * @param string $url 126 | * @return boolean 127 | */ 128 | public function isServed($url) { 129 | $url = trim($url, '/'); 130 | $route = trim($this->getMask(), '/'); 131 | 132 | // TODO: add some testing 133 | $regex = '#^' . preg_replace('#(?:\:([a-z0-9]+))#', '(?P<$1>.+)', $route) . '$#uUi'; 134 | if (preg_match($regex, $url, $matches)) { 135 | foreach ($matches as $key => $match) { 136 | if (!is_numeric($key)) { 137 | $this->setParameter($key, $match); 138 | 139 | } 140 | } 141 | 142 | return TRUE; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/bottle/view.php: -------------------------------------------------------------------------------- 1 | setFilename($filename); 34 | } 35 | } 36 | 37 | /** 38 | * Setting the file name for the wrapper 39 | * 40 | * @param string $filename 41 | * @return void 42 | */ 43 | public function setFilename($filename) { 44 | $realname = realpath(APPLICATION_PATH . ltrim($filename, DIRECTORY_SEPARATOR)); 45 | 46 | if (!file_exists($realname)) { 47 | throw new Bottle_Exception('No such file ' . $filename); 48 | } 49 | 50 | $this->_filename = $realname; 51 | } 52 | 53 | /** 54 | * sets a route list 55 | * 56 | * @param array @routes 57 | * @return void 58 | */ 59 | public function setRoutes($routes) { 60 | $this->routes = (array) $routes; 61 | } 62 | 63 | /** 64 | * Sets the view variables returned by the controller 65 | * 66 | * @param array $params 67 | * @return void 68 | */ 69 | public function bind(Array $params) { 70 | $this->_params = $params; 71 | } 72 | 73 | /** 74 | * Rendering the view 75 | * 76 | * @param boolean $return_output 77 | * @return string|void 78 | */ 79 | public function render($return_output = FALSE) { 80 | ob_start(); 81 | 82 | extract($this->_params, EXTR_OVERWRITE); 83 | include $this->_filename; 84 | 85 | $output = ob_get_clean(); 86 | if ($return_output === TRUE) { 87 | return $output; 88 | } 89 | 90 | echo $output; 91 | } 92 | 93 | /** 94 | * returns the URL of a given route, filled with optionnal params 95 | * 96 | * @param string $route the route name 97 | * @param array $params optional the associative array of URL params 98 | * @return string the matching URL 99 | */ 100 | public function url($route, $params = array()) { 101 | global $request; 102 | if(!isset($this->routes[$route])) { 103 | // the route name does not exist. Maybe we’re linking to a static 104 | // file? 105 | return $request->getDocroot() . trim($route, '/'); 106 | } else { 107 | $url = $this->routes[$route]; 108 | foreach($params as $param_name => $param_value) { 109 | $url = str_replace(':'.$param_name, urlencode($param_value), $url); 110 | } 111 | return $request->getDocroot() . trim($url, '/'); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 8 | * @version 0.1 9 | * @license MIT 10 | */ 11 | class BottleTest extends BottleTestCase { 12 | public function testIndexPage() { 13 | $url = $this->buildUrl(); 14 | $res = send_request($url); 15 | $this->assertEquals('Success', $res['content']); 16 | } 17 | 18 | public function testParam() { 19 | $url = $this->buildUrl('param/test'); 20 | $res = send_request($url); 21 | $this->assertEquals('Param: test', $res['content']); 22 | } 23 | 24 | public function testView() { 25 | $url = $this->buildUrl('view/test'); 26 | $res = send_request($url); 27 | $assertedContent = <<<'EOL' 28 | 29 | 30 | 31 | 32 | view 33 | 34 | 35 | test 36 | 37 | 38 | 39 | EOL; 40 | $this->assertEquals($assertedContent, $res['content']); 41 | } 42 | 43 | function testHeaderOK() { 44 | $url = $this->buildUrl('err'); 45 | $res = send_request($url); 46 | $this->assertEquals(404, $res['httpcode']); 47 | } 48 | 49 | function testHeader500() { 50 | $url = $this->buildUrl('exception'); 51 | $res = send_request($url); 52 | $this->assertEquals(500, $res['httpcode']); 53 | } 54 | 55 | function testHeaderRedirect() { 56 | $url = $this->buildUrl('redirect'); 57 | $res = send_request($url); 58 | $this->assertEquals(302, $res['httpcode']); 59 | $this->assertArrayHasKey('Location', $res['headers']); 60 | $this->assertEquals('/redirected', $res['headers']['Location']); 61 | } 62 | 63 | function testRedirect() { 64 | $url = $this->buildUrl('redirect'); 65 | $content = file_get_contents($url); 66 | $this->assertEquals('Redirected', $content); 67 | } 68 | 69 | function testHeader() { 70 | $url = $this->buildUrl('header'); 71 | $res = send_request($url); 72 | 73 | $this->assertArrayHasKey('Content-type', $res['headers']); 74 | $this->assertEquals('text/plain', $res['headers']['Content-type']); 75 | 76 | } 77 | 78 | function testUrl() { 79 | $url = $this->buildUrl('url'); 80 | $content = file_get_contents($url); 81 | $this->assertEquals('/redirected', $content); 82 | 83 | $url = $this->buildUrl('url2'); 84 | $content = file_get_contents($url); 85 | $this->assertEquals('/param/name', $content); 86 | } 87 | 88 | function testRouteWithNonAscii() { 89 | $data = [ 90 | 'subdir_with_éééé/' => 'Success', 91 | 'subdir_with_éééé/é' => 'Successé', 92 | 'subdir_with_éééé/\'' => 'Success\'', 93 | 'subdir_with_éééé/ ' => 'Success ', 94 | ]; 95 | 96 | foreach ($data as $part => $result) { 97 | $url = $this->buildUrl($part); 98 | $content = file_get_contents($url); 99 | $this->assertEquals($result, $content); 100 | } 101 | } 102 | 103 | function testSimpleCondition() { 104 | $url = $this->buildUrl('restricted'); 105 | $content = file_get_contents($url); 106 | $this->assertEquals('OK', $content); 107 | 108 | $url = $this->buildUrl('restricted2'); 109 | $res = send_request($url); 110 | $this->assertEquals(403, $res['httpcode']); 111 | } 112 | 113 | function testArgCondition() { 114 | $url = $this->buildUrl('restricted3'); 115 | $content = file_get_contents($url); 116 | $this->assertEquals('OK', $content); 117 | 118 | $url = $this->buildUrl('restricted4'); 119 | $res = send_request($url); 120 | $this->assertEquals(403, $res['httpcode']); 121 | } 122 | 123 | function testDynArgCondition() { 124 | $url = $this->buildUrl('restricted5/url_param'); 125 | $content = file_get_contents($url); 126 | $this->assertEquals('OK', $content); 127 | 128 | $url = $this->buildUrl('restricted5/error'); 129 | $res = send_request($url); 130 | $this->assertEquals(403, $res['httpcode']); 131 | } 132 | 133 | function testGlobalContext() { 134 | $url = $this->buildUrl('global-context'); 135 | $content = file_get_contents($url); 136 | $this->assertEquals('global:local:local2', $content); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /tests/classes/bottleTestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * @version 0.1 8 | * @license MIT 9 | */ 10 | abstract class BottleTestCase extends PHPUnit_Framework_TestCase { 11 | private $port = 8999; 12 | private $command = 'php -t %s -S localhost:%d 2>&1 > /dev/null'; 13 | private $pid = 0; 14 | 15 | /** 16 | * Setup fixture: starts a test HTTP server and sends it to background 17 | */ 18 | public function setUp() { 19 | if(!function_exists('pcntl_fork')) { 20 | exit('pcntl_fork doesn\'t exists. Exiting...'.PHP_EOL); 21 | } 22 | 23 | $path = APPLICATION_PATH . 'fixtures'; 24 | $this->pid = posix_getpid(); 25 | $this->command = sprintf($this->command, $path, $this->port); 26 | $pid = pcntl_fork(); 27 | 28 | if($pid == -1) { 29 | exit('Error forking.'.PHP_EOL); 30 | } else if($pid == 0) { 31 | exec($this->command); 32 | exit(); 33 | } 34 | sleep(1); 35 | } 36 | 37 | /** 38 | * Kills the background PHP tasks 39 | */ 40 | public function TearDown() { 41 | $forks = explode(PHP_EOL, exec('ps -C \''.$this->command.'\' -o pid='.$this->pid)); 42 | foreach($forks as $fork) { 43 | if($fork != $this->pid) { 44 | //posix_kill($fork, SIGTERM); 45 | exec('kill '.$fork.' 2>&1 > /dev/null'); 46 | } 47 | } 48 | } 49 | 50 | protected function buildUrl($uri = '/') { 51 | return 'http://localhost:' . $this->port . '/' . urlencode(ltrim($uri, '/')); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/classes/curlTest.php: -------------------------------------------------------------------------------- 1 | 8 | * @version 0.1 9 | * @license MIT 10 | */ 11 | class CurlTest extends BottleTestCase { 12 | private $url; 13 | 14 | public function setUp() 15 | { 16 | parent::setUp(); 17 | $this->url = $this->buildUrl('curl.php'); 18 | } 19 | 20 | public function testBasicRequest() { 21 | $req = send_request($this->url); 22 | $this->assertInternalType('array', $req); 23 | $this->assertEquals(200, $req['httpcode']); 24 | $this->assertEquals('Basic request success', $req['content']); 25 | } 26 | 27 | public function testGetRequest() { 28 | $req = send_request($this->url, 'GET', ['param' => 'value']); 29 | $this->assertEquals('GET:param=value'.PHP_EOL, $req['content']); 30 | 31 | $req = send_request($this->url, 'GET', ['param' => 'value', 'param2' => 'value2']); 32 | $this->assertEquals('GET:param=value'.PHP_EOL.'GET:param2=value2'.PHP_EOL, $req['content']); 33 | } 34 | 35 | public function testPostRequest() { 36 | $req = send_request($this->url, 'POST', ['param' => 'value']); 37 | $this->assertEquals('POST:param=value'.PHP_EOL, $req['content']); 38 | 39 | $req = send_request($this->url, 'POST', ['param' => 'value', 'param2' => 'value2']); 40 | $this->assertEquals('POST:param=value'.PHP_EOL.'POST:param2=value2'.PHP_EOL, $req['content']); 41 | } 42 | 43 | /** 44 | * @expectedException InvalidArgumentException 45 | */ 46 | public function testInvalidMethod() { 47 | $req = send_request($this->url, 'FAIL', ['param' => 'value']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/classes/viewTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @version 0.1 12 | * @license MIT 13 | */ 14 | class ViewTest extends PHPUnit_Framework_TestCase 15 | { 16 | public function testCreate() 17 | { 18 | $view = new Bottle_View; 19 | $this->assertInstanceOf('Bottle_View', $view); 20 | } 21 | 22 | /** 23 | * @expectedException Bottle_Exception 24 | */ 25 | public function testCreateWithNonexistingFilename() 26 | { 27 | $someFile = 'this-is-some-filename.txt'; 28 | new Bottle_View($someFile); 29 | } 30 | 31 | public function testCreateWithExistingFilename() 32 | { 33 | $someFile = 'fixtures/views/simpleview.html'; 34 | $view = new Bottle_View($someFile); 35 | 36 | $output = $view->render(true); 37 | $this->assertEquals($output, 'test'); 38 | } 39 | 40 | /** 41 | * @expectedException Bottle_Exception 42 | */ 43 | public function testSetFilenameWithNonexistingFilename() 44 | { 45 | $someFile = 'this-is-some-filename.txt'; 46 | $view = new Bottle_View; 47 | 48 | $view->setFilename($someFile); 49 | } 50 | 51 | public function testSetFilenameWithExistingFilename() 52 | { 53 | $someFile = 'fixtures/views/simpleview.html'; 54 | $view = new Bottle_View; 55 | $view->setFilename($someFile); 56 | 57 | $output = $view->render(true); 58 | $this->assertEquals($output, 'test'); 59 | } 60 | 61 | public function testSetRoutes() 62 | { 63 | $data = [ 64 | [null, []], 65 | [[], []], 66 | ['abcd', ['abcd']], 67 | [[1, 2, 3, 5], [1, 2, 3, 5]], 68 | [['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd']], 69 | ]; 70 | 71 | $view = new Bottle_View; 72 | foreach ($data as $item) { 73 | list($src, $dest) = $item; 74 | 75 | $view->setRoutes($src); 76 | $this->assertEquals($dest, $view->routes); 77 | } 78 | } 79 | 80 | public function testBindAndRender() 81 | { 82 | $view = new Bottle_View; 83 | 84 | $data = [ 85 | [ 86 | 'fixtures/views/varview1.html', 87 | 'test', 88 | 'test', 89 | ], 90 | [ 91 | 'fixtures/views/varview1.html', 92 | 'asd', 93 | 'asd', 94 | ], 95 | [ 96 | 'fixtures/views/varview2.html', 97 | 'test', 98 | 'test:test:test', 99 | ], 100 | [ 101 | 'fixtures/views/varview2.html', 102 | 'asd', 103 | 'test:asd:test', 104 | ], 105 | [ 106 | 'fixtures/views/varview3.html', 107 | 'test', 108 | 'test:test', 109 | ], 110 | [ 111 | 'fixtures/views/varview3.html', 112 | 'asd', 113 | 'asd:asd', 114 | ], 115 | ]; 116 | 117 | foreach ($data as $item) { 118 | list($filename, $src, $dest) = $item; 119 | 120 | $view->setFilename($filename); 121 | $view->bind(['test' => $src]); 122 | 123 | $output = $view->render(true); 124 | 125 | $this->assertEquals($dest, $output); 126 | } 127 | 128 | $data = [ 129 | [ 130 | ['testOne' => 'asd', 'testTwo' => 'test'], 131 | 'asd:test', 132 | ], 133 | [ 134 | ['testOne' => 'test', 'testTwo' => 'asd'], 135 | 'test:asd', 136 | ], 137 | ]; 138 | 139 | $view->setFilename('fixtures/views/varview4.html'); 140 | foreach ($data as $item) { 141 | list($vars, $dest) = $item; 142 | $view->bind($vars); 143 | $output = $view->render(true); 144 | 145 | $this->assertEquals($dest, $output); 146 | } 147 | } 148 | 149 | // /** 150 | // * @expectedException PHPUnit_Framework_Error 151 | // */ 152 | // public function testNotDefinedVariables() 153 | // { 154 | // $view = new Bottle_View('fixtures/views/varview1.html'); 155 | // $view->render(true); 156 | // } 157 | 158 | public function testUrl() 159 | { 160 | $data = [ 161 | [null, null, '/'], 162 | ['main', [], '/'], 163 | ['asd', [], '/asd'], 164 | ['dummyMul', [], '/mul/42'], 165 | ['paramMul', ['num' => 42], '/mul/42'], 166 | ['restList', [], '/rest/data/:id'], 167 | ['restList', ['id' => null], '/rest/data'], 168 | ['restPage', ['id' => 12, 'page' => 1], '/rest/data/12/1'], 169 | ['restPage', ['id' => 12], '/rest/data/12/:page'], 170 | ['restPage', ['page' => 12], '/rest/data/:id/12'], 171 | ]; 172 | 173 | $routes = [ 174 | 'main' => '/', 175 | 'dummyMul' => '/mul/42', 176 | 'paramMul' => '/mul/:num', 177 | 'restList' => '/rest/data/:id', 178 | 'restPage' => '/rest/data/:id/:page', 179 | ]; 180 | 181 | $view = new Bottle_View; 182 | $view->setRoutes($routes); 183 | 184 | foreach ($data as $item) { 185 | list($uri, $params, $dest) = $item; 186 | $url = $view->url($uri, $params); 187 | 188 | $this->assertEquals($dest, $url); 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /tests/fixtures/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteRule ^(.*)$ index.php [QSA,L] -------------------------------------------------------------------------------- /tests/fixtures/curl.php: -------------------------------------------------------------------------------- 1 | $v) { 7 | echo 'GET:'.$k.'='.$v.PHP_EOL; 8 | } 9 | foreach($_POST as $k => $v) { 10 | echo 'POST:'.$k.'='.$v.PHP_EOL; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/index.php: -------------------------------------------------------------------------------- 1 | 7 | * @version 0.1 8 | * @license MIT 9 | */ 10 | 11 | define('APPLICATION_PATH', realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR); 12 | 13 | // require '../../src/bottle.php'; 14 | require '../../build/bottle.phar'; 15 | 16 | /** 17 | * @route / 18 | */ 19 | function index() { 20 | return 'Success'; 21 | } 22 | 23 | /** 24 | * @route /param/:name 25 | */ 26 | function param($name) { 27 | return 'Param: '.$name; 28 | } 29 | 30 | /** 31 | * @route /view/:name 32 | * @view /views/view.html 33 | */ 34 | function view($name) { 35 | return ['name' => $name]; 36 | } 37 | 38 | /** 39 | * @route /exception 40 | */ 41 | function exception() { 42 | throw new Exception('You shouldn’t be here.'); 43 | } 44 | 45 | /** 46 | * @route /redirect 47 | * @view /views/view.html 48 | */ 49 | function redirect() { 50 | global $response; 51 | $response->redirect('redirected_function'); 52 | } 53 | 54 | /** 55 | * @route /redirected 56 | */ 57 | function redirected_function() { 58 | return 'Redirected'; 59 | } 60 | 61 | /** 62 | * @route /header 63 | */ 64 | function header_route() { 65 | global $response; 66 | $response->setHeader('Content-type', 'text/plain'); 67 | return 'Header'; 68 | } 69 | 70 | /** 71 | * @route /url 72 | * @view /views/view.html 73 | */ 74 | function url() { 75 | global $response; 76 | return $response->getView()->url('redirected_function'); 77 | } 78 | 79 | /** 80 | * @route /url2 81 | * @view /views/view.html 82 | */ 83 | function url2() { 84 | global $response; 85 | return $response->getView()->url('param', ['name' => 'name']); 86 | } 87 | 88 | /** 89 | * Conditions for the following controllers 90 | */ 91 | function true_condition($request) { 92 | return true; 93 | } 94 | 95 | function false_condition($request) { 96 | return false; 97 | } 98 | 99 | function arg_condition($request, $arg) { 100 | return $arg[0] == 'arg_0'; 101 | } 102 | 103 | function dyn_arg_condition($request, $arg) { 104 | return $arg[0] == 'url_param'; 105 | } 106 | 107 | /** 108 | * @route /restricted 109 | * @requires true_condition 110 | */ 111 | function restricted() { 112 | return 'OK'; 113 | } 114 | 115 | /** 116 | * @route /restricted2 117 | * @requires false_condition 118 | */ 119 | function restricted2() { 120 | return 'OK'; 121 | } 122 | 123 | /** 124 | * @route /restricted3 125 | * @requires arg_condition arg_0 126 | */ 127 | function restricted3() { 128 | return 'OK'; 129 | } 130 | 131 | /** 132 | * @route /restricted4 133 | * @requires arg_condition nope 134 | */ 135 | function restricted4() { 136 | return 'OK'; 137 | } 138 | 139 | /** 140 | * @route /restricted5/:value 141 | * @requires dyn_arg_condition $value 142 | */ 143 | function restricted5($value) { 144 | return 'OK'; 145 | } 146 | 147 | function global_context($request) { 148 | return ['var1' => 'global', 'var2' => 'global2']; 149 | } 150 | 151 | /** 152 | * @route /global-context 153 | * @view /views/global-context.html 154 | */ 155 | function global_context_controller() { 156 | return ['var2' => 'local', 'var3' => 'local2']; 157 | } 158 | -------------------------------------------------------------------------------- /tests/fixtures/subdir_with_éééé/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteRule ^(.*)$ index.php [QSA,L] -------------------------------------------------------------------------------- /tests/fixtures/subdir_with_éééé/index.php: -------------------------------------------------------------------------------- 1 | 7 | * @version 0.1 8 | * @license MIT 9 | */ 10 | 11 | define('APPLICATION_PATH', realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR); 12 | 13 | // require '../../../src/bottle.php'; 14 | require '../../../build/bottle.phar'; 15 | 16 | /** 17 | * @route / 18 | */ 19 | function index() { 20 | return 'Success'; 21 | } 22 | 23 | /** 24 | * @route /é 25 | */ 26 | function index2() { 27 | return 'Successé'; 28 | } 29 | 30 | /** 31 | * @route /' 32 | */ 33 | function index3() { 34 | return 'Success\''; 35 | } 36 | 37 | /** 38 | * @route / 39 | */ 40 | function index4() { 41 | return 'Success '; 42 | } 43 | -------------------------------------------------------------------------------- /tests/fixtures/views/global-context.html: -------------------------------------------------------------------------------- 1 | :: 2 | -------------------------------------------------------------------------------- /tests/fixtures/views/simpleview.html: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /tests/fixtures/views/varview1.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/views/varview2.html: -------------------------------------------------------------------------------- 1 | test::test -------------------------------------------------------------------------------- /tests/fixtures/views/varview3.html: -------------------------------------------------------------------------------- 1 | : -------------------------------------------------------------------------------- /tests/fixtures/views/varview4.html: -------------------------------------------------------------------------------- 1 | : -------------------------------------------------------------------------------- /tests/fixtures/views/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | view 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/utils.php: -------------------------------------------------------------------------------- 1 | assertTrue(false, 'cURL is not available. Passing.'); 19 | } 20 | if(!in_array($method, ['GET', 'POST'])) { 21 | throw new InvalidArgumentException('Method '.$method.' is not supported (yet?)'); 22 | } 23 | $ch = curl_init(); 24 | 25 | switch($method) { 26 | case 'GET': 27 | if(count($params)) { 28 | $params_formatted = []; 29 | foreach($params as $k => $param) { 30 | $params_formatted[] = urlencode($k).'='.urlencode($param); 31 | } 32 | $url .= '?'.implode('&', $params_formatted); 33 | } 34 | break; 35 | case 'POST': 36 | curl_setopt($ch,CURLOPT_POST,true); 37 | curl_setopt($ch,CURLOPT_POSTFIELDS,$params); 38 | break; 39 | } 40 | 41 | curl_setopt($ch, CURLOPT_URL, $url); 42 | curl_setopt($ch, CURLOPT_HEADER, true); 43 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 44 | 45 | foreach ($options as $key => $value) { 46 | curl_setopt($ch, $key, $value); 47 | } 48 | 49 | $response = curl_exec($ch); 50 | $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 51 | list($headers_content, $response_content) = explode("\r\n\r\n", $response); 52 | $headers_raw = explode("\r\n", $headers_content); 53 | $headers = []; 54 | foreach($headers_raw as $header) { 55 | if(strpos($header, ':') !== false) { 56 | list($k, $v) = explode(':', $header); 57 | $headers[$k] = trim($v); 58 | } 59 | } 60 | 61 | curl_close($ch); 62 | return ['httpcode' => $httpcode, 'headers' => $headers, 'content' => $response_content]; 63 | } 64 | -------------------------------------------------------------------------------- /views/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all 2 | -------------------------------------------------------------------------------- /views/mul.php: -------------------------------------------------------------------------------- 1 | The result is 2 | -------------------------------------------------------------------------------- /views/restricted.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bottle test 6 | 7 | 8 |

Bottle test

9 |

Status:

10 | 11 | 12 | 13 | --------------------------------------------------------------------------------