Created
January 6, 2025 10:41
-
-
Save AnnoyingTechnology/68316ef6ed31de032eb2aaa7be553d50 to your computer and use it in GitHub Desktop.
polyfony-readme.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<p><a href="https://insight.sensiolabs.com/projects/713fa5be-b3d6-4a10-b544-90ef45580ec0"><img src="https://insight.sensiolabs.com/projects/713fa5be-b3d6-4a10-b544-90ef45580ec0/mini.png" alt="SensioLabsInsight"></a> <a href="https://codeclimate.com/github/polyfony-inc/polyfony/maintainability"><img src="https://api.codeclimate.com/v1/badges/dcb85f03d218814504ac/maintainability" alt="Maintainability"></a></p> | |
<h2 id="polyfony-is-an-intuitive-light-and-powerful-php-micro-framework-">Polyfony is an intuitive, light and powerful PHP micro-framework.</h2> | |
<h4 id="philosophy">Philosophy</h4> | |
<p>Inspired by Symfony and Laravel but tailored to favour an inclination towards extreme simplicity and efficiency.<br>Compared to major PHP frameworks, Polyfony covers 95%+ of what we need most of the time, and does so using a fragment of the ressources, space, configuration files and dependencies required by major frameworks.<br>It features routing, bundles, controllers, profiler, views, ORM, tests, caches, locales, events, authentication, environments, form helper, CLI helper... and extensibility via composer.</p> | |
<h4 id="footprint-of-an-hello-world-https-github-com-polyfony-inc-polyfony-wiki-benchmark-">Footprint <a href="https://github.com/polyfony-inc/polyfony/wiki/Benchmark">of an Hello World</a></h4> | |
<ul> | |
<li>≤ 300 Ko of disk space <em>(35% of comment lines)</em></li> | |
<li>≤ 400 Ko of RAM</li> | |
<li>≤ 2.5 ms (cold)</li> | |
</ul> | |
<h2 id="requirements">Requirements</h2> | |
<p>You need a POSIX compatible system (Linux/MacOS/xBSD), PHP >= 7.4 with ext-pdo, ext-sqlite3, ext-mbstring, ext-msgpack and a rewrite module for your webserver. </p> | |
<h2 id="disclaimer">Disclaimer</h2> | |
<p>If you are considering using this framework instead of a major and well supported framework such as Laravel, there is something very wrong with your decision making process and/or project assesment. </p> | |
<p>In almost every case, using mainstream framework would be a better choice.</p> | |
<h2 id="installation">Installation</h2> | |
<h6 id="to-download-preconfigure-the-framework-in-your-project-folder-">To download & preconfigure the framework in <em>your-project-folder</em></h6> | |
<pre><code><span class="hljs-string">composer </span><span class="hljs-built_in">create-project</span> <span class="hljs-built_in">--stability=dev</span> <span class="hljs-string">polyfony-inc/</span><span class="hljs-string">polyfony </span><span class="hljs-string">your-project-</span><span class="hljs-string">folder</span> | |
</code></pre><p><em>--stability=dev is mandatory since we don't publish releases yet</em> | |
Pretty much all the dependencies that get installed by composer are only required by PHPUnit.</p> | |
<h6 id="nginx-configuration-https-github-com-annoyingtechnology-nginx-configuration-template-blob-master-conf-d-services-polyfony2-conf-">NginX <a href="https://github.com/AnnoyingTechnology/nginx-configuration-template/blob/master/conf.d/services/polyfony2.conf">configuration</a></h6> | |
<pre><code>root /<span class="hljs-built_in">var</span>/www/your<span class="hljs-params">-project</span><span class="hljs-params">-folder</span>/<span class="hljs-keyword">Public</span> | |
location / { | |
try_files $uri $uri/ /index.php?$query_string; | |
} | |
</code></pre><h6 id="lighttpd-configuration">LigHTTPd configuration</h6> | |
<pre><code>server<span class="hljs-selector-class">.document-root</span> = <span class="hljs-string">"/var/www/your-project-folder/Public/"</span> | |
url<span class="hljs-selector-class">.rewrite-once</span> = (<span class="hljs-string">"^(?!/Assets/).*"</span> => <span class="hljs-string">"/?"</span>) | |
</code></pre><h6 id="apache-configuration">Apache configuration</h6> | |
<pre><code><span class="hljs-attribute"><span class="hljs-nomarkup">DocumentRoot</span></span> <span class="hljs-string">"/var/www/your-project-folder/Public/"</span> | |
</code></pre><h2 id="almost-no-learning-required">Almost no learning required</h2> | |
<p>This <em>readme.md</em> file should be enough to get you started, you can also browse the <code>Private/Bundles/Demo/</code> bundle and browse the Framework's source code. | |
As the framework classes are static, everything is <strong>always available, everywhere</strong> thru simple and natural naming.</p> | |
<p><em>The code bellow assumes you are prefixing the <code>Polyfony</code> namespace before each call.</em></p> | |
<h3 id="-request-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyrequest-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyrequest">Request</a></h3> | |
<pre><code class="lang-php">// retrieve an url parameter | |
Request::get('blog_post_id'); | |
// retrieve a posted field named `search_expression` | |
Request::post('search_expression'); | |
// retrieve a posted file | |
Request::files('attachment_document'); | |
// retrieve a request header | |
Request::header('Accept-Encoding'); | |
// retrieve the user agent | |
Request::server('HTTP_USER_AGENT'); | |
//<span class="hljs-built_in"> check </span>if the method is post (returns a boolean) | |
Request::isPost(); | |
//<span class="hljs-built_in"> check </span>if the request is done using ajax (returns a boolean) | |
Request::isAjax(); | |
//<span class="hljs-built_in"> check </span>if the request is done thru TLS/SSL (returns a boolean) | |
Request::isSecure(); | |
//<span class="hljs-built_in"> check </span>if the request is from the command line (returns a boolean) | |
Request::isCli(); | |
</code></pre> | |
<h3 id="-database-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyquery-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyquery">Database</a></h3> | |
<p>Polyfony provides self-aware entities, in a similar way to an <em>ActiveRecord</em> (RubyOnRails) or an <em>Eloquent</em> object.</p> | |
<p>Examples bellow assume a table named <code>Pages</code> exists in the database. | |
The <code>Models\Pages</code> file has the following minimum amount of code.</p> | |
<pre><code class="lang-php">namespace <span class="hljs-type">Models</span>; | |
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Pages</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">\Polyfony\Entity</span> </span>{} | |
</code></pre> | |
<pre><code class="lang-php"><span class="hljs-comment">// Retrieve a database entity by its ID, here, the id 67</span> | |
$webpage = <span class="hljs-keyword">new</span> Pages(<span class="hljs-number">67</span>); | |
<span class="hljs-comment">// Retrieve another database entity by its `url` column</span> | |
$webpage = <span class="hljs-keyword">new</span> Pages([<span class="hljs-string">'url'</span>=><span class="hljs-string">'/my-awesome-vegan-burger-recipe/'</span>]); | |
<span class="hljs-comment">// Retrieve a single Entity by its ID and generate an input to change its title property, with a custom css class</span> | |
<span class="hljs-comment">// note that any html in the title field will be escaped in the <input> to prevent XSS</span> | |
<span class="hljs-comment">// returns <input type="etextmail" name="Pages[title]" value="My awesome Vegan burger recipe is so yummy" /></span> | |
(<span class="hljs-keyword">new</span> Pages(<span class="hljs-number">67</span>)) | |
->input(<span class="hljs-string">'title'</span>, [<span class="hljs-string">'class'</span>=><span class="hljs-string">'form-control'</span>]); | |
<span class="hljs-comment">// Create an new page, populate and save it</span> | |
(<span class="hljs-keyword">new</span> Pages) | |
->set([ | |
<span class="hljs-string">'url'</span> => <span class="hljs-string">'/veganaise-c-est-comme-de-la-mayonnaise-mais-vegan/'</span>, | |
<span class="hljs-string">'title'</span> => <span class="hljs-string">'I\'m running out of ideas...'</span>, | |
<span class="hljs-string">'description'</span> => <span class="hljs-string">'Meta descriptions get less and less important with Google\'s newest algorithms'</span>, | |
<span class="hljs-string">'creation_date'</span> => <span class="hljs-string">'18/04/1995'</span>, <span class="hljs-comment">// this gets converted to a unixepoch automagically</span> | |
<span class="hljs-string">'modification_date'</span> => time(), | |
<span class="hljs-string">'contents'</span> => <span class="hljs-string">'Meh...'</span>, | |
<span class="hljs-string">'categories_array'</span> => [<span class="hljs-string">'Cooking'</span>, <span class="hljs-string">'Organic'</span>], <span class="hljs-comment">// this get's saved as json automagically</span> | |
<span class="hljs-string">'id_creator'</span> => Security::getAccount()?->get(<span class="hljs-string">'id'</span>) <span class="hljs-comment">// assuming you are logged in</span> | |
]) | |
->save(); | |
<span class="hljs-comment">// Alternatively, you can also create an entity this way</span> | |
Pages::create([ | |
<span class="hljs-string">'url'</span> =><span class="hljs-string">'...'</span>, | |
<span class="hljs-string">'title'</span> =><span class="hljs-string">'...'</span>, | |
<span class="hljs-comment">// more columns and values...</span> | |
]); | |
<span class="hljs-comment">// Retrieve the `title` and `id` of 5 pages that have the `Organic` category, </span> | |
<span class="hljs-comment">// that have been modified in the last week, </span> | |
<span class="hljs-comment">// and that have been created by user's id 7.</span> | |
$pages = Pages::_select([<span class="hljs-string">'title'</span>,<span class="hljs-string">'id'</span>]) | |
->whereContains([<span class="hljs-string">'categories_array'</span>=><span class="hljs-string">'Organic'</span>]) | |
->whereGreaterThan(<span class="hljs-string">'modification_date'</span>, time() - <span class="hljs-number">7</span>*<span class="hljs-number">24</span>*<span class="hljs-number">3600</span>) | |
->where([<span class="hljs-string">'id_creator'</span>=><span class="hljs-number">7</span>]) | |
->limitTo(<span class="hljs-number">0</span>,<span class="hljs-number">5</span>) | |
->execute(); | |
</code></pre> | |
<h4 id="parameters">Parameters</h4> | |
<pre><code class="lang-php">-<span class="ruby">>where() /<span class="hljs-regexp">/ == $value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereNot() /</span><span class="hljs-regexp">/ <> $value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereBetween() /</span><span class="hljs-regexp">/ BETWEEN $min_value AND $max_value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereMatch() /</span><span class="hljs-regexp">/ MATCH column AGAINST $value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereContains() /</span><span class="hljs-regexp">/ % $value % | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereEndsWith() /</span><span class="hljs-regexp">/ % $value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereStartsWith() /</span><span class="hljs-regexp">/ $value % | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereNotEmpty() /</span><span class="hljs-regexp">/ <> '' and NOT NULL | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereEmpty() /</span><span class="hljs-regexp">/ '' or NULL | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereNotNull() /</span><span class="hljs-regexp">/ NOT NULL | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereNull() /</span><span class="hljs-regexp">/ NULL | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereGreaterThan() /</span><span class="hljs-regexp">/ < $value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereLessThan() /</span><span class="hljs-regexp">/ > $value | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereIn() /</span><span class="hljs-regexp">/ IN ( values ) | |
</span></span>-<span class="ruby"><span class="hljs-regexp">>whereNotIn() /</span><span class="hljs-regexp">/ NOT IN ( values )</span></span> | |
</code></pre> | |
<h4 id="options">Options</h4> | |
<pre><code class="lang-php">->orderBy() // associative<span class="hljs-built_in"> array </span>('column'=>'ASC') | |
->limitTo() // $start, $end | |
->groupBy() // ? | |
->first() //<span class="hljs-built_in"> return </span>the first Entity instead of an<span class="hljs-built_in"> array </span>of Entities | |
</code></pre> | |
<h4 id="magic-columns">Magic columns</h4> | |
<ul> | |
<li>Columns ending with <code>_date</code>, <code>_on</code>, <code>_at</code> will be converted from <code>DD/MM/YYYY</code> to a timestamp and vice-versa</li> | |
<li>Columns ending with <code>_datetime</code> will be converted from <code>DD/MM/YYYY HH:mm</code> to a timestamp and vice-versa</li> | |
<li>Columns ending with <code>_array</code> will be converted and stored as json, then restored to their original type</li> | |
<li>Columns ending with <code>_size</code> will be converted from bytes to human readable size</li> | |
</ul> | |
<table> | |
<thead> | |
<tr> | |
<th style="text-align:center">Setters</th> | |
<th style="text-align:center">Stored as</th> | |
<th style="text-align:center">Getters</th> | |
<th style="text-align:center">var_dump</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td style="text-align:center">->set(['creation_date'=>'01/01/2018'])</td> | |
<td style="text-align:center">1514808000</td> | |
<td style="text-align:center">->get('creation_date')</td> | |
<td style="text-align:center">string '01/01/2018'</td> | |
</tr> | |
<tr> | |
<td style="text-align:center">->set(['creation_at'=>'01/01/2018'])</td> | |
<td style="text-align:center">1514808000</td> | |
<td style="text-align:center">->get('creation_at', true)</td> | |
<td style="text-align:center">string '1514808000'</td> | |
</tr> | |
<tr> | |
<td style="text-align:center">->set(['creation_on'=>'1514808000'])</td> | |
<td style="text-align:center">1514808000</td> | |
<td style="text-align:center">->get('creation_on')</td> | |
<td style="text-align:center">string '01/01/2018'</td> | |
</tr> | |
<tr> | |
<td style="text-align:center">->set(['creation_datetime'=>'1514808000'])</td> | |
<td style="text-align:center">1514808000</td> | |
<td style="text-align:center">->get('creation_datetime')</td> | |
<td style="text-align:center">string '01/01/2018 12:00'</td> | |
</tr> | |
<tr> | |
<td style="text-align:center">->set(['products_array'=>['apple','peach']])</td> | |
<td style="text-align:center">["apple","peach"]</td> | |
<td style="text-align:center">->get('products_array')</td> | |
<td style="text-align:center">array ['apple','peach']</td> | |
</tr> | |
<tr> | |
<td style="text-align:center">->set(['picture_size'=>'24938'])</td> | |
<td style="text-align:center">24938</td> | |
<td style="text-align:center">->get('picture_size')</td> | |
<td style="text-align:center">string '24.4 Ko'</td> | |
</tr> | |
<tr> | |
<td style="text-align:center">->set(['picture_size'=>'24938'])</td> | |
<td style="text-align:center">24938</td> | |
<td style="text-align:center">->get('picture_size',true)</td> | |
<td style="text-align:center">string '24938'</td> | |
</tr> | |
</tbody> | |
</table> | |
<p>You can easily add elements to the end of an <code>_array</code> column. | |
Assuming you have a <code>Process</code> <strong>object</strong>/table, which has a <code>events_array</code> <strong>attribute</strong>/column.</p> | |
<pre><code class="lang-php"><span class="hljs-comment">// create a new Process object</span> | |
(<span class="hljs-keyword">new</span> Process) | |
<span class="hljs-comment">// push an event into the events_array object</span> | |
->push(<span class="hljs-string">'events_array'</span>, [ | |
<span class="hljs-comment">// this array is arbitrary, you are free to push anything into the column</span> | |
<span class="hljs-string">'date'</span> =>time(), | |
<span class="hljs-string">'is_important'</span> =><span class="hljs-keyword">false</span>, | |
<span class="hljs-string">'message'</span> =><span class="hljs-string">'Something just happened !'</span> | |
]) | |
<span class="hljs-comment">// your can also ommit the _array, the framework will find the right column</span> | |
->push(<span class="hljs-string">'events'</span>, [ | |
<span class="hljs-comment">// this array is arbitrary, you are free to push anything into the column</span> | |
<span class="hljs-string">'date'</span> =>time(), | |
<span class="hljs-string">'is_important'</span> =><span class="hljs-keyword">true</span>, | |
<span class="hljs-string">'message'</span> =><span class="hljs-string">'Something dubious just occured !'</span> | |
]) | |
->save(); | |
</code></pre> | |
<h4 id="entities-accessors">Entities accessors</h4> | |
<p>Entites have basic <code>->set([$column=>$value])</code> and <code>->get($column, $bymypass_html_entities_protection=false)</code> methods. | |
In addition, there are </p> | |
<ul> | |
<li><code>->oset($associative_array, $columns_to_actualy_set)</code> "OnlySet", certain columns</li> | |
<li><code>->lget($column)</code> "LocalizedGet", will attempt to use a locale for the returned value</li> | |
<li><code>->tget($column, $length)</code> "TruncatedGet", will truncate a returned value exceeding the length</li> | |
</ul> | |
<h4 id="xss-protection">XSS Protection</h4> | |
<p>Invoking ->get() on any other columns will automatically escape special html symbols using PHP's <code>FILTER_SANITIZE_FULL_SPECIAL_CHARS</code> as to prevent XSS attacks. | |
In situation where you actually want the raw data from the database, add <code>true</code> as a second parameter as such <code>$object->get('column_name', true);</code> to retrieve the data "as is". | |
Calling Format::htmlSafe() anywhere in your code will provide you with the same escaping features. </p> | |
<h3 id="data-validators">Data validators</h3> | |
<p><strong>Data validation should be managed by the developer with <code>symfony/validator</code>, <code>respect/validation</code>, <code>wixel/gump</code>, or similar packages.</strong> | |
That being said, there is a very basic <em>(and optional)</em> built-in validator, to prevent corrupted data from entering the database while manipulating objects.</p> | |
<p>To enforce it, declare a <code>VALIDATORS</code> constant array in your model, each key being a column, and each value being a regex, an array of allowed values or a standard PHP filter name (ex. <code>FILTER_VALIDATE_IP</code>).</p> | |
<ul> | |
<li>Example</li> | |
</ul> | |
<pre><code class="lang-php"> | |
Models\Accounts extends Polyfony\Security\Accounts { | |
<span class="hljs-comment">// Normal model classes extend Polyfony\Entity. </span> | |
<span class="hljs-comment">// Accounts extends an intermediate (but transparent) class that adds authentication logic.</span> | |
<span class="hljs-keyword">const</span> IS_ENABLED = [ | |
<span class="hljs-number">0</span> =><span class="hljs-string">'No'</span>, | |
<span class="hljs-number">1</span> =><span class="hljs-string">'Yes'</span> | |
]; | |
<span class="hljs-keyword">const</span> VALIDATORS = [ | |
<span class="hljs-comment">// using PHP's built in validators</span> | |
<span class="hljs-string">'login'</span> =>FILTER_VALIDATE_EMAIL, | |
<span class="hljs-string">'last_login_origin'</span> =>FILTER_VALIDATE_IP, | |
<span class="hljs-string">'last_failure_origin'</span> =>FILTER_VALIDATE_IP, | |
<span class="hljs-comment">// using arrays</span> | |
<span class="hljs-string">'is_enabled'</span>=><span class="hljs-keyword">self</span>::IS_ENABLED, | |
<span class="hljs-string">'id_level'</span> =><span class="hljs-keyword">self</span>::ID_LEVEL | |
]; | |
} | |
</code></pre> | |
<p>The validation occurs when <code>->set()</code> is invoked and will throw exceptions. </p> | |
<p>Note that you don't have to include <code>NULL</code> or <code>EMPTY</code> values in your validators to allow them. <code>NULL/NOT NULL</code> are to be configured in your database, so that the framework knows which column can, and cannot be null.</p> | |
<p><strong>Please be aware that doing a batch ->update (aka : not using distinct objects/Entities) on a table will circumvent those validators. This will get fixed in a later version.</strong></p> | |
<h3 id="data-filtering">Data filtering</h3> | |
<p>Data filtering and sanitizing can be used <em>in addition</em> or <em>instead</em> of data validators. | |
While validators throw exception when invalid data is encountered, data filters will clean up the data, so that it matches the expected nature of said data. </p> | |
<p>To enforce data filtering, declare a <code>FILTERS</code> constant array in your model, each key being a column, and each value being a filter name, or an array of filters names that will be applied one after the other. </p> | |
<ul> | |
<li>Example </li> | |
</ul> | |
<pre><code class="lang-php"> | |
<span class="hljs-comment">// an imaginary group model, that represent a group of people</span> | |
Models\Groups <span class="hljs-keyword">extends</span> Polyfony\Entity { | |
<span class="hljs-keyword">const</span> FILTERS = [ | |
<span class="hljs-comment">// replaces , with a dot and removes everything except 0-9 + - .</span> | |
<span class="hljs-string">'group_monthly_allowance'</span> => <span class="hljs-string">'numeric'</span>, | |
<span class="hljs-comment">// trim spaces, removes any special chars and capitalize each words</span> | |
<span class="hljs-string">'group_name'</span> => [<span class="hljs-string">'trim'</span>,<span class="hljs-string">'text'</span>,<span class="hljs-string">'ucwords'</span>], | |
<span class="hljs-comment">// removes any special chars and capitalize each words</span> | |
<span class="hljs-string">'group_manager_name'</span> => [<span class="hljs-string">'text'</span>,<span class="hljs-string">'strtoupper'</span>], | |
<span class="hljs-comment">// cleanup an email address</span> | |
<span class="hljs-string">'group_manager_email'</span> => <span class="hljs-string">'email'</span> | |
]; | |
} | |
</code></pre> | |
<p>The filtering occurs when <code>->set()</code> is invoked, and after the validations (if any). </p> | |
<h5 id="list-of-available-filters">List of available filters</h5> | |
<table> | |
<thead> | |
<tr> | |
<th>Filter name</th> | |
<th>What that filter does</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>strtoupper</td> | |
<td>applies mb_strtoupper()</td> | |
</tr> | |
<tr> | |
<td>strtolower</td> | |
<td>applies mb_strtolower()</td> | |
</tr> | |
<tr> | |
<td>ucfirst</td> | |
<td>applies ucfirst()</td> | |
</tr> | |
<tr> | |
<td>ucwords</td> | |
<td>applies ucwords()</td> | |
</tr> | |
<tr> | |
<td>trim</td> | |
<td>applies trim()</td> | |
</tr> | |
<tr> | |
<td>numeric</td> | |
<td>replaces coma with dot then applies FILTER_SANITIZE_NUMBER_FLOAT</td> | |
</tr> | |
<tr> | |
<td>integer</td> | |
<td>applies FILTER_SANITIZE_NUMBER_INT</td> | |
</tr> | |
<tr> | |
<td>phone</td> | |
<td>removes anything but 0 to 9 the plus sign and parenthesis</td> | |
</tr> | |
<tr> | |
<td>email</td> | |
<td>applies FILTER_SANITIZE_EMAIL</td> | |
</tr> | |
<tr> | |
<td>text</td> | |
<td>replaces ' with ’ then removes < > & " \ /</td> | |
</tr> | |
<tr> | |
<td>name</td> | |
<td>removes anything but letters, space and ’</td> | |
</tr> | |
<tr> | |
<td>slug</td> | |
<td>applies Polyfony/Format::slug()</td> | |
</tr> | |
<tr> | |
<td>length{4-4096}</td> | |
<td>applies mb_substr()</td> | |
</tr> | |
<tr> | |
<td>capslock{30,50,70}</td> | |
<td>applies ucfirst(mb_strtolower()) if the uppercase ratio exceeds XX %</td> | |
</tr> | |
</tbody> | |
</table> | |
<p>The <code>capslock30</code>, <code>capslock50</code> and <code>capslock70</code> don't affect the data if it has a low enough uppercase ratio. | |
This filter allows for nicer and cleaner databases, it has been designed for older people who enjoy the <em>FUCK YEAH CAPS LOCK !!</em> lifestyle. </p> | |
<p><strong>An added benefit of using model's filters is that your inputs and textarea automatically get the right html attributes and types.</strong> </p> | |
<p>Check out the following Model, View and HTML output.</p> | |
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Polyfony\Entity</span> </span>{ | |
const <span class="hljs-type">FILTERS</span> = [ | |
<span class="hljs-symbol">'user_logi</span>n'=>[<span class="hljs-symbol">'emai</span>l',<span class="hljs-symbol">'length12</span>8'] | |
]; | |
} | |
</code></pre> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= (<span class="hljs-keyword">new</span> Models\Users->input(<span class="hljs-string">'user_login'</span>)); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<pre><code class="lang-html"><input name=<span class="hljs-string">"Users[user_login]"</span> <span class="hljs-built_in">type</span>=<span class="hljs-string">"email"</span> maxlength=<span class="hljs-string">"128"</span> <span class="hljs-built_in">value</span>=<span class="hljs-string">""</span> /> | |
</code></pre> | |
<p>Your input also gets a <code>required="required"</code> attribute if the column cannot be null. This is deduced from the database schema.</p> | |
<p><strong>Please be aware that doing a batch ->update (aka : not using distinct objects/Entities) on a table will circumvent those validators. This will get fixed in a later version.</strong></p> | |
<h3 id="data-auto-population">Data auto-population</h3> | |
<p>The framework will look for some common columns and try to populate them with either the unix epoch or the ID of the currently authenticated user.</p> | |
<p>Upon <code>Entity</code> <strong>creation</strong> : <code>creation_by</code>, <code>creation_date</code>, <code>created_by</code>, <code>created_at</code>, <code>creation_datetime</code></p> | |
<p>Upon <code>Entity</code> <strong>modification</strong> : <code>modification_by</code>, <code>modification_date</code>, <code>modified_by</code>, <code>modified_at</code></p> | |
<h3 id="-router-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyrouter-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyrouter">Router</a></h3> | |
<p><strong>A route maps an URL to an <code>Action</code>, which resides in a <code>Controller</code>, which resides in a <code>Bundle</code></strong><br>An <code>Action</code> is a <em>method</em>, a <code>Controller</code> is a <em>class</em>, a <code>Bundle</code> is a <em>folder</em>. | |
Routes are to be declared in each bundle's <code>Loader</code> directory, in a file called <code>Route.php</code></p> | |
<p><em>Example : <code>Private/Bundles/{BundleName}/Loader/Route.php</code></em></p> | |
<h6 id="routes-can-accept-a-number-of-parameters-and-lack-thereof">Routes can accept a number of parameters, and lack thereof</h6> | |
<ul> | |
<li><code>Router::map('/admin/:what/:id/', 'Bundle/Controller@{what}')</code>.</li> | |
<li><code>Router::map('/url/', 'Bundle/Controller@action')</code>.</li> | |
</ul> | |
<h6 id="the-action-can">The action can</h6> | |
<ul> | |
<li>be a parameter of the url (as with the first example. The action would be the 2nd parameter <code>{what}</code>)</li> | |
<li>be ommited. In that case an <code>->index()</code> is called. If it doesn't exist, <code>->default()</code> will be called, if it doesn't exist an exception is thrown.</li> | |
</ul> | |
<p>Note that as an additional safety measure, <em>Actions</em> can only contain alphanumerical characters, <code>-</code>, <code>_</code> and <code>.</code>.</p> | |
<p>Before calling the desired action a <code>->before()</code> method will be called on the controller. <em>You can declare one, or ommit it.</em><br>after the desired action has been be called, a <code>->after()</code> method will be called on the controller. <em>You can declare one, or ommit it.</em></p> | |
<ul> | |
<li>The following route will match a GET request to /about-us/ <pre><code class="lang-php">Router::<span class="hljs-keyword">get</span>(<span class="hljs-string">'/about-us/'</span>, <span class="hljs-string">'Pages/Static@aboutUs'</span>); | |
</code></pre> | |
</li> | |
</ul> | |
<p>It will call <code>Private/Bundles/Pages/Controllers/Static.php->aboutUs();</code></p> | |
<ul> | |
<li>The following route will match a request of any method (GET,POST...) to /admin/{edit,update,delete,create}/ and /admin/ <pre><code class="lang-php">Router::map(<span class="hljs-string">'/admin/:section/:id/'</span>, <span class="hljs-string">'Admin/Main@{section}'</span>) | |
->where([ | |
<span class="hljs-string">'section'</span>=>[ | |
<span class="hljs-comment">// section must be one of the following four</span> | |
<span class="hljs-comment">// though you can ommit this, a wrong section value will trigger a defaultAction() on the controller</span> | |
<span class="hljs-comment">// which means an exception if you have not declared it.</span> | |
<span class="hljs-string">'in_array'</span>=>[<span class="hljs-string">'edit'</span>,<span class="hljs-string">'update'</span>,<span class="hljs-string">'delete'</span>,<span class="hljs-string">'create'</span>] | |
], | |
<span class="hljs-string">'id'</span>=>[ | |
<span class="hljs-comment">// id has to be a numeric value</span> | |
<span class="hljs-string">'is_numeric'</span>, | |
<span class="hljs-comment">// id can't be 0</span> | |
<span class="hljs-string">'!in_array'</span>=>[<span class="hljs-number">0</span>] | |
] | |
]); | |
</code></pre> | |
</li> | |
</ul> | |
<p>It will call <code>Private/Bundles/Admin/Controllers/Main.php->{section}();</code></p> | |
<p><em>Route can also be generated dynamically, over database iterations.</em></p> | |
<pre><code class="lang-php"><span class="hljs-comment">// assuming you have a Pages table containing, well, pages. </span> | |
<span class="hljs-comment">// those would have an "url" column that define the absolute url of the page.</span> | |
<span class="hljs-comment">// you should replace the _select with a cachable method in the model though.</span> | |
<span class="hljs-keyword">foreach</span>(Pages::_select([<span class="hljs-string">'url'</span>])->execute() <span class="hljs-keyword">as</span> $page) { | |
<span class="hljs-comment">// map that url to a single "Pages" controller </span> | |
<span class="hljs-comment">// that resides in a "Site" bundle</span> | |
Router::get( | |
$page->get(<span class="hljs-string">'url'</span>), | |
<span class="hljs-string">'Site/Pages@view'</span> | |
); | |
} | |
</code></pre> | |
<p>then, in <code>Bundles/Site/Controllers/Pages.php</code> </p> | |
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PagesController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span> </span>{ | |
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">viewAction</span><span class="hljs-params">()</span> </span>{ | |
<span class="hljs-comment">// retrieve your webpage from the database</span> | |
<span class="hljs-comment">// using not its id, but its url column value !</span> | |
$page = <span class="hljs-keyword">new</span> Pages([<span class="hljs-string">'url'</span>=>Request::getUrl()]) | |
<span class="hljs-comment">// maybe set the title and description using that database object</span> | |
Response\HTML::set( | |
<span class="hljs-string">'metas'</span>=>[ | |
<span class="hljs-string">'title'</span> =>$page->get(<span class="hljs-string">'title'</span>), | |
<span class="hljs-string">'description'</span> =>$page->get(<span class="hljs-string">'description'</span>) | |
] | |
); | |
<span class="hljs-comment">// and pass your variables to the view</span> | |
<span class="hljs-keyword">$this</span>->view(<span class="hljs-string">'Pages/FromTheDatabase'</span>, [ | |
<span class="hljs-string">'title'</span> =>$page->get(<span class="hljs-string">'title'</span>), | |
<span class="hljs-string">'contents'</span> =>$page->get(<span class="hljs-string">'contents'</span>) | |
]); | |
} | |
} | |
</code></pre> | |
<h6 id="url-parameters-constraints">URL Parameters constraints</h6> | |
<ul> | |
<li>"in_array" => [allowed values]</li> | |
<li>"!in_array" => [disalowed values]</li> | |
<li>"preg_match" => "regex-to-match"</li> | |
<li>"!preg_match" => "regex-not-to-match"</li> | |
<li>"is_numeric"</li> | |
<li>"!is_numeric" | |
<em>If multiple constraints are declared, they all have to match.</em> </li> | |
</ul> | |
<h6 id="redirects-declaration">Redirects declaration</h6> | |
<ul> | |
<li>The following will redirect from <code>/some-old-url/</code> to <code>/the-new-url/</code> using a 301 status code.<pre><code class="lang-php"><span class="hljs-symbol">Router:</span><span class="hljs-symbol">:redirect</span>(<span class="hljs-string">'/some-old-url/'</span>, <span class="hljs-string">'/the-new-url/'</span>, [<span class="hljs-variable">$status_code</span>=<span class="hljs-number">301</span>]); | |
</code></pre> | |
<em>Those are static redirections, not rewrite rules. They cannot include dynamic parameters.</em></li> | |
</ul> | |
<h6 id="url-parameters-signing">URL Parameters signing</h6> | |
<p>Sometimes, you may want to share a link with someone who doesn't have an account on your software. | |
You may not want to bother those people with the requirements of having an account. | |
In those cases, signing the url parameters and passing the hash along is a good way of securing an URL. | |
The generated URL will be unique and unpredictable. </p> | |
<p>To require a route to be signed, apply the <code>->sign()</code> method to your route declaration.</p> | |
<pre><code class="lang-php"> | |
<span class="hljs-comment">// the associated signed URL can be sent by email</span> | |
<span class="hljs-comment">// the client would not have to log in to track their order, how comfortable</span> | |
Router::get( | |
<span class="hljs-string">'/track/shipping/:id_client/:id_order/'</span> | |
<span class="hljs-string">'External/Tracking@order'</span>, | |
<span class="hljs-string">'order-tracking'</span> | |
)->sign(); | |
</code></pre> | |
<p>To get a signed URL, use the <code>Router::reverse()</code> method, the generated URL will automatically be signed.</p> | |
<pre><code class="lang-php"> | |
<span class="hljs-comment">// this will generate an URL looking like this </span> | |
<span class="hljs-comment">// https://my.domain.com/track/shipping/4289/24389/E29E798A097F099827/</span> | |
Router::reverse( | |
<span class="hljs-string">'order-tracking'</span> | |
[ | |
<span class="hljs-string">'id_client'</span>=>$client->get(<span class="hljs-string">'id'</span>), | |
<span class="hljs-string">'id_order'</span>=>$order->get(<span class="hljs-string">'id'</span>) | |
], | |
<span class="hljs-keyword">true</span>, <span class="hljs-comment">// force TLS</span> | |
<span class="hljs-keyword">true</span> <span class="hljs-comment">// include domain name</span> | |
); | |
</code></pre> | |
<p>You can customize the name of the hash parameter in <code>Config.ini</code> then <code>[router]</code> then <code>signing_parameter_name = 'url_parameters_hash'</code>, so that it doesn't conflict with your named parameters.</p> | |
<h6 id="rate-limiting">Rate-limiting</h6> | |
<p>Throttling is by default based on the route name and the remote address (IP), you therefor must name your routes. | |
The mechanisms used is <a href="https://en.wikipedia.org/wiki/Leaky_bucket">Leaky Bucket</a>. </p> | |
<p>To throttle <strong>a route</strong></p> | |
<pre><code class="lang-php"> | |
Router::put( | |
<span class="hljs-string">'/place-order/:id_customer/'</span> | |
<span class="hljs-string">'External/Tracking@order'</span>, | |
<span class="hljs-string">'place-order'</span> | |
)->throttle(<span class="hljs-number">2</span>, <span class="hljs-number">3600</span>); <span class="hljs-comment">// enforces a limit of 2 requests every 3600 minutes (one hour, leaky bucket)</span> | |
</code></pre> | |
<p>To throttle <strong>manually</strong> (in a controller)</p> | |
<pre><code class="lang-php"> | |
<span class="hljs-comment">// you can use the method enforce (this limits to 5 per hour)</span> | |
Throttle<span class="hljs-type">::enforce</span>(<span class="hljs-number">5</span>, <span class="hljs-number">3600</span>); | |
<span class="hljs-comment">// method shortcuts (this limits to 2 per minute)</span> | |
Throttle<span class="hljs-type">::perMinute</span>(<span class="hljs-number">2</span>); | |
<span class="hljs-comment">//Throttle::perSecond();</span> | |
<span class="hljs-comment">//Throttle::perHour();</span> | |
<span class="hljs-comment">//Throttle::perDay();</span> | |
<span class="hljs-comment">// and even define your own keys (here, the lookup table will use a hash of id_client+IP instead of the IP address + route name)</span> | |
Throttle<span class="hljs-type">::perHour</span>( | |
<span class="hljs-number">2</span>, | |
Hashs<span class="hljs-type">::get</span>(<span class="hljs-meta">[</span> | |
$id_client, | |
Request<span class="hljs-type">::server</span>(<span class="hljs-string">'REMOTE_ADDR'</span>) | |
<span class="hljs-meta">]</span>) | |
); | |
</code></pre> | |
<p>When someone is being throttle, a <code>429</code> Exception is thrown with the message <em>You are being rate-limited</em>. | |
Note that </p> | |
<ul> | |
<li>new hits while being rate-limited will not extend a lock</li> | |
<li>there is not burst support</li> | |
<li>the backend used is APCu for performance and DoS safety reasons </li> | |
</ul> | |
<h3 id="environments">Environments</h3> | |
<p><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyconfig">https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyconfig</a></p> | |
<p>Environments characterize a context of execution, with their own set of variables. | |
<strong>Two environments exist in Polyfony</strong> </p> | |
<ul> | |
<li><code>Dev</code>, the development environment (this is where your coding occurs, most likely on your local developement server, or your own computer), </li> | |
<li><code>Prod</code>, the production environment (also referred to as <code>Live</code>).</li> | |
</ul> | |
<p>Variables that are common to both environments should be put in the main configuration file <code>Private/Config/Config.ini</code> | |
The environment detection can be based on either : </p> | |
<ul> | |
<li>the domain name </li> | |
<li>the port. </li> | |
</ul> | |
<p>You can chose the detection method in <code>Config.ini</code> </p> | |
<pre><code><span class="hljs-section">[polyfony]</span> | |
<span class="hljs-attr">detection_method</span> = <span class="hljs-string">"domain"</span> ; or <span class="hljs-string">"port"</span> | |
</code></pre><p>Depending on the detected environment, either </p> | |
<ul> | |
<li><code>Private/Config/Dev.ini</code> or </li> | |
<li><code>Private/Config/Prod.ini</code> | |
will overload/merge with the main <code>Config.ini</code></li> | |
</ul> | |
<p>Contrary to many frameworks, your development application folder and production folder are strictly identical. You do not need to use different .env files on your production server. </p> | |
<h6 id="bellow-is-a-sample-dev-ini-with-its-development-domain">Bellow is a sample <code>Dev.ini</code> with its development domain</h6> | |
<pre><code>Private/Config/Dev<span class="hljs-selector-class">.ini</span> | |
[router] | |
domain = project<span class="hljs-selector-class">.company</span><span class="hljs-selector-class">.tld</span><span class="hljs-selector-class">.test</span> | |
port = <span class="hljs-number">80</span> | |
</code></pre><h6 id="and-a-sample-prod-ini-with-its-production-domain">And a sample <code>Prod.ini</code> with its production domain</h6> | |
<p><em>The framework falls back to production if neither domain or port are matched</em></p> | |
<pre><code>Private/Config/Prod.ini | |
[router] | |
domain = project.company.tld | |
port <span class="hljs-number">80</span> | |
[response] | |
minify = <span class="hljs-number">1</span> | |
compress = <span class="hljs-number">1</span> | |
cache = <span class="hljs-number">1</span> | |
pack_js = <span class="hljs-number">1</span> | |
pack_css = <span class="hljs-number">1</span> | |
</code></pre><p><em>Default configurations files with ready-to-go settings are put in place by composer during installation</em></p> | |
<p>You will need to modify your <code>/etc/hosts</code> file to point <code>project.company.tld.test</code> to <code>127.0.0.1</code> or modify your local DNS server.</p> | |
<h6 id="to-retrieve-configurations-values-from-the-merged-configurations-files-">To retrieve configurations values (from the merged configurations files)</h6> | |
<pre><code class="lang-php"><span class="hljs-comment">// retrieve the whole 'response' group</span> | |
Config::<span class="hljs-keyword">get</span>(<span class="hljs-string">'response'</span>); | |
<span class="hljs-comment">// retrieve only a key from that group</span> | |
Config::<span class="hljs-keyword">get</span>(<span class="hljs-string">'response'</span>, <span class="hljs-string">'minify'</span>); | |
</code></pre> | |
<p>Having distinct configuration files allows you to :</p> | |
<ul> | |
<li>set a bypass email to catch all emails sent in development environment</li> | |
<li>enable compression, obfuscation/minifying and caching only in production</li> | |
<li>show the profiler in development (and even, in the early production stage if needed)</li> | |
<li>use different database configuration</li> | |
<li>harden security parameters in production while allowing softer settings during local tests</li> | |
<li>etc.</li> | |
</ul> | |
<h3 id="-security-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonysecurity-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonysecurity">Security</a></h3> | |
<p>The security is based around on a common email/password couple. | |
Passwords are strongly hashed and salted before storage in the database. The hash algorithm can be tweaked using the <code>algo</code> (default is <code>sha512</code>) and <code>salt</code> parameters.</p> | |
<p>Users can have any number of roles <code>AccountsRoles</code> and permissions <code>AccountsPermissions</code> directly assigned to them. | |
Roles themselves can have permissions assigned to them. Users will inherit permissions from their roles. | |
Permissions must be grouped into at least one logical group <code>AccountsPermissionsGroups</code>.</p> | |
<p>In addition, two mechanisms tighten the security : </p> | |
<ul> | |
<li>a throttling mechanism preventing bruteforce attacks. It can be tweaked using <code>forcing_timeframe</code> and <code>forcing_maximum_attempts</code> parameters.</li> | |
<li>an anti cookie-theft mechanism, checking that the user that initially logged in is still the same one. Even if he/she has the correct session cookie in his/her possession. This can be disabled by changing the <code>enable_signature_verification</code> parameter (default is <code>1</code> - enabled)</li> | |
</ul> | |
<p>You can disable the cookie theft protection on a per-request basis. | |
By chaging the configuration on the fly <code>Config::set('security','enable_signature_verification', 0)</code> which can be useful in very specific cases</p> | |
<h6 id="to-secure-a-page-require-a-user-to-be-logged-in-">To secure a page (require a user to be logged in)</h6> | |
<pre><code class="lang-php">Security::authenticate(); | |
</code></pre> | |
<p>Failure to authenticate will throw an exception with a <code>403</code> Status Code, and redirect to <code>Private/Config/Config.ini</code> -> <code>[router]</code> -> <code>login_route = ""</code></p> | |
<h6 id="if-a-page-or-action-requires-a-specific-permission">If a page or action requires a specific permission</h6> | |
<pre><code class="lang-php">Security::denyUnlessHasPermission(<span class="hljs-symbol">'permission_name</span> <span class="hljs-keyword">or</span> permission_id <span class="hljs-keyword">or</span> permission <span class="hljs-keyword">entity</span>'); | |
</code></pre> | |
<p>Failure to comply with those requirements will throw an exception, but won't redirect the user anywhere.</p> | |
<h6 id="to-check-manually-for-credentials">To check manually for credentials</h6> | |
<pre><code class="lang-php">Security::getAccount() | |
->hasPermission( | |
$permission_id <span class="hljs-comment">// or permission_name or permission entity</span> | |
); | |
Security::getAccount() | |
->hasRole( | |
$role_id <span class="hljs-comment">// // or role_name or role entity</span> | |
); | |
</code></pre> | |
<h6 id="opportunistic-authentication">Opportunistic authentication</h6> | |
<p>In some cases, you might use signed URLs for anonymous users but still want to authenticate users who are already logged in.</p> | |
<p>To achieve this, use <code>Security::authenticate(opportunistic: true)</code> <strong>in addition</strong> to the signed URLs. If the user has an active session, they will be authenticated; if not, no further action will occur, and script execution will continue as usual.</p> | |
<p>Note that this approach does <strong>not</strong> enforce security—it will neither deny access nor throw an exception. It simply attempts to authenticate the user <strong>if possible</strong>.</p> | |
<h3 id="-profiler-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyprofiler-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyprofiler">Profiler</a></h3> | |
<p>Set markers arounds heavy code blocks to estimate the time and memory impact of that block.</p> | |
<pre><code class="lang-php">Profiler::setMarker(<span class="hljs-string">'ClueA.subclue1'</span>); | |
Profiler::releaseMarker(<span class="hljs-string">'ClueA.subclue1'</span>) | |
</code></pre> | |
<p>If the <code>Config::get('profiler', 'enable')</code> if set to <code>true (1)</code> and your <code>Response</code> is of type <code>html</code>, you will see a nice bar at the bottom of the page, with lots of useful informations. | |
That bar depends on bootstrap 4 CSS and JS. Be sure to add those to your assets to enjoy the bull benefits of the Profiler. | |
By default, some markers are placed in key places (around every <code>Database</code> queries, around Controller forwarding...).</p> | |
<p>If your <code>Response</code> is of type <code>json</code>, then the <code>Profiler</code> ìnformations will be merged with your <code>Response</code> as an array.</p> | |
<p><img src="https://i.imgur.com/DDrZqBu.png" alt="Profiler Demo1"></p> | |
<p><img src="https://i.imgur.com/x7EyKMF.png" alt="Profiler Demo2"></p> | |
<p><img src="https://i.imgur.com/DClz03j.png" alt="Profiler Demo3"></p> | |
<h3 id="-locales-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonylocales-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonylocales">Locales</a></h3> | |
<p>Locales are stored in csv files (tab + double-quotes), stored in each bundle in the <code>Bundles/MyBundle/Locales/</code> folder. | |
The files are parsed the first time you ask for a locale. The language is automatically detected using the browser's language, you can set it manually.</p> | |
<h6 id="retrieve-a-locale-in-the-current-language-auto-detection-">Retrieve a locale in the current language (auto-detection)</h6> | |
<pre><code class="lang-php">Locales::<span class="hljs-keyword">get</span>($key) | |
</code></pre> | |
<h6 id="retrieve-a-locale-in-a-different-languague">Retrieve a locale in a different languague</h6> | |
<pre><code class="lang-php"><span class="hljs-symbol">Locales:</span><span class="hljs-symbol">:get</span>(<span class="hljs-variable">$key</span>, <span class="hljs-variable">$language</span>) | |
</code></pre> | |
<h6 id="set-the-language-it-is-memorized-in-a-cookie-for-a-month-">Set the language (it is memorized in a cookie for a month)</h6> | |
<pre><code class="lang-php">Locales::<span class="hljs-built_in">set</span>Languague(<span class="hljs-variable">$language</span>) | |
</code></pre> | |
<h3 id="-exception-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyexception-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyexception">Exception</a></h3> | |
<p>Exception are routed to a route named « exception » if any, otherwise exception are thrown normally. | |
The status code is 500 by default, you can specify any HTTP status code. The cache is disabled by such a status code.</p> | |
<pre><code class="lang-php"><span class="hljs-keyword">Throw</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">Exception</span>($error_message, $http_status_code); | |
</code></pre> | |
<h3 id="-response-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyresponse-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyresponse">Response</a></h3> | |
<p>The response if preconfigured according to the Config.ini to render an full HTML page (body, header, metas,, stylesheets, etc.) | |
You can alter the response type and parameters at runtime.</p> | |
<p>Anything that is common to <strong>all</strong> responses, you will find in <code>Response</code>. | |
<strong>As <code>links</code>, <code>scripts</code>, and <code>metas</code> are specific to HTML <code>Response</code>s, those are set in the subnamespace <code>Response\HTML</code></strong> </p> | |
<h6 id="to-redirect">To redirect</h6> | |
<pre><code class="lang-php">Response::<span class="hljs-built_in">set</span>Redirect(<span class="hljs-variable">$url</span> [, <span class="hljs-variable">$after_seconds</span>=0]) | |
</code></pre> | |
<h6 id="to-redirect-to-the-previous-page">To redirect to the previous page</h6> | |
<pre><code class="lang-php">Response::previous() | |
</code></pre> | |
<h6 id="to-change-the-charset">to change the charset</h6> | |
<pre><code class="lang-php">Response::<span class="hljs-built_in">set</span>Charset(<span class="hljs-string">'utf-8'</span>) | |
</code></pre> | |
<h6 id="to-output-a-file-inline">to output a file inline</h6> | |
<pre><code class="lang-php"><span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setType</span>(<span class="hljs-string">'file'</span>) | |
<span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setContent</span>(<span class="hljs-variable">$file_path</span>) | |
<span class="hljs-symbol">Response:</span><span class="hljs-symbol">:render</span>() | |
</code></pre> | |
<h6 id="to-download-a-file">to download a file</h6> | |
<pre><code class="lang-php"><span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setType</span>(<span class="hljs-string">'file'</span>) | |
<span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setContent</span>(<span class="hljs-variable">$file_path</span>) | |
<span class="hljs-symbol">Response:</span><span class="hljs-symbol">:download</span>(<span class="hljs-string">'Myfilename.ext'</span>[, <span class="hljs-variable">$force_download</span>=<span class="hljs-keyword">false</span>]) | |
</code></pre> | |
<h6 id="to-change-the-status-code-to-400-bad-request-for-example-">to change the status code (to 400 Bad Request for example)</h6> | |
<p><em>Doing that will prevent the response from being cached. Only 200 status can be cached.</em></p> | |
<pre><code class="lang-php">Response::setStatus(<span class="hljs-number">400</span>) | |
</code></pre> | |
<h6 id="to-output-plaintext">to output plaintext</h6> | |
<pre><code class="lang-php">Response::<span class="hljs-built_in">set</span>Type(<span class="hljs-string">'text'</span>) | |
</code></pre> | |
<h6 id="to-output-html">to output html</h6> | |
<p>This outputs only the HTML you build in your view.</p> | |
<pre><code class="lang-php">Response::<span class="hljs-built_in">set</span>Type(<span class="hljs-string">'html'</span>) | |
</code></pre> | |
<p>This builds a page for you, including <code><header></code>, <code><style></code>, <code><script></code>, <code><title></code> metas. | |
The HTML you build is placed in the <code><body></code> of the built page.</p> | |
<pre><code class="lang-php">Response::<span class="hljs-built_in">set</span>Type(<span class="hljs-string">'html'</span>) | |
</code></pre> | |
<p><em>Note that changing <code>setType</code> cleans any buffered output</em>.</p> | |
<h6 id="to-output-json">to output json</h6> | |
<pre><code class="lang-php">Response::setType(<span class="hljs-string">'json'</span>) | |
Response::setContent([<span class="hljs-string">'example'</span>]) | |
Response::render() | |
</code></pre> | |
<h6 id="to-add-css-files-and-headers-links">to add css files and headers links</h6> | |
<pre><code class="lang-php">Response\HTML::<span class="hljs-built_in">set</span>Links([<span class="hljs-string">'//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'</span>]) | |
</code></pre> | |
<p>You can also specify optional attributes, such as <code>media</code></p> | |
<pre><code class="lang-php">Response\HTML::setLinks([ | |
<span class="hljs-string">'//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'</span>=>[ | |
<span class="hljs-string">'media'</span>=><span class="hljs-string">'screen'</span> | |
] | |
]) | |
</code></pre> | |
<p>Or even set totaly different types of links, such as a favicon</p> | |
<pre><code class="lang-php">Response\HTML::setLinks([ | |
<span class="hljs-string">'/Assets/Shared/Svg/Favicon.svg'</span>=>[ | |
<span class="hljs-string">'rel'</span> =><span class="hljs-string">'icon'</span>, | |
<span class="hljs-string">'sizes'</span> =><span class="hljs-string">'any'</span>, | |
<span class="hljs-string">'type'</span> =><span class="hljs-string">'image/svg+xml'</span> | |
] | |
]) | |
</code></pre> | |
<h6 id="to-add-js-files-local-or-remote-">to add js files (local or remote)</h6> | |
<pre><code class="lang-php">Response\HTML::setScripts([ | |
<span class="hljs-string">'//code.jquery.com/jquery-3.3.1.slim.min.js'</span> | |
<span class="hljs-string">'Shared/myfile.js'</span> <span class="hljs-comment">// this will import directly from the Bundle/Shared/Assets/Js folder</span> | |
]) | |
</code></pre> | |
<h6 id="to-add-a-meta-tag">to add a meta tag</h6> | |
<pre><code class="lang-php"><span class="hljs-type">Response</span>\<span class="hljs-type">HTML</span>::setMetas([<span class="hljs-symbol">'google</span>-site-verification'=><span class="hljs-symbol">'google</span>-is-watching-you']) | |
</code></pre> | |
<h6 id="to-manually-push-and-preload-assets-using-http-2-feature">to manually push and preload assets using HTTP/2 feature</h6> | |
<pre><code class="lang-php">Response::push([ | |
<span class="hljs-string">'/Assets/Css/Shared/common.css'</span> =><span class="hljs-string">'style'</span>, | |
<span class="hljs-string">'/Assets/Js/Shared/common.js'</span> =><span class="hljs-string">'script'</span>, | |
<span class="hljs-string">'/Assets/Img/Website/header.wep'</span>=><span class="hljs-string">'image'</span>, | |
]) | |
</code></pre> | |
<h6 id="to-automatically-push-all-assets-declared-to-response-html">to automatically push all assets declared to Response\HTML</h6> | |
<p>Enable the parameter <code>push_assets</code> in the <code>[response]</code> section. | |
This will push your assets before the DOM has been loaded and parsed. Making for quicker webpages loading.</p> | |
<h4 id="to-output-a-spreadsheet">To output a spreadsheet</h4> | |
<p>You can either output </p> | |
<ul> | |
<li>CSV</li> | |
<li>XLS (Requires phpoffice/phpspreadsheet)</li> | |
<li>XLSX (Requires phpoffice/phpspreadsheet) </li> | |
</ul> | |
<pre><code class="lang-php"><span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setType</span>(<span class="hljs-string">'xlsx'</span>); <span class="hljs-regexp">//</span> xls <span class="hljs-keyword">or</span> csv | |
<span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setContent</span>([ | |
[<span class="hljs-string">'A'</span>,<span class="hljs-string">'B'</span>,<span class="hljs-string">'C'</span>], | |
[<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>], | |
[<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>] | |
]); | |
<span class="hljs-regexp">//</span> <span class="hljs-symbol">Response:</span><span class="hljs-symbol">:setContent</span>(Models\<span class="hljs-symbol">Accounts:</span><span class="hljs-symbol">:_select</span>()->execute()); | |
<span class="hljs-symbol">Response:</span><span class="hljs-symbol">:download</span>(<span class="hljs-string">'Accounts.xlsx'</span>); | |
</code></pre> | |
<p>Note that arrays of objects from the database will automatically be converted to arrays.</p> | |
<h4 id="in-the-case-of-csv-files">In the case of CSV files</h4> | |
<p>You can pass (optional) options thru the global configuration.</p> | |
<pre><code class="lang-ini"><span class="hljs-section"> | |
[response]</span> | |
<span class="hljs-attr">csv_delimiter</span> = <span class="hljs-string">','</span> | |
<span class="hljs-attr">csv_encloser</span> = <span class="hljs-string">'"'</span> | |
</code></pre> | |
<h4 id="in-the-case-of-xlsx-files">In the case of XLSX files</h4> | |
<p>You can pass an (optional) compatibility option thru the global configuration.</p> | |
<pre><code class="lang-ini"><span class="hljs-section"> | |
[phpoffice]</span> | |
<span class="hljs-attr">office_2003_compatibility</span> = <span class="hljs-number">1</span> | |
</code></pre> | |
<h6 id="to-cache-the-result-of-a-reponse-all-output-type-will-be-cached-except-file-">To cache the result of a reponse (all output type will be cached except <code>file</code>)</h6> | |
<p><em>Note that cache has to be enabled in your ini configuration, posted <code>Request</code> are not cached, errors <code>Response</code> neither.</em></p> | |
<pre><code class="lang-php">Response::<span class="hljs-built_in">enable</span>OutputCache(<span class="hljs-variable">$hours</span>); | |
</code></pre> | |
<p>A cache hit will always use less than 400 Ko of RAM and execute much faster, under a millisecond on any decent server</p> | |
<h6 id="the-response-provides-some-headers-by-default-relative-slowness-of-this-example-is-due-the-the-filesystem-being-nfs-thru-wifi-">The <code>Response</code> provides some headers by default <em>Relative slowness of this example is due the the filesystem being NFS thru wifi</em></h6> | |
<pre><code>< HTTP/<span class="hljs-number">1.1</span> <span class="hljs-number">200</span> OK | |
< X-Powered-<span class="hljs-string">By:</span> PHP | |
< <span class="hljs-string">Server:</span> None of your business | |
< Content-<span class="hljs-string">Language:</span> fr | |
< Content-<span class="hljs-string">type:</span> text/html; charset=utf<span class="hljs-number">-8</span> | |
< Content-<span class="hljs-string">Length:</span> <span class="hljs-number">11</span> | |
< X-Memory-<span class="hljs-string">Usage:</span> <span class="hljs-number">436.9</span> Ko | |
< X-Execution-<span class="hljs-string">Time:</span> <span class="hljs-number">13.5</span> ms | |
</code></pre><h6 id="the-example-bellow-shows-the-same-hello-world-response-as-above-but-from-the-cache">The example bellow shows the same Hello World <code>Response</code> as above, but from the cache</h6> | |
<pre><code>< HTTP/<span class="hljs-number">1.1</span> <span class="hljs-number">200</span> OK | |
< X-Powered-<span class="hljs-string">By:</span> PHP | |
< <span class="hljs-string">Server:</span> None of your business | |
< Content-<span class="hljs-string">type:</span> text/html; charset=utf<span class="hljs-number">-8</span> | |
< Content-<span class="hljs-string">Encoding:</span> gzip | |
< Content-<span class="hljs-string">Length:</span> <span class="hljs-number">31</span> | |
< X-<span class="hljs-string">Footprint:</span> <span class="hljs-number">13.5</span> ms <span class="hljs-number">436.9</span> Ko | |
< X-<span class="hljs-string">Environment:</span> Prod | |
< <span class="hljs-string">Date:</span> Mon, <span class="hljs-number">19</span> Feb <span class="hljs-number">2018</span> <span class="hljs-number">19</span>:<span class="hljs-number">54</span>:<span class="hljs-number">19</span> +<span class="hljs-number">0100</span> | |
< <span class="hljs-string">Expires:</span> Mon, <span class="hljs-number">19</span> Feb <span class="hljs-number">2</span> <span class="hljs-number">018</span> <span class="hljs-number">23</span>:<span class="hljs-number">54</span>:<span class="hljs-number">19</span> +<span class="hljs-number">0100</span> | |
< X-<span class="hljs-string">Cache:</span> hit | |
< X-Cache-<span class="hljs-string">Footprint:</span> <span class="hljs-number">1.2</span> ms <span class="hljs-number">418.2</span> Ko | |
</code></pre><h3 id="-store-https-github-com-polyfony-inc-polyfony-wiki-reference-interface-polyfonystorestoreinterface-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#interface-polyfonystorestoreinterface">Store</a></h3> | |
<h6 id="the-store-interface-looks-like-this">The Store interface looks like this</h6> | |
<pre><code class="lang-php">Store\Engine::has(<span class="hljs-symbol">$</span><span class="hljs-keyword">variable</span>); | |
Store\Engine::<span class="hljs-keyword">put</span>(<span class="hljs-symbol">$</span><span class="hljs-keyword">variable</span>, $value <span class="hljs-comment">[, $overwrite = false])</span>; | |
Store\Engine::get(<span class="hljs-symbol">$</span><span class="hljs-keyword">variable</span>); | |
Store\Engine::remove(<span class="hljs-symbol">$</span><span class="hljs-keyword">variable</span>); | |
</code></pre> | |
<h6 id="you-can-choose-from-different-storage-engines">You can choose from different storage engines</h6> | |
<pre><code>Store<span class="hljs-string">\Cookie</span> | |
Store<span class="hljs-string">\Filesystem</span> | |
Store<span class="hljs-string">\Session</span> | |
Store<span class="hljs-string">\Database</span> | |
Store<span class="hljs-string">\Memcache</span> | |
Store<span class="hljs-string">\Request</span> | |
</code></pre><p>The last one stores your key-value only for the time of the current request. | |
Some of those engines have more capabilities than others, but all implement the basic interface and can store both variables, arrays, or raw data.</p> | |
<h3 id="-bundle-configurations-https-github-com-polyfony-inc-polyfony-wiki-reference-interface-polyfonyconfig-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#interface-polyfonyconfig">Bundle configurations</a></h3> | |
<h6 id="storing-some-bundle-specific-data">Storing some bundle specific data</h6> | |
<p>Configurations that are specific to a bundle should be placed in <code>Bundles/{MyBundle}/Loader/Config.php</code> (ex. static list choices, etc.) | |
<em>Note that these configurations are merged with <code>Config.ini</code> + <code>Dev.ini</code>/<code>Prod.ini</code> so all your configs are available with one interface : <code>Config::{get()/set()}</code></em></p> | |
<pre><code class="lang-php">Config::<span class="hljs-keyword">set</span>($<span class="hljs-keyword">group</span>, $<span class="hljs-keyword">key</span>, $<span class="hljs-keyword">value</span>); | |
</code></pre> | |
<h6 id="retrieve-values-whole-bundle-or-a-subset-">Retrieve values (whole bundle, or a subset)</h6> | |
<pre><code class="lang-php"><span class="hljs-symbol">Config:</span><span class="hljs-symbol">:get</span>(<span class="hljs-variable">$group</span>); | |
<span class="hljs-symbol">Config:</span><span class="hljs-symbol">:get</span>(<span class="hljs-variable">$group</span>, <span class="hljs-variable">$key</span>); | |
</code></pre> | |
<h3 id="-emails-https-github-com-polyfony-inc-polyfony-wiki-reference-interface-polyfonymail-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#interface-polyfonymail">Emails</a></h3> | |
<p>Emails are very simple to use and built over PHPMailer. | |
They extend <code>Entity</code> object, so there are normal database entries that have a few more methods, and they can be sent !</p> | |
<pre><code class="lang-php">(<span class="hljs-keyword">new</span> Models\Emails) | |
->set([ | |
<span class="hljs-string">'charset'</span> =><span class="hljs-string">'utf8'</span>, <span class="hljs-comment">// (get its defaults from Config.ini)</span> | |
<span class="hljs-string">'format'</span> =><span class="hljs-string">'text'</span>, <span class="hljs-comment">// (get its defaults from Config.ini)</span> | |
<span class="hljs-string">'from_name'</span> =><span class="hljs-string">'Me me me'</span>, <span class="hljs-comment">// (get its defaults from Config.ini)</span> | |
<span class="hljs-string">'from_email'</span>=><span class="hljs-string">'[email protected]'</span>, <span class="hljs-comment">// (get its defaults from Config.ini)</span> | |
<span class="hljs-string">'subject'</span> =><span class="hljs-string">'Hello !'</span>, | |
<span class="hljs-string">'body'</span> =><span class="hljs-string">'Hello again ?'</span>, | |
<span class="hljs-string">'reply_to'</span> =><span class="hljs-string">'[email protected]'</span>, <span class="hljs-comment">// fake column</span> | |
<span class="hljs-string">'to'</span> =><span class="hljs-string">'[email protected]'</span>, <span class="hljs-comment">// fake column</span> | |
<span class="hljs-string">'bcc'</span> =><span class="hljs-string">'[email protected]'</span>, <span class="hljs-comment">// fake column</span> | |
<span class="hljs-string">'cc'</span> =>[ <span class="hljs-comment">// fake column</span> | |
<span class="hljs-string">'email1'</span>=><span class="hljs-string">'name1'</span>, | |
<span class="hljs-string">'email2'</span>=><span class="hljs-string">'name2'</span> | |
], | |
<span class="hljs-string">'files_array'</span>=>[ | |
<span class="hljs-string">'../Private/Storage/Data/something.pdf'</span>=><span class="hljs-string">'Something.pdf'</span> | |
] | |
]) | |
->send([bool $save=<span class="hljs-keyword">false</span>]) <span class="hljs-comment">// instead of ->send() you can ->save(), to send it later</span> | |
</code></pre> | |
<p>An email that fails to send, but has ->send(true) will end up in the Emails table. You can send it later. | |
Its <code>creation_date</code> column will be filled, but it will have an empty <code>sending_date</code> column, making it really easy to retry later.</p> | |
<pre><code class="lang-php">(<span class="hljs-name">new</span> Emails(<span class="hljs-number">267</span>)) // assuming its id is <span class="hljs-number">267</span> | |
->send() | |
</code></pre> | |
<p>Even though <code>->save()</code> isn't explicitely called, nor is <code>->send(true)</code>, | |
since the email has been retrieved from the database, upon sending, its sending_date column will be updated and <strong>it will be saved</strong>. </p> | |
<h6 id="using-templates">Using templates</h6> | |
<pre><code class="lang-php">(<span class="hljs-keyword">new</span> Models\Emails) | |
->set([ | |
<span class="hljs-string">'subject'</span> =><span class="hljs-string">'Another passionnating subject'</span>, | |
<span class="hljs-string">'to'</span> =>[ | |
<span class="hljs-string">'[email protected]'</span> =><span class="hljs-string">'Jack'</span>, | |
<span class="hljs-string">'[email protected]'</span> =><span class="hljs-string">'Jill'</span> | |
], | |
<span class="hljs-comment">// set a PHP view, that is searched for in the "Emails" bundle.</span> | |
<span class="hljs-string">'view'</span> =><span class="hljs-string">'OrderConfirmation'</span> <span class="hljs-comment">// fake column, .php is suffixed automatically</span> | |
<span class="hljs-comment">// pass variables to the view, instead of directly setting the body</span> | |
<span class="hljs-string">'body'</span> =>[ | |
<span class="hljs-string">'firstname'</span> =><span class="hljs-string">'Louis'</span>, | |
<span class="hljs-string">'order_number'</span> =><span class="hljs-string">'XH20210722'</span>, | |
<span class="hljs-string">'order_products'</span>=>[$product_1,$product_2] | |
], | |
<span class="hljs-comment">// pass any number of CSS files. They will get inlined into style="" attributes of your view</span> | |
<span class="hljs-string">'css'</span> =>[ | |
<span class="hljs-string">'OrderConfirmationStyling'</span>, <span class="hljs-comment">// .css is suffixed automatically</span> | |
] | |
]) | |
->send(<span class="hljs-keyword">true</span>) | |
->isSent() ? do_something() : do_something_else(); | |
</code></pre> | |
<p>Your <code>Bundles/Emails/Views/OrderConfirmation.php</code> view could look something like this </p> | |
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">body</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">h1</span>></span> | |
Order Confirmation N° <span class="php"><span class="hljs-meta"><?</span>= $order_number; <span class="hljs-meta">?></span></span> | |
<span class="hljs-tag"></<span class="hljs-name">h1</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">p</span>></span> | |
Hi <span class="php"><span class="hljs-meta"><?</span>= $firstname; <span class="hljs-meta">?></span></span>, <span class="hljs-tag"><<span class="hljs-name">br</span> /></span> | |
We have received your order and will ship it as soon as possible. | |
<span class="hljs-tag"></<span class="hljs-name">p</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"products-table"</span>></span> | |
<span class="php"><span class="hljs-meta"><?php</span> <span class="hljs-keyword">foreach</span>($products <span class="hljs-keyword">as</span> $product): <span class="hljs-meta">?></span></span> | |
<span class="hljs-tag"><<span class="hljs-name">tr</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">td</span>></span> | |
<span class="php"><span class="hljs-meta"><?</span>= $product->getName(); <span class="hljs-meta">?></span></span> | |
<span class="hljs-tag"></<span class="hljs-name">td</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">tr</span>></span> | |
<span class="php"><span class="hljs-meta"><?php</span> <span class="hljs-keyword">endforeach</span>; <span class="hljs-meta">?></span></span> | |
<span class="hljs-tag"></<span class="hljs-name">table</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">body</span>></span> | |
</code></pre> | |
<p>Your <code>Bundles/Emails/Assets/Css/OrderConfirmationStyling.css</code> css could look something like this</p> | |
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> { | |
<span class="hljs-attribute">background</span>: <span class="hljs-number">#efefef</span>; | |
} | |
<span class="hljs-selector-tag">h1</span> { | |
<span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>; | |
} | |
<span class="hljs-selector-tag">p</span> { | |
<span class="hljs-attribute">font-weight</span>: bold; | |
} | |
<span class="hljs-selector-tag">table</span> { | |
<span class="hljs-attribute">padding</span>: <span class="hljs-number">25px</span>; | |
} | |
</code></pre> | |
<p>And the sent email would look like this</p> | |
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">body</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"background: #efefef"</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">h1</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"font-size: 16px;"</span>></span> | |
Order Confirmation N° XH20210722 | |
<span class="hljs-tag"></<span class="hljs-name">h1</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">p</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"font-weight: bold;"</span>></span> | |
Hi Louis, <span class="hljs-tag"><<span class="hljs-name">br</span> /></span> | |
We have received your order and will ship it as soon as possible. | |
<span class="hljs-tag"></<span class="hljs-name">p</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">table</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"padding: 25px;"</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">tr</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">td</span>></span> | |
F1 Steering Wheel signed by Louis Hamilton signed | |
<span class="hljs-tag"></<span class="hljs-name">td</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">tr</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">tr</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">td</span>></span> | |
Driving shoes, size 43 | |
<span class="hljs-tag"></<span class="hljs-name">td</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">tr</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">table</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">body</span>></span> | |
</code></pre> | |
<p>Always pay <strong>serious attention</strong> to validate user inputs that are inserted into emails (or anywhere for that matter).</p> | |
<h3 id="-element-https-github-com-polyfony-inc-polyfony-wiki-reference-interface-polyfonyelement-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#interface-polyfonyelement">Element</a></h3> | |
<h6 id="create-an-html-tag-similar-to-mootools-element-">Create an HTML tag (similar to mootools' Element)</h6> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= Element(<span class="hljs-string">'img'</span>,[<span class="hljs-string">'src'</span>=><span class="hljs-string">'/img/demo.png'</span>])->set(<span class="hljs-string">'alt'</span>,<span class="hljs-string">'test'</span>); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<pre><code class="lang-html"><<span class="hljs-selector-tag">img</span> src=<span class="hljs-string">"/img/demo.png"</span> alt=<span class="hljs-string">"test"</span> /> | |
</code></pre> | |
<h6 id="create-an-html-element-with-an-opening-and-a-closing-tag">Create an HTML element with an opening and a closing tag</h6> | |
<pre><code class="lang-php">$quote = <span class="hljs-keyword">new</span> Element(<span class="hljs-string">'quote'</span>,[<span class="hljs-string">'text'</span>=><span class="hljs-string">'Assurément, les affaires humaines ne méritent pas le grand sérieux'</span>]); | |
$quote->adopt($image); | |
</code></pre> | |
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">quote</span>></span>Assurément, les affaires humaines ne méritent pas le grand sérieux<span class="hljs-tag"><<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/img/demo.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"test"</span> /></span><span class="hljs-tag"></<span class="hljs-name">quote</span>></span> | |
</code></pre> | |
<p>Setting <code>value</code> will escape its html so will with setting <code>text</code>.</p> | |
<h3 id="-form-https-github-com-polyfony-inc-polyfony-wiki-reference-interface-polyfonyform-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#interface-polyfonyform">Form</a></h3> | |
<h6 id="form-helper-allow-you-to-build-and-preset-form-elements-ex-">Form helper allow you to build and preset form elements, ex.</h6> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= Form::input($name[, $value=<span class="hljs-keyword">null</span> [, $options=[]]]); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<h6 id="this-will-build-a-two-element-select-with-the-class-form-control-and-preset-peach-">This will build a two element select (with the class <code>form-control</code>), and preset Peach.</h6> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= Form::select(<span class="hljs-string">'sample'</span>, [] <span class="hljs-number">0</span> => <span class="hljs-string">'Apple'</span>, <span class="hljs-number">1</span> => <span class="hljs-string">'Peach'</span> ], <span class="hljs-number">1</span>, [<span class="hljs-string">'class'</span>=><span class="hljs-string">'form-control'</span>]); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<h6 id="this-will-build-a-select-element-with-optgroups-">This will build a select element with optgroups.</h6> | |
<p><em>Note that optgroup are replaced by a matching locale (if any), and values are also replaced by matching locale (if any).</em></p> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= Form::select(<span class="hljs-string">'sample'</span>, [ | |
<span class="hljs-string">'food'</span>=>[ | |
<span class="hljs-number">0</span> => <span class="hljs-string">'Cheese'</span>, | |
<span class="hljs-number">1</span> => <span class="hljs-string">'Houmus'</span>, | |
<span class="hljs-number">2</span> => <span class="hljs-string">'Mango'</span> | |
], | |
<span class="hljs-string">'not_food'</span>=>[ | |
<span class="hljs-number">3</span> => <span class="hljs-string">'Dog'</span>, | |
<span class="hljs-number">4</span> => <span class="hljs-string">'Cow'</span>, | |
<span class="hljs-number">5</span> => <span class="hljs-string">'Lizard'</span> | |
] | |
], <span class="hljs-number">3</span>); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"sample"</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">optgroup</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"food"</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0"</span>></span>Cheese<span class="hljs-tag"></<span class="hljs-name">option</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"1"</span>></span>Houmus<span class="hljs-tag"></<span class="hljs-name">option</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"2"</span>></span>Mango<span class="hljs-tag"></<span class="hljs-name">option</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">optgroup</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">optgroup</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"not_food"</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"3"</span> <span class="hljs-attr">selected</span>=<span class="hljs-string">"selected"</span>></span>Dog<span class="hljs-tag"></<span class="hljs-name">option</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"4"</span>></span>Cow<span class="hljs-tag"></<span class="hljs-name">option</span>></span> | |
<span class="hljs-tag"><<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"5"</span>></span>Lizard<span class="hljs-tag"></<span class="hljs-name">option</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">optgroup</span>></span> | |
<span class="hljs-tag"></<span class="hljs-name">select</span>></span> | |
</code></pre> | |
<p>Shortcuts are available from objects that extends the <code>Entity</code> class (ex: your Models).</p> | |
<h6 id="retrieve-an-account-from-its-id">retrieve an account from its id</h6> | |
<pre><code class="lang-php"><?= <span class="hljs-function"><span class="hljs-params">(<span class="hljs-keyword">new</span> Accounts(<span class="hljs-number">1</span>))</span> | |
-></span>set<span class="hljs-function"><span class="hljs-params">(<span class="hljs-string">'login'</span>, <span class="hljs-string">'[email protected]'</span>)</span> | |
-></span>input(<span class="hljs-string">'login'</span>, [<span class="hljs-string">'data-validators'</span>=><span class="hljs-string">'required'</span>]); | |
?> | |
</code></pre> | |
<pre><code class="lang-html"><input <span class="hljs-built_in">type</span>=<span class="hljs-string">"text"</span> name=<span class="hljs-string">"Accounts[login]"</span> <span class="hljs-built_in">value</span>=<span class="hljs-string">"[email protected]"</span> data-validators=<span class="hljs-string">"required"</span>/> | |
</code></pre> | |
<p>List of available elements :</p> | |
<ul> | |
<li>input</li> | |
<li>select</li> | |
<li>textarea</li> | |
<li>checkbox</li> | |
</ul> | |
<p>Form elements general syntax is : <code>$name, $value, $options</code> when you get a form element from a <code>Entity</code>, the <code>$name</code> and <code>$value</code> are set automatically, only <code>$options</code> are available. The select elements is slighly different : <code>$name, $list, $value, $options</code></p> | |
<p>To obtain, say, a password field, simply add this to your array of attributes : 'type'=>'password'</p> | |
<h2 id="-crsf-protection-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyformtoken-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyformtoken">CRSF Protection</a></h2> | |
<p>A CRSF Protection and double-submit guard is available.</p> | |
<h6 id="in-the-middle-of-your-html-form-in-a-view-">In the middle of your html form (in a View)</h6> | |
<pre><code class="lang-html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">""</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>></span> | |
<span class="hljs-comment"><!-- more form here --></span> | |
<span class="php"><span class="hljs-meta"><?</span>= <span class="hljs-keyword">new</span> Polyfony\Form\Token(); <span class="hljs-meta">?></span></span> | |
<span class="hljs-comment"><!-- more form here --></span> | |
<span class="hljs-tag"></<span class="hljs-name">form</span>></span> | |
</code></pre> | |
<h6 id="in-your-controller">In your controller</h6> | |
<pre><code class="lang-php">Polyfony\<span class="hljs-keyword">Form</span>\<span class="hljs-keyword">Token</span>::enforce(); | |
</code></pre> | |
<p><strong>That's it.</strong> </p> | |
<p>Instanciating a "Token" objet generates a unique token, stores it in the PHP SESSION and builds an html input element.<br>The static enforce method, checks if a request has been POSTed, and if so, if a token exists, and matches one stored in the session. Otherwise, throws an exception and redirects to the previous page.</p> | |
<h2 id="-captcha-protection-https-github-com-polyfony-inc-polyfony-wiki-reference-class-polyfonyformcaptcha-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyformcaptcha">Captcha Protection</a></h2> | |
<p>A Captcha provider is available, it's a wrapper of gregwar/captcha.</p> | |
<h6 id="in-the-middle-of-your-html-form-in-a-view-">In the middle of your html form (in a View)</h6> | |
<p>Show the captcha image itself</p> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= <span class="hljs-keyword">new</span> Polyfony\Form\Captcha( | |
<span class="hljs-number">5</span>, <span class="hljs-comment">// number of characters in the captcha (optional)</span> | |
<span class="hljs-number">150</span>, <span class="hljs-comment">// width of the captcha, in px (optional)</span> | |
<span class="hljs-number">40</span> <span class="hljs-comment">// height of the captcha, in px (optional)</span> | |
); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<p>Show an input to type the captcha in</p> | |
<pre><code class="lang-php"><span class="php"><span class="hljs-meta"><?</span>= Polyfony\Form\Captcha::input([ | |
<span class="hljs-comment">// html attributes (optional)</span> | |
<span class="hljs-string">'class'</span>=><span class="hljs-string">'form-control'</span>, | |
<span class="hljs-string">'placeholder'</span>=><span class="hljs-string">'Type the captcha here'</span> | |
]); <span class="hljs-meta">?></span></span> | |
</code></pre> | |
<h6 id="in-your-controller">In your controller</h6> | |
<pre><code class="lang-php">Polyfony<span class="hljs-symbol">\F</span>orm<span class="hljs-symbol">\C</span>aptcha::enforce(); | |
</code></pre> | |
<p><strong>That's it.</strong> </p> | |
<p>Instanciating a "Captcha" objet generates a phrase, stores it in the PHP SESSION and builds a captcha image using gregwar/captcha builder.<br>The static enforce method, checks if a request has been POSTed, and if so, if a captcha value exists, and matches one stored in the session. Otherwise, throws an exception and redirects to the previous page. | |
You can manually try/catch exception to avoid loosing what the user typed, in that case, use <code>Captcha::enforce(true)</code> to prevent automatic redirections. </p> | |
<h2 id="database-structure">Database structure</h2> | |
<p>The framework has been extensively tested using SQLite, it <em>may</em> work with other engines, it defitively works without. | |
Without, you'd just loose <code>Security</code>, the <code>Emails</code> storage feature, the <code>Store\Database</code> engine and the <code>Logger</code>'s database feature.</p> | |
<p>The database's structure is available by dumping the SQLite Database <code>Private/Storage/Database/Polyfony.db</code>. | |
The PDO driver can be changed to <code>MySQL</code>, <code>Postgres</code> or <code>ODBC</code> in <code>Private/Config/Config.ini</code>. <strong>There is no <code>Query</code> object support for Postgres and ODBC.</strong></p> | |
<h2 id="logging">Logging</h2> | |
<p>The framework exposes a <strong>Logger class</strong>, with the following <strong>static</strong> methods</p> | |
<ul> | |
<li><code>debug(string $message, ?mixed $context) :void</code> (level 0)</li> | |
<li><code>info(string $message, ?mixed $context) :void</code> (level 1)</li> | |
<li><code>notice(string $message, ?mixed $context) :void</code> (level 2)</li> | |
<li><code>warning(string $message, ?mixed $context) :void</code> (level 3)</li> | |
<li><code>critial(string $message, ?mixed $context) :void</code> (level 4)</li> | |
</ul> | |
<p>The logs can be sent to a file, or to your database (see <code>Config.ini [logger][type]</code>). | |
The minimum level to log is configurable (see <code>Config.ini [logger][level]</code>) by default, <code>Dev</code> environement is configured to log from the <code>0</code> level, <code>Prod</code> environment is configured to log from the <code>1</code> level. | |
Critital level logs (<code>4</code>) can also be sent automatically via email (see <code>Config.ini [logger][mail]</code>. </p> | |
<p><strong>Logged message/objects/array are also automatically made available in the Profiler</strong> </p> | |
<p>Example</p> | |
<pre><code class="lang-php"><span class="hljs-symbol">Logger:</span><span class="hljs-symbol">:notice</span>(<span class="hljs-string">'Something not too worrying just happened'</span>); | |
<span class="hljs-symbol">Logger:</span><span class="hljs-symbol">:debug</span>(<span class="hljs-string">'Someone did something'</span>, <span class="hljs-variable">$some_kind_of_object</span>); | |
<span class="hljs-symbol">Logger:</span><span class="hljs-symbol">:critical</span>(<span class="hljs-string">'Failed to contact remote API'</span>, <span class="hljs-variable">$api_handler</span>); | |
</code></pre> | |
<h2 id="updating-the-framework">Updating the framework</h2> | |
<h4 id="to-updade-the-framework-run-this-command-from-your-project-directory-beware-of-backward-incompatible-changes-">To updade <strong>the framework</strong>, run this command from your project directory (beware of backward incompatible changes)</h4> | |
<p>The first and last command allow you to preserve and restore your composer.json after the udpate</p> | |
<pre><code class="lang-bash">git stash | |
git pull | |
git stash <span class="hljs-built_in">apply</span> | |
</code></pre> | |
<h4 id="to-updade-the-dependencies-run-this-command-from-your-project-directory">To updade <strong>the dependencies</strong>, run this command from your project directory</h4> | |
<pre><code class="lang-bash"><span class="hljs-attribute">composer update</span> | |
</code></pre> | |
<h2 id="deprecated-discontinued-and-renamed-features">Deprecated, discontinued and renamed features</h2> | |
<table> | |
<thead> | |
<tr> | |
<th><strong>Previous Feature</strong></th> | |
<th><strong>Status</strong></th> | |
<th><strong>Replacement</strong></th> | |
<th><strong>How to get it</strong></th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>Polyfony\HttpRequest()</td> | |
<td>DISCONTINUED</td> | |
<td>Curl\Curl()</td> | |
<td>require php-curl-class/php-curl-class</td> | |
</tr> | |
<tr> | |
<td>Polyfony\Filesystem()</td> | |
<td>DISCONTINUED</td> | |
<td>Filesystem\Filesystem()</td> | |
<td>require symfony/filesystem</td> | |
</tr> | |
<tr> | |
<td>Polyfony\Uploader()</td> | |
<td>DISCONTINUED</td> | |
<td>FileUpload\FileUpload()</td> | |
<td>require gargron/fileupload</td> | |
</tr> | |
<tr> | |
<td>Polyfony\Validate()</td> | |
<td>DISCONTINUED</td> | |
<td>Validator\Validation()</td> | |
<td>require symfony/validator</td> | |
</tr> | |
<tr> | |
<td>Polyfony\Thumnbail()</td> | |
<td>DISCONTINUED</td> | |
<td>Intervention\Image()</td> | |
<td>require intervention/image</td> | |
</tr> | |
<tr> | |
<td>Polyfony\Notice()</td> | |
<td>DISCONTINUED</td> | |
<td>Bootstrap\Alert()</td> | |
<td>require polyfony-inc/bootstrap</td> | |
</tr> | |
<tr> | |
<td>Polyfony\Keys()</td> | |
<td>RENAMED</td> | |
<td>Polyfony\Hashs()</td> | |
<td>bundled with polyfony</td> | |
</tr> | |
</tbody> | |
</table> | |
<h2 id="release-history">Release history</h2> | |
<table> | |
<thead> | |
<tr> | |
<th>Version</th> | |
<th>Major change</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>2.0-alpha</td> | |
<td>Major rewrite from 1.x new folder structure, routes syntax, new helpers, new configuration files, MVC architecture, database entries are instanciated as Entity objects.</td> | |
</tr> | |
<tr> | |
<td>2.0</td> | |
<td>Better ORM, Database entries now are instanciated as Models/{TableName} that inherit the Entity class</td> | |
</tr> | |
<tr> | |
<td>2.1</td> | |
<td>PHP 7.2 support, composer support, new debugging tools are introduced (Profiler), deprecation of old helpers</td> | |
</tr> | |
<tr> | |
<td>2.2</td> | |
<td>Old routes syntax have been dropped, redirections are now supported directly in routes declaration</td> | |
</tr> | |
<tr> | |
<td>2.3</td> | |
<td>XSS escaping as default for all Entity->get(), Filters are now supported on Entities, Entities Validators are enhanced</td> | |
</tr> | |
<tr> | |
<td>2.4</td> | |
<td>Query->first() used to return false when no result were found, it now returns null.</td> | |
</tr> | |
<tr> | |
<td>2.5</td> | |
<td>Query->get() shortcut for ->first()->execute(), enhanced Profiler, Console shortcut</td> | |
</tr> | |
<tr> | |
<td>2.6</td> | |
<td>Emails refactoring, Tests support via PHPUnit, Events support</td> | |
</tr> | |
<tr> | |
<td>3.0</td> | |
<td>New ACLs, PHP views and CSS inlining in emails, new helper accessors for Entities, HTTP/2 push support, discontinuation of HttpRequest, Filesystem and Uploader classes</td> | |
</tr> | |
<tr> | |
<td>3.1</td> | |
<td>New Routes signature feature, Keys renamed to Hashs, PHP 7.4+ required</td> | |
</tr> | |
<tr> | |
<td>4.0</td> | |
<td>PHP 8.0+ required, minor refactoring of some classes</td> | |
</tr> | |
</tbody> | |
</table> | |
<h2 id="-performance-https-github-com-polyfony-inc-polyfony-wiki-benchmark-"><a href="https://github.com/polyfony-inc/polyfony/wiki/Benchmark">Performance</a></h2> | |
<p>Polyfony has been designed to be fast, no compromise (> 2000 req/s). | |
If implementating a « convenience » tool/function into the framework was to cost a global bump in execution time, it is either implemented in a more efficient manner, or not implemented at all.</p> | |
<h2 id="security">Security</h2> | |
<p>The codebase is small, straightforward and abundantly commented. It's audited using SensioInsight, CodeClimate, RIPS, and Sonar.</p> | |
<h2 id="coding-standard">Coding Standard</h2> | |
<p>Polyfony follows the PSR-0, PSR-1, PSR-4 coding standards. It does not respect PSR-2, as tabs are used for indentation.</p> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment