polyfony-inc / polyfony
Polyfony 是一个简单且强大的 PHP 微框架
Requires
- php: >=8.0.0
- ext-mbstring: *
- ext-msgpack: *
- ext-pdo: *
- ext-sqlite3: *
- laminas/laminas-escaper: ^2.12
- matthiasmullie/minify: ^1.3
- phpmailer/phpmailer: ^6.0
- polyfony-inc/bootstrap: dev-master
- polyfony-inc/captcha: dev-master
- polyfony-inc/console: dev-master
- polyfony-inc/google: dev-master
- tijsverkoyen/css-to-inline-styles: ^2.2
Suggests
- gargron/fileupload: ^1.4
- intervention/image: ^2.4
- php-curl-class/php-curl-class: ^9.0
- phpoffice/phpspreadsheet: ^1.5
- symfony/filesystem: ^4.0
- symfony/validator: ^4.0
This package is auto-updated.
Last update: 2024-09-30 01:30:21 UTC
README
Polyfony 是一个直观、轻量且强大的 PHP 微框架。
哲学
受 Symfony 和 Laravel 启发,但更倾向于极端的简洁和效率。
与主要 PHP 框架相比,Polyfony 覆盖了我们大部分时间所需功能的 95% 以上,并且只使用了主要框架所需资源、空间、配置文件和依赖的一小部分。
它具有路由、组件、控制器、分析器、视图、ORM、测试、缓存、多语言、事件、身份验证、环境、表单助手、命令行助手等功能,并通过 composer 提供可扩展性。
"Hello World" 的足迹
- ≤ 300 Ko 的磁盘空间 (35% 的注释行)
- ≤ 400 Ko 的 RAM
- ≤ 2.5 ms (冷启动)
要求
您需要一个 POSIX 兼容的系统(Linux/MacOS/xBSD)、PHP >= 7.4 以及 ext-pdo、ext-sqlite3、ext-mbstring、ext-msgpack 和您 web 服务器的重写模块。
免责声明
如果您考虑使用此框架代替 Laravel 等主要且受支持的框架,那么您的决策过程和/或项目评估中可能存在一些严重问题。
在几乎所有情况下,使用主流框架都是一个更好的选择。
安装
在 your-project-folder 中下载并预配置框架
composer create-project --stability=dev polyfony-inc/polyfony your-project-folder
--stability=dev 是必需的,因为我们尚未发布版本 大多数通过 composer 安装的依赖项仅由 PHPUnit 需要。
NginX 配置
root /var/www/your-project-folder/Public
location / {
try_files $uri $uri/ /index.php?$query_string;
}
LigHTTPd 配置
server.document-root = "/var/www/your-project-folder/Public/"
url.rewrite-once = ("^(?!/Assets/).*" => "/?")
Apache 配置
DocumentRoot "/var/www/your-project-folder/Public/"
几乎不需要学习
此 readme.md 文件应该足以让您入门。您还可以浏览 Private/Bundles/Demo/
组件和框架的源代码。由于框架类是静态的,所有内容都通过简单和自然的命名在任何地方始终可用。
下面的代码假设您在每个调用之前都在 Polyfony
命名空间前添加了前缀。
请求
// 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'); // check if the method is post (returns a boolean) Request::isPost(); // check if the request is done using ajax (returns a boolean) Request::isAjax(); // check if the request is done thru TLS/SSL (returns a boolean) Request::isSecure(); // check if the request is from the command line (returns a boolean) Request::isCli();
数据库
Polyfony 提供了自感知实体,类似于 RubyOnRails 中的 ActiveRecord 或 Eloquent 对象。
以下示例假设数据库中存在名为 Pages
的表。在 Models\Pages
文件中需要以下最小量的代码。
namespace Models; class Pages extends \Polyfony\Entity {}
// Retrieve a database entity by its ID, here, the id 67 $webpage = new Pages(67); // Retrieve another database entity by its `url` column $webpage = new Pages(['url'=>'/my-awesome-vegan-burger-recipe/']); // Retrieve a single Entity by its ID and generate an input to change its title property, with a custom css class // note that any html in the title field will be escaped in the <input> to prevent XSS // returns <input type="etextmail" name="Pages[title]" value="My awesome Vegan burger recipe is so yummy" /> (new Pages(67)) ->input('title', ['class'=>'form-control']); // Create an new page, populate and save it (new Pages) ->set([ 'url' => '/veganaise-c-est-comme-de-la-mayonnaise-mais-vegan/', 'title' => 'I\'m running out of ideas...', 'description' => 'Meta descriptions get less and less important with Google\'s newest algorithms', 'creation_date' => '18/04/1995', // this gets converted to a unixepoch automagically 'modification_date' => time(), 'contents' => 'Meh...', 'categories_array' => ['Cooking', 'Organic'], // this get's saved as json automagically 'id_creator' => Security::getAccount()?->get('id') // assuming you are logged in ]) ->save(); // Alternatively, you can also create an entity this way Pages::create([ 'url' =>'...', 'title' =>'...', // more columns and values... ]); // Retrieve the `title` and `id` of 5 pages that have the `Organic` category, // that have been modified in the last week, // and that have been created by user's id 7. $pages = Pages::_select(['title','id']) ->whereContains(['categories_array'=>'Organic']) ->whereGreaterThan('modification_date', time() - 7*24*3600) ->where(['id_creator'=>7]) ->limitTo(0,5) ->execute();
参数
->where() // == $value ->whereNot() // <> $value ->whereBetween() // BETWEEN $min_value AND $max_value ->whereMatch() // MATCH column AGAINST $value ->whereContains() // % $value % ->whereEndsWith() // % $value ->whereStartsWith() // $value % ->whereNotEmpty() // <> '' and NOT NULL ->whereEmpty() // '' or NULL ->whereNotNull() // NOT NULL ->whereNull() // NULL ->whereGreaterThan() // < $value ->whereLessThan() // > $value ->whereIn() // IN ( values ) ->whereNotIn() // NOT IN ( values )
选项
->orderBy() // associative array ('column'=>'ASC') ->limitTo() // $start, $end ->groupBy() // ? ->first() // return the first Entity instead of an array of Entities
魔法列
- 以
_date
、_on
、_at
结尾的列将从DD/MM/YYYY
转换为时间戳,反之亦然 - 以
_datetime
结尾的列将从DD/MM/YYYY HH:mm
转换为时间戳,反之亦然 - 以
_array
结尾的列将转换为 json 并存储,然后恢复到其原始类型 - 以
_size
结尾的列将转换为可读的 human readable 大小
您可以轻松地向 _array
列的末尾添加元素。假设您有一个 Process
对象/表,它有一个 events_array
属性/列。
// create a new Process object (new Process) // push an event into the events_array object ->push('events_array', [ // this array is arbitrary, you are free to push anything into the column 'date' =>time(), 'is_important' =>false, 'message' =>'Something just happened !' ]) // your can also ommit the _array, the framework will find the right column ->push('events', [ // this array is arbitrary, you are free to push anything into the column 'date' =>time(), 'is_important' =>true, 'message' =>'Something dubious just occured !' ]) ->save();
实体访问器
实体具有基本的 ->set([$column=>$value])
和 ->get($column, $bymypass_html_entities_protection=false)
方法。此外,还有
->oset($associative_array, $columns_to_actualy_set)
"OnlySet",某些列->lget($column)
"LocalizedGet",将尝试使用区域设置来获取返回值->tget($column, $length)
"TruncatedGet",将截断超出长度的返回值
跨站脚本(XSS)防护
在任意其他列上调用 ->get() 将会自动使用 PHP 的 FILTER_SANITIZE_FULL_SPECIAL_CHARS
对特殊 HTML 符号进行转义,以防止 XSS 攻击。如果实际上你需要从数据库中获取原始数据,请在第二个参数中添加 true
,如下所示 $object->get('column_name', true);
来检索数据 "原样"。在代码中的任何地方调用 Format::htmlSafe() 都会提供相同的转义功能。
数据验证器
应该使用 symfony/validator
、respect/validation
、wixel/gump
或类似的包来管理数据验证。 话虽如此,有一个非常基础的(可选的)内置验证器,用于防止在操作对象时数据损坏。
要强制执行它,在你的模型中声明一个名为 VALIDATORS
的常量数组,其中每个键是列名,每个值是一个正则表达式、一个允许值的数组或一个标准的 PHP 过滤器名称(例如 FILTER_VALIDATE_IP
)。
- 示例
Models\Accounts extends Polyfony\Security\Accounts { // Normal model classes extend Polyfony\Entity. // Accounts extends an intermediate (but transparent) class that adds authentication logic. const IS_ENABLED = [ 0 =>'No', 1 =>'Yes' ]; const VALIDATORS = [ // using PHP's built in validators 'login' =>FILTER_VALIDATE_EMAIL, 'last_login_origin' =>FILTER_VALIDATE_IP, 'last_failure_origin' =>FILTER_VALIDATE_IP, // using arrays 'is_enabled'=>self::IS_ENABLED, 'id_level' =>self::ID_LEVEL ]; }
验证在调用 ->set() 时发生,并将抛出异常。
请注意,您不需要在验证器中包含 NULL
或 EMPTY
值来允许它们。 NULL/NOT NULL
应在数据库中进行配置,以便框架知道哪些列可以是 null,哪些不可以。
请注意,在表上执行批量 ->update(即:不使用不同的对象/实体)将绕过这些验证器。这将在后续版本中修复。
数据过滤
数据过滤和净化可以用来 附加 或 代替 数据验证器。虽然验证器在遇到无效数据时会抛出异常,但数据过滤器会清理数据,使其符合预期数据的性质。
要强制执行数据过滤,在你的模型中声明一个名为 FILTERS
的常量数组,其中每个键是列名,每个值是一个过滤器名称或一个将要依次应用的过滤器名称的数组。
- 示例
// an imaginary group model, that represent a group of people Models\Groups extends Polyfony\Entity { const FILTERS = [ // replaces , with a dot and removes everything except 0-9 + - . 'group_monthly_allowance' => 'numeric', // trim spaces, removes any special chars and capitalize each words 'group_name' => ['trim','text','ucwords'], // removes any special chars and capitalize each words 'group_manager_name' => ['text','strtoupper'], // cleanup an email address 'group_manager_email' => 'email' ]; }
过滤在调用 ->set() 时发生,并在验证(如果有)之后。
可用过滤器列表
如果数据的大写字母比例足够低,则 capslock30
、capslock50
和 capslock70
不会影响数据。此过滤器旨在创建更美观、更干净的数据库,它是为喜欢 FUCK YEAH CAPS LOCK !! 生活方式的老年人设计的。
使用模型过滤器的好处之一是,您的输入和文本区域将自动获得正确的 HTML 属性和类型。
查看以下模型、视图和 HTML 输出。
class User extends Polyfony\Entity { const FILTERS = [ 'user_login'=>['email','length128'] ]; }
<?= (new Models\Users->input('user_login')); ?>
<input name="Users[user_login]" type="email" maxlength="128" value="" />
如果列不能为 null,则您的输入还将获得 required="required"
属性。这是从数据库模式中推断出来的。
请注意,在表上执行批量 ->update(即:不使用不同的对象/实体)将绕过这些验证器。这将在后续版本中修复。
数据自动填充
框架将寻找一些常见的列,并尝试使用 Unix 纪元或当前认证用户的 ID 来填充它们。
在 Entity
创建 时:creation_by
、creation_date
、created_by
、created_at
、creation_datetime
在 Entity
修改 时:modification_by
、modification_date
、modified_by
、modified_at
路由器
路由将 URL 映射到一个位于 Bundle
中的 Controller
中的 Action
。Action
是一个 方法,Controller
是一个 类,Bundle
是一个 文件夹。路由应在每个包的 Loader
目录中的 Route.php
文件中声明。
示例: Private/Bundles/{BundleName}/Loader/Route.php
路由可以接受多个参数,也可以没有。
Router::map('/admin/:what/:id/', 'Bundle/Controller@{what}')
.Router::map('/url/', 'Bundle/Controller@action')
.
操作可以
- 成为URL的参数(如第一个示例。动作将是第二个参数
{what}
) - 可以省略。在这种情况下将调用
->index()
。如果它不存在,将调用->default()
,如果它也不存在,则抛出异常。
注意,作为额外的安全措施,动作只能包含字母数字字符、-
、_
和.
。
在调用所需动作之前,将在控制器上调用一个->before()
方法。你可以声明一个,或者省略它。
在调用所需动作之后,将在控制器上调用一个->after()
方法。你可以声明一个,或者省略它。
- 以下路由将匹配到/about-us/的GET请求
Router::get('/about-us/', 'Pages/Static@aboutUs');
它将调用Private/Bundles/Pages/Controllers/Static.php->aboutUs();
- 以下路由将匹配到/admin/{edit,update,delete,create}/和/admin/的任何方法请求(GET、POST...)
Router::map('/admin/:section/:id/', 'Admin/Main@{section}') ->where([ 'section'=>[ // section must be one of the following four // though you can ommit this, a wrong section value will trigger a defaultAction() on the controller // which means an exception if you have not declared it. 'in_array'=>['edit','update','delete','create'] ], 'id'=>[ // id has to be a numeric value 'is_numeric', // id can't be 0 '!in_array'=>[0] ] ]);
它将调用Private/Bundles/Admin/Controllers/Main.php->{section}();
路由也可以在数据库迭代中动态生成。
// assuming you have a Pages table containing, well, pages. // those would have an "url" column that define the absolute url of the page. // you should replace the _select with a cachable method in the model though. foreach(Pages::_select(['url'])->execute() as $page) { // map that url to a single "Pages" controller // that resides in a "Site" bundle Router::get( $page->get('url'), 'Site/Pages@view' ); }
然后,在Bundles/Site/Controllers/Pages.php
class PagesController extends Controller { public function viewAction() { // retrieve your webpage from the database // using not its id, but its url column value ! $page = new Pages(['url'=>Request::getUrl()]) // maybe set the title and description using that database object Response\HTML::set( 'metas'=>[ 'title' =>$page->get('title'), 'description' =>$page->get('description') ] ); // and pass your variables to the view $this->view('Pages/FromTheDatabase', [ 'title' =>$page->get('title'), 'contents' =>$page->get('contents') ]); } }
URL参数约束
- "in_array" => [允许的值]
- "!in_array" => [不允许的值]
- "preg_match" => "要匹配的正则表达式"
- "!preg_match" => "不要匹配的正则表达式"
- "is_numeric"
- "!is_numeric" 如果声明了多个约束,它们都必须匹配。
重定向声明
- 以下将从
/some-old-url/
重定向到/the-new-url/
,使用301状态码。
Router::redirect('/some-old-url/', '/the-new-url/', [$status_code=301]);
这些都是静态重定向,不是重写规则。它们不能包含动态参数。
URL参数签名
有时,你可能想与一个没有你软件账户的人分享链接。你可能不想麻烦这些人注册账户。在这些情况下,签名URL参数并传递哈希是一种保护URL的好方法。生成的URL将是唯一的且不可预测的。
要要求路由签名,将->sign()
方法应用于你的路由声明。
// the associated signed URL can be sent by email // the client would not have to log in to track their order, how comfortable Router::get( '/track/shipping/:id_client/:id_order/' 'External/Tracking@order', 'order-tracking' )->sign();
要获取签名URL,使用Router::reverse()
方法,生成的URL将自动签名。
// this will generate an URL looking like this // https://my.domain.com/track/shipping/4289/24389/E29E798A097F099827/ Router::reverse( 'order-tracking' [ 'id_client'=>$client->get('id'), 'id_order'=>$order->get('id') ], true, // force TLS true // include domain name );
你可以在Config.ini
中的[router]
下自定义哈希参数的名称,然后在signing_parameter_name = 'url_parameters_hash'
,这样它就不会与你的命名参数冲突。
速率限制
默认情况下,节流基于路由名称和远程地址(IP),因此你必须命名你的路由。使用的机制是漏桶。
要节流路由
Router::put( '/place-order/:id_customer/' 'External/Tracking@order', 'place-order' )->throttle(2, 3600); // enforces a limit of 2 requests every 3600 minutes (one hour, leaky bucket)
要手动节流(在控制器中)
// you can use the method enforce (this limits to 5 per hour) Throttle::enforce(5, 3600); // method shortcuts (this limits to 2 per minute) Throttle::perMinute(2); //Throttle::perSecond(); //Throttle::perHour(); //Throttle::perDay(); // 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) Throttle::perHour( 2, Hashs::get([ $id_client, Request::server('REMOTE_ADDR') ]) );
当有人正在节流时,将抛出一个带有消息“您正在被速率限制”的429
异常。请注意
- 在节流期间的新点击不会扩展锁定
- 没有爆发支持
- 后端使用APCu,出于性能和DoS安全的原因
环境
https://github.com/polyfony-inc/polyfony/wiki/Reference#class-polyfonyconfig
环境定义了执行上下文,它们有自己的变量集。在Polyfony中存在两个环境
Dev
,开发环境(这是你的编码发生的地方,很可能是你的本地开发服务器或你的电脑),Prod
,生产环境(也称为Live
)。
两个环境共有的变量应该放在主配置文件Private/Config/Config.ini
中,环境检测可以基于
- 域名
- 端口。
你可以在Config.ini
中选择检测方法
[polyfony]
detection_method = "domain" ; or "port"
根据检测到的环境,要么是
Private/Config/Dev.ini
,要么是Private/Config/Prod.ini
将覆盖/合并到主Config.ini
与许多框架不同,您的开发应用程序文件夹和生成文件夹完全相同。您不需要在生产服务器上使用不同的.env文件。
以下是带有其开发域的示例Dev.ini
Private/Config/Dev.ini
[router]
domain = project.company.tld.test
port = 80
以及带有其生产域的示例Prod.ini
如果既没有匹配的域也没有端口,框架将回退到生产模式
Private/Config/Prod.ini
[router]
domain = project.company.tld
port 80
[response]
minify = 1
compress = 1
cache = 1
pack_js = 1
pack_css = 1
安装期间,Composer会将默认配置文件和已准备好的设置放入其中
您需要修改您的/etc/hosts
文件,将project.company.tld.test
指向127.0.0.1
或修改您的本地DNS服务器。
要检索配置值(从合并的配置文件中)
// retrieve the whole 'response' group Config::get('response'); // retrieve only a key from that group Config::get('response', 'minify');
具有不同的配置文件允许您
- 设置一个绕过电子邮件来捕获开发环境中发送的所有电子邮件
- 仅在生产环境中启用压缩、混淆/压缩和缓存
- 在开发中显示分析器(甚至在早期生产阶段如有必要)
- 使用不同的数据库配置
- 在生产中加强安全参数,同时在本地测试期间允许较松的设置
- 等。
安全
安全性基于一个共同的电子邮件/密码对。密码在存储到数据库之前会被强加密和加盐。可以使用algo
(默认为sha512
)和salt
参数调整哈希算法。
用户可以直接分配任意数量的角色AccountsRoles
和权限AccountsPermissions
。角色本身也可以分配权限。用户将从其角色继承权限。权限必须至少分组到一个逻辑组AccountsPermissionsGroups
中。
此外,两种机制加强安全性
- 一个节流机制,防止暴力攻击。可以通过
forcing_timeframe
和forcing_maximum_attempts
参数进行调整。 - 一个反cookie窃取机制,检查最初登录的用户是否仍然是同一个。即使他/她拥有正确的会话cookie。可以通过更改
enable_signature_verification
参数(默认为1
- 启用)来禁用此功能。
您可以根据请求禁用cookie窃取保护。通过动态更改配置Config::set('security','enable_signature_verification', 0)
,这在特定情况下可能很有用。
要保护一个页面(要求用户登录)
Security::authenticate();
认证失败将抛出一个带有403
状态码的异常,并将重定向到Private/Config/Config.ini
-> [router]
-> login_route = ""
如果页面或操作需要特定的权限
Security::denyUnlessHasPermission('permission_name or permission_id or permission entity');
如果不符合这些要求,将抛出异常,但不会将用户重定向到任何地方。
要手动检查凭据
Security::getAccount() ->hasPermission( $permission_id // or permission_name or permission entity ); Security::getAccount() ->hasRole( $role_id // // or role_name or role entity );
分析器
在重代码块周围设置标记来估计该块的时间和内存影响。
Profiler::setMarker('ClueA.subclue1'); Profiler::releaseMarker('ClueA.subclue1')
如果Config::get('profiler', 'enable')
设置为true (1)
且您的Response
类型为html
,您将在页面底部看到一个漂亮的条形图,其中包含大量有用的信息。该条形图依赖于bootstrap 4 CSS和JS。请确保将它们添加到您的资产中,以享受分析器的所有好处。默认情况下,一些标记放置在关键位置(围绕每个Database
查询,围绕控制器转发...)。
如果您的Response
类型为json
,则Profiler
信息将作为一个数组与您的Response
合并。
区域设置
地区信息存储在csv文件中(制表符+双引号),存储在每个捆绑包的Bundles/MyBundle/Locales/
文件夹中。文件在您第一次请求地区时进行解析。语言将自动检测浏览器语言,您也可以手动设置。
获取当前语言的地区(自动检测)
Locales::get($key)
获取不同语言的地区
Locales::get($key, $language)
设置语言(将在cookie中保存一个月)
Locales::setLanguague($language)
异常
如果有任何异常,将路由到名为“exception”的路径,否则异常将正常抛出。默认状态码为500,您可以指定任何HTTP状态码。禁用缓存的状态码。
Throw new Exception($error_message, $http_status_code);
响应
响应是预先配置的,根据Config.ini来渲染完整的HTML页面(主体、标题、元信息、样式表等)。您可以在运行时更改响应类型和参数。
所有响应中的公共部分,您可以在Response
中找到。由于links
、scripts
和metas
是HTML Response
的特定部分,因此这些在子命名空间Response\HTML
中设置
重定向
Response::setRedirect($url [, $after_seconds=0])
重定向到上一页
Response::previous()
更改字符集
Response::setCharset('utf-8')
以内联形式输出文件
Response::setType('file') Response::setContent($file_path) Response::render()
下载文件
Response::setType('file') Response::setContent($file_path) Response::download('Myfilename.ext'[, $force_download=false])
更改状态码(例如,设置为400 Bad Request)
这样做将防止响应被缓存。只有200状态码可以被缓存。
Response::setStatus(400)
输出纯文本
Response::setType('text')
输出HTML
这仅输出您在视图中构建的HTML。
Response::setType('html')
这为您构建一个页面,包括<header>
、<style>
、<script>
、<title>
元信息。您构建的HTML将放置在构建页面的<body>
中。
Response::setType('html')
注意,更改setType
将清除任何缓冲的输出.
输出JSON
Response::setType('json') Response::setContent(['example']) Response::render()
添加CSS文件和头部链接
Response\HTML::setLinks(['//maxcdn.bootstrap.ac.cn/bootstrap/3.3.1/css/bootstrap.min.css'])
您还可以指定可选属性,例如media
Response\HTML::setLinks([ '//maxcdn.bootstrap.ac.cn/bootstrap/3.3.1/css/bootstrap.min.css'=>[ 'media'=>'screen' ] ])
甚至设置完全不同的链接类型,例如favicon
Response\HTML::setLinks([ '/Assets/Shared/Svg/Favicon.svg'=>[ 'rel' =>'icon', 'sizes' =>'any', 'type' =>'image/svg+xml' ] ])
添加JS文件(本地或远程)
Response\HTML::setScripts([ '//code.jqueryjs.cn/jquery-3.3.1.slim.min.js' 'Shared/myfile.js' // this will import directly from the Bundle/Shared/Assets/Js folder ])
添加元标签
Response\HTML::setMetas(['google-site-verification'=>'google-is-watching-you'])
手动使用HTTP/2特性推送和预加载资源
Response::push([ '/Assets/Css/Shared/common.css' =>'style', '/Assets/Js/Shared/common.js' =>'script', '/Assets/Img/Website/header.wep'=>'image', ])
自动推送Response\HTML中声明的所有资源
在[response]
部分中启用参数push_assets
。这将使资源在DOM加载和解析之前被推送,从而加快网页的加载速度。
输出电子表格
您可以选择输出
- CSV
- XLS(需要phpoffice/phpspreadsheet)
- XLSX(需要phpoffice/phpspreadsheet)
Response::setType('xlsx'); // xls or csv Response::setContent([ ['A','B','C'], [1,2,3], [4,5,6] ]); // Response::setContent(Models\Accounts::_select()->execute()); Response::download('Accounts.xlsx');
注意,来自数据库的对象数组将自动转换为数组。
在CSV文件的情况下
您可以通过全局配置传递(可选的)选项。
[response] csv_delimiter = ',' csv_encloser = '"'
在XLSX文件的情况下
您可以通过全局配置传递(可选的)兼容性选项。
[phpoffice] office_2003_compatibility = 1
缓存响应的结果(所有输出类型都将被缓存,除了file
)
请注意,必须在ini配置中启用缓存,发布的Request
不被缓存,错误Response
也不被缓存。
Response::enableOutputCache($hours);
缓存命中将始终使用少于400 Ko的RAM,并且执行速度非常快,在任何合理的服务器上不到一毫秒。
Response
默认提供了一些头部信息此示例的相对较慢是由于文件系统通过wifi是NFS
< HTTP/1.1 200 OK
< X-Powered-By: PHP
< Server: None of your business
< Content-Language: fr
< Content-type: text/html; charset=utf-8
< Content-Length: 11
< X-Memory-Usage: 436.9 Ko
< X-Execution-Time: 13.5 ms
下面的示例显示了与上面相同的Hello World Response
,但来自缓存
< HTTP/1.1 200 OK
< X-Powered-By: PHP
< Server: None of your business
< Content-type: text/html; charset=utf-8
< Content-Encoding: gzip
< Content-Length: 31
< X-Footprint: 13.5 ms 436.9 Ko
< X-Environment: Prod
< Date: Mon, 19 Feb 2018 19:54:19 +0100
< Expires: Mon, 19 Feb 2 018 23:54:19 +0100
< X-Cache: hit
< X-Cache-Footprint: 1.2 ms 418.2 Ko
存储
Store接口看起来像这样
Store\Engine::has($variable); Store\Engine::put($variable, $value [, $overwrite = false]); Store\Engine::get($variable); Store\Engine::remove($variable);
您可以从不同的存储引擎中选择
Store\Cookie
Store\Filesystem
Store\Session
Store\Database
Store\Memcache
Store\Request
最后一个存储引擎仅将您的键值存储在当前请求的时间内。这些引擎中的某些具有比其他更多的功能,但所有都实现了基本接口,可以存储变量、数组或原始数据。
捆绑包配置
存储一些捆绑包特定数据
特定于包的配置应放置在 Bundles/{MyBundle}/Loader/Config.php
中(例如静态列表选项等)。注意,这些配置将与 Config.ini
+ Dev.ini
/Prod.ini
合并,因此所有配置都可通过一个接口访问: Config::{get()/set()}
Config::set($group, $key, $value);
检索值(整个包或子集)
Config::get($group); Config::get($group, $key);
电子邮件
电子邮件非常简单易用,并基于 PHPMailer 构建。它们扩展了 Entity
对象,因此具有正常数据库条目的几个额外方法,并且可以发送!
(new Models\Emails) ->set([ 'charset' =>'utf8', // (get its defaults from Config.ini) 'format' =>'text', // (get its defaults from Config.ini) 'from_name' =>'Me me me', // (get its defaults from Config.ini) 'from_email'=>'me@myself.com', // (get its defaults from Config.ini) 'subject' =>'Hello !', 'body' =>'Hello again ?', 'reply_to' =>'foo@bar.com', // fake column 'to' =>'email@domain.com', // fake column 'bcc' =>'email2@domain.com', // fake column 'cc' =>[ // fake column 'email1'=>'name1', 'email2'=>'name2' ], 'files_array'=>[ '../Private/Storage/Data/something.pdf'=>'Something.pdf' ] ]) ->send([bool $save=false]) // instead of ->send() you can ->save(), to send it later
如果电子邮件发送失败,但使用了 ->send(true),它将出现在电子邮件表中。您可以稍后发送。其 creation_date
列将被填充,但 sending_date
列将为空,这使得稍后重试变得非常简单。
(new Emails(267)) // assuming its id is 267 ->send()
即使没有显式调用 ->save()
或 ->send(true)
,由于电子邮件是从数据库中检索的,发送时,其发送日期列将被更新,并且 它将被保存。
使用模板
(new Models\Emails) ->set([ 'subject' =>'Another passionnating subject', 'to' =>[ 'jack@domain.com' =>'Jack', 'jill@domain.com' =>'Jill' ], // set a PHP view, that is searched for in the "Emails" bundle. 'view' =>'OrderConfirmation' // fake column, .php is suffixed automatically // pass variables to the view, instead of directly setting the body 'body' =>[ 'firstname' =>'Louis', 'order_number' =>'XH20210722', 'order_products'=>[$product_1,$product_2] ], // pass any number of CSS files. They will get inlined into style="" attributes of your view 'css' =>[ 'OrderConfirmationStyling', // .css is suffixed automatically ] ]) ->send(true) ->isSent() ? do_something() : do_something_else();
您的 Bundles/Emails/Views/OrderConfirmation.php
视图可能看起来像这样
<body> <h1> Order Confirmation N° <?= $order_number; ?> </h1> <p> Hi <?= $firstname; ?>, <br /> We have received your order and will ship it as soon as possible. </p> <table class="products-table"> <?php foreach($products as $product): ?> <tr> <td> <?= $product->getName(); ?> </td> </tr> <?php endforeach; ?> </table> </body>
您的 Bundles/Emails/Assets/Css/OrderConfirmationStyling.css
CSS 可能看起来像这样
body { background: #efefef; } h1 { font-size: 16px; } p { font-weight: bold; } table { padding: 25px; }
发送的电子邮件可能看起来像这样
<body style="background: #efefef"> <h1 style="font-size: 16px;"> Order Confirmation N° XH20210722 </h1> <p style="font-weight: bold;"> Hi Louis, <br /> We have received your order and will ship it as soon as possible. </p> <table style="padding: 25px;"> <tr> <td> F1 Steering Wheel signed by Louis Hamilton signed </td> </tr> <tr> <td> Driving shoes, size 43 </td> </tr> </table> </body>
始终要 严格注意 验证插入到电子邮件(或任何地方)中的用户输入。
元素
创建一个 HTML 标签(类似于 mootools' Element)
<?= Element('img',['src'=>'/img/demo.png'])->set('alt','test'); ?>
<img src="/img/demo.png" alt="test" />
创建一个具有开标签和闭标签的 HTML 元素
$quote = new Element('quote',['text'=>'Assurément, les affaires humaines ne méritent pas le grand sérieux']); $quote->adopt($image);
<quote>Assurément, les affaires humaines ne méritent pas le grand sérieux<img src="/img/demo.png" alt="test" /></quote>
设置 value
将转义其 HTML,与设置 text
类似。
表单
表单助手允许您构建和预设表单元素,例如。
<?= Form::input($name[, $value=null [, $options=[]]]); ?>
这将构建一个具有 form-control
类的两元素选择框,并预设 Peach。
<?= Form::select('sample', [] 0 => 'Apple', 1 => 'Peach' ], 1, ['class'=>'form-control']); ?>
这将构建一个具有 optgroups 的选择框。
注意,optgroup 被替换为匹配的本地化(如果有),值也被替换为匹配的本地化(如果有)。
<?= Form::select('sample', [ 'food'=>[ 0 => 'Cheese', 1 => 'Houmus', 2 => 'Mango' ], 'not_food'=>[ 3 => 'Dog', 4 => 'Cow', 5 => 'Lizard' ] ], 3); ?>
<select name="sample"> <optgroup label="food"> <option value="0">Cheese</option> <option value="1">Houmus</option> <option value="2">Mango</option> </optgroup> <optgroup label="not_food"> <option value="3" selected="selected">Dog</option> <option value="4">Cow</option> <option value="5">Lizard</option> </optgroup> </select>
从扩展了 Entity
类的对象(例如:您的模型)中可用快捷键。
根据其 ID 获取一个账户
<?= (new Accounts(1)) ->set('login', 'mylogin@example.com') ->input('login', ['data-validators'=>'required']); ?>
<input type="text" name="Accounts[login]" value="mylogin@example.com" data-validators="required"/>
可用元素列表
- 输入
- 选择
- 文本区域
- 复选框
表单元素的一般语法是: $name, $value, $options
当您从 Entity
获取表单元素时,$name
和 $value
将自动设置,只有 $options
是可用的。选择元素略有不同:$name, $list, $value, $options
要获取密码字段,例如,只需将此添加到您的属性数组中:'type'=>'password'
CRSF 保护
提供 CRSF 保护和多提交保护。
在您的 HTML 表单中间(在视图中)
<form action="" method="post"> <!-- more form here --> <?= new Polyfony\Form\Token(); ?> <!-- more form here --> </form>
在您的控制器中
Polyfony\Form\Token::enforce();
就是这样。
实例化一个 "Token" 对象生成一个唯一的令牌,将其存储在 PHP 会话中,并构建一个 HTML 输入元素。
静态 enforce 方法检查是否已通过 POST 提交请求,如果是,则检查是否存在令牌,并且它与会话中存储的令牌匹配。否则,抛出异常并将用户重定向到上一页。
Captcha 保护
提供 Captcha 提供商,它是对 gregwar/captcha 的封装。
在您的 HTML 表单中间(在视图中)
显示 captcha 图像本身
<?= new Polyfony\Form\Captcha( 5, // number of characters in the captcha (optional) 150, // width of the captcha, in px (optional) 40 // height of the captcha, in px (optional) ); ?>
显示一个用于输入 captcha 的输入框
<?= Polyfony\Form\Captcha::input([ // html attributes (optional) 'class'=>'form-control', 'placeholder'=>'Type the captcha here' ]); ?>
在您的控制器中
Polyfony\Form\Captcha::enforce();
就是这样。
实例化一个 "Captcha" 对象生成一个短语,将其存储在 PHP 会话中,并使用 gregwar/captcha builder 构建 captcha 图像。
静态 enforce 方法检查是否已通过 POST 提交请求,如果是,则检查 captcha 值是否存在,并且它与会话中存储的值匹配。否则,抛出异常并将用户重定向到上一页。您可以手动尝试/捕获异常以避免丢失用户输入的内容,在这种情况下,使用 Captcha::enforce(true)
以防止自动重定向。
数据库结构
该框架已经通过SQLite进行了广泛测试,它可能与其他引擎一起工作,但肯定可以在没有的情况下工作。没有它,您将丢失Security
、Emails
存储功能、Store\Database
引擎和Logger
的数据库功能。
数据库结构可以通过转储SQLite数据库Private/Storage/Database/Polyfony.db
来获取。可以在Private/Config/Config.ini
中将PDO驱动程序更改为MySQL
、Postgres
或ODBC
。 Postgres和ODBC不支持Query
对象。
日志记录
该框架公开了一个具有以下静态方法的Logger
类
debug(string $message, ?mixed $context) :void
(级别 0)info(string $message, ?mixed $context) :void
(级别 1)notice(string $message, ?mixed $context) :void
(级别 2)warning(string $message, ?mixed $context) :void
(级别 3)critical(string $message, ?mixed $context) :void
(级别 4)
日志可以发送到文件,或发送到您的数据库(请参阅Config.ini [logger][type]
)。日志的最低级别可配置(请参阅Config.ini [logger][level]
),默认情况下,Dev
环境配置为从0
级别记录,Prod
环境配置为从1
级别记录。临界级别日志(4
)也可以自动通过电子邮件发送(请参阅Config.ini [logger][mail]
)。
记录的消息/对象/数组也会自动在分析器中可用
示例
Logger::notice('Something not too worrying just happened'); Logger::debug('Someone did something', $some_kind_of_object); Logger::critical('Failed to contact remote API', $api_handler);
更新框架
要从项目目录中更新框架
,运行以下命令(注意向后不兼容的更改)
第一个和最后一个命令允许您在更新后保留和恢复您的composer.json
git stash git pull git stash apply
要从项目目录中更新依赖项
,运行此命令
composer update
已弃用、已停止和重命名的功能
版本历史
性能
Polyfony被设计为快速,没有妥协(> 2000 req/s)。如果将一个“便利”工具/功能实现到框架中会导致全局执行时间增加,则要么以更有效的方式实现,要么根本不实现。
安全
代码库很小,简单明了,注释丰富。它使用SensioInsight、CodeClimate、RIPS和Sonar进行审计。
编码标准
Polyfony遵循PSR-0、PSR-1、PSR-4编码标准。它不遵守PSR-2,因为它使用制表符进行缩进。