bcosca / fatfree
一个强大且易于使用的PHP微框架,旨在帮助您快速构建动态且健壮的Web应用程序!
Requires
- bcosca/fatfree-core: ^3.8
README
一个强大且易于使用的PHP微框架,旨在帮助您快速构建动态且健壮的Web应用程序!
压缩成一个 ~65KB 的单个文件,F3(我们亲切地称呼它)为您提供坚实的基础、成熟的代码库,以及编写Web应用程序的实用方法。内部是一个易于使用的Web开发工具包、高性能的URL路由和缓存引擎、内置代码高亮显示,以及多语言应用的支持。它轻量级、易于使用且速度快。最重要的是,它不会妨碍您的操作。
无论您是PHP编程新手还是专家,F3都能让您迅速开始运行。无需不必要的繁琐安装程序。无需复杂的配置。无需复杂的目录结构。现在开始以简单的方式开发Web应用程序再合适不过了!
F3支持现成的SQL和NoSQL数据库:MySQL、SQLite、MSSQL/Sybase、PostgreSQL、DB2和MongoDB。它还包含与框架一样轻量级的强大对象关系映射器,用于数据抽象和建模,无需配置。
不仅如此。F3还包含其他可选插件,扩展其功能:-
- 快速且干净的模板引擎,
- 单元测试工具包,
- 数据库管理会话,带自动CSRF保护,
- Markdown到HTML转换器,
- Atom/RSS订阅者,
- 图像处理器,
- 地理数据处理,
- Google静态地图,
- 即时JavaScript/CSS压缩器,
- OpenID(消费者),
- 自定义记录器,
- 购物篮/购物车,
- Pingback服务器/消费者,
- Unicode感知字符串函数,
- SSL/TLS上的SMTP,
- 与其他服务器通信的工具,
- 以及更多,都在这个小型超级充电包中!
与其他框架不同,F3旨在实用——而非普通。
框架背后的哲学及其对软件架构的方法是结构组件的极简主义,避免应用复杂性,在代码优雅、应用性能和程序员生产力之间取得平衡。
目录
获取最新版本!
F3具有稳定的面向企业的架构。无与伦比的性能、用户友好的功能和轻量级的足迹。您还需要什么?要获取此包,请下载此包或访问 fatfree-core 仓库以查找最新边缘版本。
对于所有Composer用户
- 使用
composer create-project bcosca/fatfree
开始一个新的项目 - 使用
composer require bcosca/fatfree-core
将fatfree添加到现有项目中
强烈建议有经验的用户使用最新版本开发新应用程序,以利用更新的代码库和持续改进。
请访问FatFreeFramework.com
最新的用户指南和详细的API文档,包含大量代码示例和图形指南,可以在 fatfreeframework.com/ 找到。
当然,这个便捷的在线参考资料是由F3驱动的!它展示了该框架的能力和性能。现在就来看看吧。如果您想直接在GitHub上阅读,可以在github.com/F3Community/F3com-data找到网站内容。
入门
一个设计师知道他达到完美的时刻,不是当他没有什么可以添加的时候,而是当他没有什么可以删除的时候。 —— 安东尼·德·圣埃克苏佩里
无脂肪框架可以轻松快速地构建整个网站。它具有现代JavaScript工具包和库相同的强大和简洁,F3可以帮助您编写外观更佳且更可靠的PHP程序。只需一眼您的PHP源代码,任何人都会发现它很容易理解,您可以在这么少的代码行中完成多少工作,以及结果有多强大。
F3是周围文档最完善的框架之一。学习它几乎不花钱。没有难以导航的目录结构,也没有令人反感的编程步骤。没有一大堆配置选项,只是为了在浏览器中显示'Hello, World'
。无脂肪框架给您提供了大量的自由——以及风格——以轻松和高效的方式完成更多工作。
F3的声明式编程方法使得新手和专家都容易理解PHP代码。如果您熟悉Ruby编程语言,您会注意到Fat-Free与Sinatra微框架之间的相似之处,因为它们都采用简单的领域特定语言来构建RESTful网络服务。但与Sinatra及其PHP版本(如Fitzgerald、Limonade、Glue等)不同,Fat-Free不仅处理路由和请求,视图可以是任何形式,如纯文本、HTML、XML或电子邮件消息。该框架附带快速且易于使用的模板引擎。F3还与其他模板引擎无缝协同工作,包括Twig、Smarty和PHP本身。模型通过F3的数据映射器和SQL助手与数据库引擎进行更复杂的交互。其他插件进一步扩展了基本功能。这是一个完整的网络开发框架——功能强大!
suffice to say - see for yourself
将发行版的内容解压到您的硬盘上的任何位置。默认情况下,框架文件和可选插件位于lib/
路径。您可以按任何您想要的方式组织目录结构。您可以移动默认文件夹到一个非Web可访问的路径以获得更好的安全性。删除您不需要的插件。您总是可以在以后恢复它们,并且F3会自动检测它们的存在。
重要:如果您的应用程序使用APC、Memcached、WinCache、XCache或文件系统缓存,在用新版本的框架覆盖旧版本之前,请先清除所有缓存条目。
确保您正在运行正确的PHP版本。F3不支持低于PHP 7.2的版本。由于新的语言结构和闭包/匿名函数不受过时PHP版本的支持,您将在各个地方遇到语法错误(假阳性)。
/path/to/php -v
PHP会告诉您正在运行哪个特定版本,您应该得到类似以下内容:
PHP 7.4.21 (cli) (built: Jul 27 2021 15:56:07) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Xdebug v2.9.8, Copyright (c) 2002-2020, by Derick Rethans
如有必要,请升级,如果您已升级到PHP 7.4或更高版本,请返回此处。Fatfree至少需要PHP 7.2才能运行。如果您需要托管服务提供商,请尝试以下服务之一
Hello, World: Less Than A Minute Fat-Free Recipe
是时候开始编写我们的第一个应用程序了:
$f3 = require('path/to/base.php'); $f3->route('GET /', function() { echo 'Hello, world!'; } ); $f3->run();
在第一行将base.php
的前缀设置为适当的路径。将上面的代码片段保存为index.php
到您的Web根目录中。我们已经编写了第一个网页。
使用composer?那么只需运行composer require bcosca/fatfree
并使用以下内容
require 'vendor/autoload.php'; $f3 = \Base::instance(); $f3->route('GET /', function() { echo 'Hello, world!'; } ); $f3->run();
第一条命令告诉PHP解释器,您希望框架的功能和特性可用于您的应用程序。使用$f3->route()
方法,告知Fat-Free在斜杠(/
)指示的相对URL处有网页可用。任何访问位于http://www.example.com/
的您的网站的访客都会看到'Hello, world!'
消息,因为URL /
等同于根页面。要创建从根页面分支出来的路由,如http://www.example.com/inside/
,您可以定义另一个路由,使用简单的GET /inside
字符串。
上面描述的路由告诉框架只有在接收到使用HTTP GET
方法的URL请求时才渲染页面。包含表单的更复杂的网站使用其他HTTP方法,如POST
,您也可以将其作为$f3->route()
规范的一部分来实现。
如果框架看到对位于根URL /
的您的网页的传入请求,它将自动将请求路由到回调函数,该函数包含处理请求和渲染适当HTML所需代码。在此示例中,我们仅向用户的Web浏览器发送字符串'Hello, world!'
。
因此,我们已建立了第一条路由。但这不会做很多事情,除了让F3知道有一个处理程序将处理它,并在用户的Web浏览器上显示一些文本。如果您网站上有很多页面,您需要为每个组设置不同的路由。现在,让我们保持简单。为了指示框架开始等待请求,我们发出$f3->run()
命令。
无法运行示例?如果您在服务器上运行此简单程序时遇到问题,您可能需要稍微调整一下您的Web服务器设置。请参阅以下部分中的示例Apache配置(以及Nginx和Lighttpd等效配置)。
仍然有问题?请确保在脚本中的任何输出之前,将$f3 = require('path/to/base.php');
赋值。因为base.php
修改HTTP头,所以在此赋值之前输出的任何字符都会导致错误。
路由引擎
概述
我们的第一个示例并不难理解,对吧?如果您喜欢在Fat-Free汤中加入更多风味,可以在$f3->run()
命令之前插入另一个路由:
$f3->route('GET /about', function() { echo 'Donations go to a local charity... us!'; } );
您不想在全局命名空间中充斥着函数名?Fat-Free识别将路由处理程序映射到OOP类和方法的不同方式:
class WebPage { function display() { echo 'I cannot object to an object'; } } $f3->route('GET /about','WebPage->display');
HTTP请求也可以路由到静态类方法:
$f3->route('GET /login','Auth::login');
传递的参数始终作为第二个参数提供
$f3->route('GET /hello/@name','User::greet'); class User { public static function greet($f3, $args) { //$args is type of Array echo "Hello " . $args['name']; } }
如果提供的名称参数为foo
(/hello/foo),则以下输出将显示
Hello foo
路由和标记
作为Fat-Free强大的领域特定语言(DSL)的演示,您可以指定单个路由来处理不同的可能性:
$f3->route('GET /brew/@count', function($f3) { echo $f3->get('PARAMS.count').' bottles of beer on the wall.'; } );
此示例显示了如何我们可以指定标记@count
来表示URL的一部分。框架将为匹配/brew/
前缀的任何请求URL提供服务,如/brew/99
、/brew/98
等。这将分别显示'99 bottles of beer on the wall'
和'98 bottles of beer on the wall'
。Fat-Free还会接受对/brew/unbreakable
的页面请求。(预期此操作将显示'unbreakable bottles of beer on the wall'
。)当指定此类动态路由时,Fat-Free会自动将捕获的字符串的值填充到全局PARAMS
数组变量中。在回调函数内的$f3->get()
调用检索框架变量的值。您当然可以在代码中将此方法应用于表示或业务逻辑的一部分。但我们将更详细地讨论这一点。
请注意,Fat-Free支持数组点表示法。在代码中,您可以使用PARAMS['count']
常规表示法代替,这容易出错且括号不平衡。在视图和模板中,框架允许使用与JavaScript类似的@PARAMS.count
表示法。(我们稍后会介绍视图和模板。)
这是访问请求模式中令牌的另一种方法:
$f3->route('GET /brew/@count', function($f3,$params) { echo $params['count'].' bottles of beer on the wall.'; } );
您可以使用星号(*
)接受/brew
路由之后的所有URL——如果您对路径的其余部分并不关心的话:
$f3->route('GET /brew/*', function() { echo 'Enough beer! We always end up here.'; } );
一个需要考虑的重要点:如果您在同一应用程序中同时使用GET /brew/@count
和GET /brew/*
,这将使Fat-Free(以及您自己)感到困惑。请使用其中一个。另外,Fat-Free将GET /brew
视为与路由GET /brew/@count
不同且独立。每个都可以有不同的路由处理程序。
动态网站
等等——在所有前面的例子中,我们从未在我们的硬盘上真正创建任何目录来存储这些路由。简短的回答是:我们不必这样做。所有F3路由都是虚拟的。它们不反映我们的硬盘文件夹结构。如果您有程序或静态文件(图像、CSS等),这些文件不使用框架——只要这些文件的路径不与您的应用程序中定义的任何路由冲突——只要服务器配置正确,您的Web服务器软件就会将它们发送到用户的浏览器。
命名路由
当您定义一个路由时,您可以为其分配一个名称。在您的代码和模板中使用路由名称,而不是键入URL。然后,如果您需要更改URL以满足市场营销主管的要求,您只需在定义路由的地方进行更改即可。路由名称必须遵循PHP变量命名规则(没有点、破折号或连字符)。
让我们为路由命名:
$f3->route('GET @beer_list: /beer', 'Beer->list');
名称插入到路由动词(本例中的GET
)之后,前面有一个@
符号,并且与URL部分由冒号:
符号分隔。如果您在冒号之后插入一个空格可以使代码更容易阅读(如下所示)。
要在模板中访问命名路由,将命名路由的值作为ALIASES
蜂巢数组的关键字来获取:
<a href="{{ @ALIASES.beer_list }}">View beer list</a>
要重定向访客到新URL,在reroute()
方法中调用命名路由,如下所示:
// a named route is a string value $f3->reroute('@beer_list'); // note the single quotes
如果您在路由中使用令牌,F3将用它们的当前值替换这些令牌。如果您在调用reroute之前想更改令牌的值,将其作为第二个参数传递。
$f3->route('GET @beer_list: /beer/@country', 'Beer->bycountry'); $f3->route('GET @beer_list: /beer/@country/@village', 'Beer->byvillage'); // a set of key-value pairs is passed as argument to named route $f3->reroute('@beer_list(@country=Germany)'); // if more than one token needed $f3->reroute('@beer_list(@country=Germany,@village=Rhine)');
请记住,如果您有不符合RFC 1738指南的字符,请使用urlencode()
对您的参数进行编码。
PHP的内置Web服务器
PHP的最新稳定版本有一个内置的Web服务器。使用以下配置启动它:
php -S localhost:80 -t /var/www/
上面的命令将启动所有请求路由到Web根/var/www
。如果收到对文件或文件夹的HTTP请求,PHP将在其中查找它,如果找到,将其发送到浏览器。否则,PHP将加载默认的index.php
(包含您的F3启用代码)。
示例Apache配置
如果您使用Apache,请确保在apache.conf(或httpd.conf)文件中激活URL重写模块(mod_rewrite)。您还应该创建一个包含以下内容的.htaccess
文件:
# Enable rewrite engine and route requests to framework RewriteEngine On # Some servers require you to specify the `RewriteBase` directive # In such cases, it should be the path (relative to the document root) # containing this .htaccess file # # RewriteBase / RewriteRule ^(tmp)\/|\.ini$ - [R=404] RewriteCond %{REQUEST_FILENAME} !-l RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [L,QSA] RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
该脚本告诉Apache,每当收到一个HTTP请求,如果找不到物理文件(!-f
)、路径(!-d
)或符号链接(!-l
),它应将控制权传递给index.php
,其中包含我们的主要/前端控制器,然后它反过来调用框架。
包含上述Apache指令的.htaccess文件
应始终位于与index.php
相同的文件夹中。
您还需要设置Apache,以便它知道index.php
在您的硬盘中的物理位置。一个典型的配置是:
DocumentRoot "/var/www/html" <Directory "/var/www/html"> Options -Indexes +FollowSymLinks +Includes AllowOverride All Order allow,deny Allow from All </Directory>
如果您同时开发多个应用程序,虚拟主机配置更容易管理:
NameVirtualHost * <VirtualHost *> ServerName site1.com DocumentRoot "/var/www/site1" <Directory "/var/www/site1"> Options -Indexes +FollowSymLinks +Includes AllowOverride All Order allow,deny Allow from All </Directory> </VirtualHost> <VirtualHost *> ServerName site2.com DocumentRoot "/var/www/site2" <Directory "/var/www/site2"> Options -Indexes +FollowSymLinks +Includes AllowOverride All Order allow,deny Allow from All </Directory> </VirtualHost>
每个ServerName
(例如我们的示例中的site1.com
和site2.com
)必须在您的/etc/hosts
文件中列出。在Windows上,您应该编辑C:/WINDOWS/system32/drivers/etc/hosts
。可能需要重新启动才能生效更改。然后您可以将您的网络浏览器指向地址http://site1.com
或http://site2.com
。虚拟主机使您的应用程序部署更加容易。
示例Nginx配置
对于Nginx服务器,这里有一些推荐的配置(用您环境中的FastCGI PHP设置替换ip_address:port):
server { root /var/www/html; location / { index index.php index.html index.htm; try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass ip_address:port; include fastcgi_params; } }
示例Lighttpd配置
Lighttpd服务器的配置方式类似:
$HTTP["host"] =~ "www\.example\.com$" {
url.rewrite-once = ( "^/(.*?)(\?.+)?$"=>"/index.php/$1?$2" )
server.error-handler-404 = "/index.php"
}
示例IIS配置
安装URL重写模块以及与您的Windows版本相应的.NET框架。然后在您的应用程序根目录中创建一个名为web.config
的文件,其内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Application" stopProcessing="true">
<match url=".*" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
重定向
让我们回到编码。您可以声明一个页面已废弃,并将您的访客重定向到另一个网站/页面:
$f3->route('GET|HEAD /obsoletepage', function($f3) { $f3->reroute('/newpage'); } );
如果有人尝试使用HTTP GET或HEAD请求访问URLhttp://www.example.com/obsoletepage
,框架将用户重定向到URL:http://www.example.com/newpage
,如上例所示。您也可以将用户重定向到另一个网站,例如$f3->reroute('http://www.anotherexample.org/');
。
重定向在您需要对您的网站进行一些维护工作时特别有用。您可以有一个路由处理程序,通知您的访客您的网站将暂时离线。
HTTP重定向是必不可少的,但它们也可能很昂贵。尽可能避免使用$f3->reroute()
将用户发送到同一网站上的另一个页面,如果您可以通过调用处理目标路由的函数或方法来控制应用程序的流程。然而,这种方法不会更改用户网络浏览器地址栏上的URL。如果这不是您想要的行为,并且您确实需要将用户发送到另一个页面,例如表单提交成功或用户经过认证后,Fat-Free发送一个HTTP 302 Found
头部。对于所有其他尝试重定向到另一个页面或网站的尝试,框架发送一个HTTP 301 Moved Permanently
头部。
触发404
在运行时,Fat-Free会在它看到传入的HTTP请求不匹配您应用程序中定义的任何路由时自动生成HTTP 404错误。但是,有时您需要自己触发它。
例如,一个定义为GET /dogs/@breed
的路由。您应用程序的逻辑可能涉及在数据库中进行搜索并尝试检索与传入HTTP请求中@breed
值对应的记录。由于Fat-Free会因为存在@breed
令牌而接受/dogs/
前缀之后的所有值,因此当程序在我们的数据库中找不到匹配项时,显示HTTP 404 Not Found消息就变得程序化成为必要。为此,请使用以下命令:
$f3->error(404);
表现层状态转换(ReST)
Fat-Free的架构基于HTTP URI代表抽象Web资源(不仅限于HTML)的概念,每个资源都可以从一个应用程序状态移动到另一个状态。因此,F3对您应用程序的结构方式没有限制。如果您更喜欢使用模型-视图-控制器模式,F3可以帮助您将应用程序组件分隔开来以坚持这一范式。另一方面,该框架还支持资源-方法-表示模式,实现它更为直接。
以下是一个ReST接口的示例:
class Item { function get() {} function post() {} function put() {} function delete() {} } $f3=require('lib/base.php'); $f3->map('/cart/@item','Item'); $f3->run();
Fat-Free的$f3->map()
方法通过将路由中的HTTP方法映射到对象或PHP类的等效方法,提供了一个ReST接口。如果你的应用程序接收到类似GET /cart/123
的HTTP请求,Fat-Free将自动将控制权传递给对象或类的get()
方法。另一方面,POST /cart/123
请求将被路由到Item
类的post()
方法。
注意:浏览器不实现HTTP中的PUT
和DELETE
方法。这些以及其他ReST方法(如HEAD
和CONNECT
)只能通过向服务器发出AJAX调用才能访问。
如果框架收到对路由的HTTP请求,而该路由映射到的类没有实现方法(可能你在路由映射中犯了错误,或者该方法尚未编写),它将生成HTTP 405 Method Not Allowed
错误。
如果客户端请求对URL资源的HTTP OPTIONS
,F3将响应适当的HTTP头,以指示允许对该资源使用哪些方法(HEAD、GET、PUT等)。框架不会将OPTIONS
请求映射到类。
F3自动加载器
Fat-Free有一种方法,只在需要时加载类,这样它们就不会占用比应用程序特定部分所需的更多内存。你也不必编写长长的include
或require
语句来加载存储在不同文件和不同位置的不同PHP类。框架可以为你自动完成这项工作。只需将文件(每个文件一个类)保存在一个文件夹中,并在调用类中的方法时告诉框架自动加载相应的文件:
$f3->set('AUTOLOAD','autoload/');
你可以通过更改AUTOLOAD
全局变量的值来为自动加载的类指定不同的位置。你还可以有多个自动加载路径。如果你的类组织在不同的文件夹中,你可以指示框架在调用静态方法或实例化对象时自动加载适当的类。这样修改AUTOLOAD
变量:
$f3->set('AUTOLOAD','admin/autoload/; user/autoload/; default/');
重要:除了.php扩展名外,类名和文件名必须相同,以便框架正确地自动加载你的类。此文件的基名必须与你的类调用相同,例如,F3在检测到应用程序中的new Foo\BarBaz
语句时,将寻找Foo/BarBaz.php
或foo/barbaz.php
。
使用命名空间
AUTOLOAD
允许类层次结构位于类似命名的子文件夹中,因此如果你想要框架自动加载按以下方式调用的命名空间类:
$f3->set('AUTOLOAD','autoload/'); $obj=new Gadgets\iPad;
你可以创建一个遵循相同结构的文件夹层次结构。假设/var/www/html/
是你的Web根目录,那么F3将在/var/www/html/autoload/gadgets/ipad.php
中寻找该类。文件ipad.php
应包含以下最小代码:
namespace Gadgets; class iPad {}
记住:Fat-Free中的所有目录名必须以斜杠结尾。你可以按以下方式为自动加载器指定搜索路径:
$f3->set('AUTOLOAD','main/;aux/');
将路由映射到命名空间类
F3是一个命名空间感知框架,允许你将命名空间类中的方法用作路由处理程序,有几种方法可以实现这一点。要调用静态方法:
$f3->set('AUTOLOAD','classes/'); $f3->route('GET|POST /','Main\Home::show');
上面的代码将在Main
命名空间内的Home
类中调用静态的show()
方法。如果Home
类保存在文件夹classes/main/home.php
中,则它可以自动加载。
如果你更愿意与对象一起工作:
$f3->route('GET|POST /','Main\Home->show');
将在运行时实例化Home
类,并在之后调用show()
方法。
事件处理程序
F3有几个路由事件监听器,可能有助于您改善控制器类的流程和结构。假设您定义了一个如下所示的路由:
$f3->route('GET /','Main->home');
如果应用程序收到与上述路由匹配的HTTP请求,F3会实例化Main
,但在执行home()
方法之前,框架会寻找这个类中名为beforeRoute()
的方法。如果找到了,F3会在控制权传递给home()
方法之前运行beforeRoute()
事件处理器中的代码。一旦完成,框架会寻找afterRoute()
事件处理器。就像beforeRoute()
一样,如果定义了该方法,则执行它。
动态路由处理器
这是F3的另一个优点:
$f3->route('GET /products/@action','Products->@action');
如果您的应用程序收到对,例如/products/itemize
的请求,F3将提取URL中的'itemize'
字符串,并将其传递给路由处理器中的@action
令牌。然后,F3会寻找名为Products
的类并执行itemize()
方法。
动态路由处理器可能有各种形式:
// static method $f3->route('GET /public/@genre','Main::@genre'); // object mode $f3->route('GET /public/@controller/@action','@controller->@action');
F3在运行时如果无法将控制权传递到当前路由相关联的类或方法(即未定义的类或方法),会触发一个HTTP 404 Not Found
错误。
AJAX和同步请求
路由模式可能包含修饰符,指示框架根据HTTP请求的类型来基于其路由决策:
$f3->route('GET /example [ajax]','Page->getFragment'); $f3->route('GET /example [sync]','Page->getFull');
如果服务器收到一个带有X-Requested-With: XMLHttpRequest
头(AJAX对象)的请求,则第一个语句将仅将HTTP请求路由到Page->getFragment()
回调。如果检测到普通(同步)请求,F3将简单地跳到下一个匹配的模式,在这种情况下,它执行Page->getFull()
回调。
如果没有在路由模式中定义修饰符,那么AJAX和同步请求类型都会被路由到指定的处理器。
路由模式修饰符也被$f3->map()
识别。
框架变量
基本用法
Fat-Free中定义的变量是全局的,即它们可以被任何MVC组件访问。框架的全局变量与PHP的全局变量不同。F3中名为content
的变量与PHP的$content
不同。F3是一种独立的领域特定语言,并维护自己的符号表以用于系统和应用程序变量。框架,就像每个设计良好的面向对象程序一样,不会使用可能与应用程序冲突的常量、变量、函数或类来污染PHP的全局命名空间。与其他框架不同,F3不使用PHP的define()
语句。所有框架常量都限制在类中。
要为Fat-Free变量赋值
$f3->set('var',value); // or $f3->var=value; $f3->set('hello.world','good morning'); // translates to: 'hello' == array('world'=>'good morning') $f3->{'hello.world'}='good morning'; // same as prior statement
注意:Fat-Free变量接受所有PHP数据类型,包括对象和匿名函数。
要同时设置几个变量
$f3->mset( [ 'foo'=>'bar', 'baz'=>123 ] );
要检索名为var
的框架变量的值:
echo $f3->get('var'); // or echo $f3->var;
如果您不再需要Fat-Free变量(取消使用它,以免干扰您的其他函数/方法),请使用以下方法:
$f3->clear('var'); // or unset($f3->var);
要检查变量是否已之前定义:
$f3->exists('var') // isset($f3->var)
全局变量
F3维护自己的符号表,用于框架和应用程序变量,这些变量独立于PHP的。一些变量映射到PHP全局变量。Fat-Free的SESSION
等同于$_SESSION
,而REQUEST
映射到$_REQUEST
。建议使用框架变量而不是PHP变量来帮助您在不同函数、类和方法之间传输数据。它们还有其他优点:
- 您可以直接在模板中使用框架变量。
- 您不需要在每个函数或方法中使用全局关键字来指示PHP引用当前作用域之外的变量。所有F3变量都是应用程序的全局变量。
- 设置类似
SESSION
的 PHP 全局变量的 Fat-Free 等价物也会更改 PHP 的底层$_SESSION
。更改后者也会更改框架对应的变量。
Fat-Free 不仅维护变量的简单存储,还可以自动化会话管理和其他操作。通过 F3 的 SESSION
变量赋值或检索值会自动启动会话。如果你直接使用 $_SESSION
(或与会话相关的函数),而不是框架变量 SESSION
,则你的应用程序将负责管理会话。
一般来说,框架变量在 HTTP 请求之间不会持久化。只有映射到 PHP 的全局变量 $_SESSION
和 $_COOKIE
的 SESSION
和 COOKIE
(及其元素)才免于 HTTP 的无状态性。
Fat-Free 内部使用一些预定义的全局变量,你当然可以在应用程序中使用它们。确保你知道自己在做什么。更改一些 Fat-Free 全局变量可能会导致意外的框架行为。
框架提供了一些变量来帮助你组织文件和目录结构。我们已经看到了如何通过使用 AUTOLOAD
来自动化类加载。还有一个 UI
全局变量,其中包含指向你的 HTML 视图/模板位置的路径。《DEBUG》是另一个你将在应用程序开发过程中经常使用的变量,用于设置错误跟踪的详细程度。
如果你需要内置框架变量的完整列表,请参阅 快速参考。
命名规则
框架变量可以包含任意数量的字母、数字和下划线。它必须以字母开头,并且不应该有空格。变量名区分大小写。
F3 使用全大写字母为内部预定义的全局变量。没有任何阻止你在自己的程序中使用全大写变量名,但作为一般规则,在设置自己的变量时坚持使用小写(或 camelCase),这样你可以避免与当前和未来的框架版本发生任何可能的冲突。
你不应该使用 PHP 保留字,如 if
、for
、class
、default
等,作为框架变量名。这可能会导致不可预测的结果。
处理字符串和数组变量
F3 还提供了一些工具来帮助你处理框架变量。
$f3->set('a','fire'); $f3->concat('a','cracker'); echo $f3->get('a'); // returns the string 'firecracker' $f3->copy('a','b'); echo $f3->get('b'); // returns the same string: 'firecracker'
F3 还提供了一些处理数组变量的基本方法:
$f3->set('colors',['red','blue','yellow']); $f3->push('colors','green'); // works like PHP's array_push() echo $f3->pop('colors'); // returns 'green' $f3->unshift('colors','purple'); // similar to array_unshift() echo $f3->shift('colors'); // returns 'purple' $f3->set('grays',['light','dark']); $result=$f3->merge('colors','grays'); // merges the two arrays
自定义目录结构
与其他具有刚性文件夹结构的框架不同,F3 给你很多灵活性。你可以拥有以下这样的文件夹结构(括号中的大写字母代表需要调整的 F3 框架变量):
/ (your Web root, where index.php is located)
app/ (application files)
dict/ (LOCALES, optional)
controllers/
logs/ (LOGS, optional)
models/
views/ (UI)
css/
js/
lib/ (you can store base.php here)
tmp/ (TEMP, used by the framework)
cache/ (CACHE)
请随意组织你的文件和目录。只需设置适当的 F3 全局变量。如果你想创建一个非常安全的网站,Fat-Free 甚至允许你将所有文件存储在非 Web 可访问的目录中。唯一的要求是你将 index.php
、.htaccess
和你的公共文件(如 CSS、JavaScript、图像等)放在浏览器可访问的路径中。
关于 F3 错误处理器
Fat-Free 会生成自己的 HTML 错误页面,其中包含堆栈跟踪以帮助您进行调试。以下是一个示例:
内部服务器错误
strpos() 需要至少 2 个参数,但只提供了 0 个
• var/html/dev/main.php:96 strpos() • var/html/dev/index.php:16 Base->run()
如果你觉得它有点太简单,或者希望在发生错误时执行其他操作,你可以创建自己的自定义错误处理器:
$f3->set('ONERROR', function($f3) { // custom error handler code goes here // use this if you want to display errors in a // format consistent with your site's theme echo $f3->get('ERROR.status'); } );
F3 维护一个包含应用程序中最新错误详情的全局变量。变量 ERROR
是一个结构如下所示的数组:
ERROR.code - displays the error code (404, 500, etc.)
ERROR.status - header and page title
ERROR.text - error context
ERROR.trace - stack trace
在开发应用程序时,最好将调试级别设置为最大,以便您可以跟踪所有错误到其根本原因:
$f3->set('DEBUG',3);
只需在应用程序的引导序列中插入此命令即可。
当您的应用程序准备发布时,只需从应用程序中删除该语句,或将其替换为:
$f3->set('DEBUG',0);
这将抑制任何系统生成的HTML错误页面中的堆栈跟踪输出(因为它不打算让您的网站访客看到)。
DEBUG
的值可以从0(抑制堆栈跟踪)到3(最详细)。
别忘了!堆栈跟踪可能包含路径、文件名、数据库命令、用户名和密码。如果在生产环境中未能将全局变量DEBUG
设置为0,您的网站可能会暴露于不必要的安全风险之中。
配置文件
如果您的应用程序需要用户可配置,F3提供了一个方便的方法来读取配置文件以设置您的应用程序。这样,您和您的用户可以调整应用程序,而无需修改任何PHP代码。
而不是创建包含以下示例代码的PHP脚本:
$f3->set('num',123); $f3->set('str','abc'); $f3->set('hash',['x'=>1,'y'=>2,'z'=>3]); $f3->set('items',[7,8,9]); $f3->set('mix',['this',123.45,FALSE]);
您可以构建一个执行相同操作的配置文件:
[globals] num=123 ; this is a regular string str=abc ; another way of assigning strings str="abc" ; this is an array hash[x]=1 hash[y]=2 hash[z]=3 ; dot-notation is recognized too hash.x=1 hash.y=2 hash.z=3 ; this is also an array items=7,8,9 ; array with mixed elements mix="this",123.45,FALSE
而不是在代码中使用冗长的$f3->set()
语句,您可以指示框架加载一个配置文件作为代码替代。让我们将上述文本保存为setup.cfg。然后我们可以通过以下简单的方式调用它:
$f3->config('setup.cfg');
字符串值不需要引号,除非您想包含前导或尾随空格。如果逗号应被视为字符串的一部分,请使用双引号包围字符串 - 否则,该值将被视为数组(逗号用作数组元素分隔符)。字符串可以跨越多行:
[globals] str="this is a \ very long \ string"
F3还允许您在配置文件中定义HTTP路由:
[routes] GET /=home GET /404=App->page404 GET /page/@num=Page->@controller
路由映射也可以在配置文件中定义:
[maps] /blog=Blog\Login /blog/@controller=Blog\@controller
需要[globals]
、[routes]
和[maps]
部分标题。您可以将这两个部分合并到一个配置文件中 - 虽然将[routes]
和[maps]
放在单独的文件中是推荐的。这样,您可以允许最终用户修改一些特定于应用程序的标志,同时限制他们干预您的路由逻辑。
视图和模板
关注点的分离
类似于HTML页面的用户界面应独立于底层与路由和业务逻辑相关的PHP代码。这是MVC范式的基础。将<h3>
转换为<p>
的基本修订不应要求更改您的应用程序代码。同样,将简单路由GET /about
转换为GET /about-us
也不应影响您的用户界面和业务逻辑(MVC中的视图和模型或RMR中的表示和方法)。
将编程结构和用户界面组件混合在单个文件中,就像意大利面代码一样,会使未来的应用程序维护变得是一场噩梦。
PHP作为模板引擎
F3支持PHP作为模板引擎。请看以下保存为template.htm
的HTML片段:
<p>Hello, <?php echo $name; ?>!</p>
如果您的服务器启用了短标签,这也应该可以工作:
<p>Hello, <?= $name ?></p>
要显示此模板,您可以拥有如下PHP代码(存储在与模板分开的文件中):
$f3=require('lib/base.php'); $f3->route('GET /', function($f3) { $f3->set('name','world'); $view=new View; echo $view->render('template.htm'); // Previous two lines can be shortened to:- // echo View::instance()->render('template.htm'); } ); $f3->run();
使用PHP作为模板引擎的唯一问题是,由于这些文件中嵌入的PHP代码,需要付出有意识的努力来遵守关注点分离的指南,并抵制将业务逻辑与您的用户界面混合的诱惑。
F3模板语言的快速浏览
作为PHP的替代,您可以使用F3的模板引擎。上述HTML片段可以重写为:
<p>Hello, {{ @name }}!</p>
并查看此模板所需的代码:
$f3=require('lib/base.php'); $f3->route('GET /', function($f3) { $f3->set('name','world'); $template=new Template; echo $template->render('template.htm'); // Above lines can be written as:- // echo Template::instance()->render('template.htm'); } ); $f3->run();
类似于用于在URL中捕获变量的路由令牌(还记得上一节中提到的 GET /brew/@count
示例吗?),F3模板令牌以@
符号开头,后跟一系列用花括号包围的字母和数字。第一个字符必须是字母。模板令牌与框架变量一一对应。框架自动将令牌替换为相同名称变量的存储值。
在我们的示例中,F3将模板中的@name
令牌替换为我们分配给name变量的值。在运行时,上述代码的输出将是:
<p>Hello, world</p>
担心F3模板的性能?在运行时,框架第一次通过$template->render()
显示F3模板时,将解析并编译/转换F3模板为PHP代码。然后,框架在随后的所有调用中使用此编译后的代码。因此,性能应该与PHP模板相同,如果不是更好的话,这是因为模板编译器在涉及更复杂的模板时进行了代码优化。
无论您使用PHP的模板引擎还是F3的模板引擎,如果您在服务器上安装了APC、WinCache或XCache,模板渲染可以显著更快。
如前所述,框架变量可以存储任何PHP数据类型。但是,如果不小心,在F3模板中使用非标量数据类型可能会产生奇怪的结果。花括号中的表达式将始终被评估并转换为字符串。您应该将用户界面变量限制为简单的标量:string
、integer
、boolean
或float
数据类型。
但数组怎么办?Fat-Free可以识别数组,您可以在模板中使用它们。您可能会有以下这样的内容:
<p>{{ @buddy[0] }}, {{ @buddy[1] }}, and {{ @buddy[2] }}</p>
并在在服务模板之前,在您的PHP代码中填充@buddy
数组:
$f3->set('buddy',['Tom','Dick','Harry']);
然而,如果您只是将{{ @buddy }}
插入模板中,PHP会将其替换为'Array'
,因为它是将令牌转换为字符串。另一方面,PHP将在运行时生成一个Array to string conversion
警告。
F3允许您在模板中嵌入表达式。这些表达式可以采取各种形式,如算术计算、布尔表达式、PHP常量等。以下是一些示例:
{{ 2*(@page-1) }} {{ (int)765.29+1.2e3 }} <option value="F" {{ @active?'selected="selected"':'' }}>Female</option> {{ var_dump(@xyz) }} <p>That is {{ preg_match('/Yes/i',@response)?'correct':'wrong' }}!</p> {{ @obj->property }}
关于数组表达式的附加说明:请注意,@foo.@bar
是字符串连接($foo.$bar
),而@foo.bar
转换为$foo['bar']
。如果您想要的是$foo[$bar]
,请使用@foo[@bar]
的正则表示法。
框架变量还可以包含匿名函数
$f3->set('func', function($a,$b) { return $a.', '.$b; } );
如果您指定以下表达式,F3模板引擎将按预期解释令牌
{{ @func('hello','world') }}
模板中的模板
简单的变量替换是所有模板引擎都有的。Fat-Free还有更多:
<include href="header.htm" />
该指令将把header.htm模板的内容嵌入到指令声明的确切位置。您也可以有动态内容,形式如下:
<include href="{{ @content }}" />
这种模板指令的一个实际用途是,当您有多个具有共同HTML布局但内容不同的页面时。指导框架将子模板插入主模板就像编写以下PHP代码一样简单:
// switch content to your blog sub-template $f3->set('content','blog.htm'); // in another route, switch content to the wiki sub-template $f3->set('content','wiki.htm');
子模板可以包含任意数量的指令。F3允许无限嵌套模板。
您可以使用除了.htm或.html文件扩展名之外的文件名,但在开发和调试阶段更容易在Web浏览器中预览它们。模板引擎不仅限于渲染HTML文件。实际上,您可以使用模板引擎来渲染其他类型的文件。
<include>
指令还有一个可选的if
属性,因此您可以在插入子模板之前指定需要满足的条件:
<include if="{{ count(@items) }}" href="items.htm" />
排除段
在编写/调试由F3驱动的程序和设计模板的过程中,有时可能需要禁用一组HTML的显示,这样做可能很方便。为此,您可以使用<exclude>
指令:
<exclude> <p>A chunk of HTML we don't want displayed at the moment</p> </exclude>
这就像HTML注释标签<!-- comment -->
,但<exclude>
指令会在模板渲染后使HTML块完全不可见。
以下是另一种排除模板内容或添加注释的方法:
{* <p>A chunk of HTML we don't want displayed at the moment</p> *}
条件段
另一个有用的模板特性是<check>
指令。它允许您根据某个条件的评估嵌入HTML片段。以下是一些示例:
<check if="{{ @page=='Home' }}"> <false><span>Inserted if condition is false</span></false> </check> <check if="{{ @gender=='M' }}"> <true> <div>Appears when condition is true</div> </true> <false> <div>Appears when condition is false</div> </false> </check>
您可以拥有任意数量的嵌套<check>
指令。
if属性内的F3表达式等于NULL
、空字符串、布尔值FALSE
、空数组或零时,会自动调用<false>
。如果您的模板中没有<false>
块,那么<true>
开始和结束标签是可选的:
<check if="{{ @loggedin }}"> <p>HTML chunk to be included if condition is true</p> </check>
重复段
Fat-Free还可以处理重复的HTML块:
<repeat group="{{ @fruits }}" value="{{ @fruit }}"> <p>{{ trim(@fruit) }}</p> </repeat>
<repeat>
指令中的group
属性@fruits
必须是一个数组,并且应在您的PHP代码中相应设置:
$f3->set('fruits',['apple','orange ',' banana']);
在您的应用程序代码中将值分配给@fruit
没有任何好处。Fat-Free忽略它可能具有的任何预设值,因为它在遍历组时使用该变量来表示当前项。上述HTML模板片段和相应的PHP代码的输出如下:
<p>apple</p> <p>orange</p> <p>banana</p>
框架允许无限嵌套<repeat>
块:
<repeat group="{{ @div }}" key="{{ @ikey }}" value="{{ @idiv }}"> <div> <p><span><b>{{ @ikey }}</b></span></p> <p> <repeat group="{{ @idiv }}" value="{{ @ispan }}"> <span>{{ @ispan }}</span> </repeat> </p> </div> </repeat>
应用以下F3命令:
$f3->set('div', [ 'coffee'=>['arabica','barako','liberica','kopiluwak'], 'tea'=>['darjeeling','pekoe','samovar'] ] );
因此,您将得到以下HTML片段:
<div> <p><span><b>coffee</b></span></p> <p> <span>arabica</span> <span>barako</span> <span>liberica</span> <span>kopiluwak</span> <p> </div> <div> <p><span><b>tea</b></span></p> <p> <span>darjeeling</span> <span>pekoe</span> <span>samovar</span> </p> </div>
令人惊讶,不是吗?在PHP中,您只需要定义一个F3变量div
的内容来替换@div
令牌。Fat-Free使编程和网络模板设计变得非常简单。
<repeat>
模板指令的value
属性返回迭代中当前元素的价值。如果您需要获取当前元素的数组键,请使用key
属性代替。key
属性是可选的。
<repeat>
还有一个可选的计数器属性,可以按以下方式使用:
<repeat group="{{ @fruits }}" value="{{ @fruit }}" counter="{{ @ctr }}"> <p class="{{ @ctr%2?'odd':'even' }}">{{ trim(@fruit) }}</p> </repeat>
内部,F3的模板引擎记录循环迭代的次数,并将该值保存到变量/令牌@ctr
中,在示例中用于确定奇偶分类。
嵌入JavaScript和CSS
如果您必须将F3令牌插入模板的<script>
或<style>
部分,框架仍然会以通常的方式替换它们:
<script type="text/javascript"> function notify() { alert('You are logged in as: {{ @userID }}'); } </script>
在您的<script>
或<style>
标签内嵌入模板指令不需要特殊处理:
<script type="text/javascript"> var discounts=[]; <repeat group="{{ @rates }}" value="{{ @rate }}"> // whatever you want to repeat in Javascript, e.g. discounts.push("{{ @rate }}"); </repeat> </script>
文档编码
默认情况下,Fat-Free使用UTF-8字符集,除非更改。您可以通过以下方式覆盖此行为:
$f3->set('ENCODING','ISO-8859-1');
一旦您通知框架所需的字符集,F3将在所有HTML和XML模板中使用它,直到再次更改。
所有类型的模板
如本节前面所述,框架不仅限于HTML模板。您还可以同样处理XML模板。机制几乎相同。您仍然有相同的{{ @variable }}
和{{ expression }}
令牌、<repeat>
、<check>
、<include>
和<exclude>
指令可供使用。只需告诉F3您正在传递一个XML文件而不是HTML:
echo Template::instance()->render('template.xml','application/xml');
第二个参数表示正在渲染的文档的MIME类型。
MVC的视图组件涵盖了不属于模型和控制器的一切,这意味着您的表示可以和应该包括所有类型的用户界面,如RSS、电子邮件、RDF、FOAF、文本文件等。下面的示例显示了如何将电子邮件表示与您的应用程序的业务逻辑分离:
MIME-Version: 1.0 Content-type: text/html; charset={{ @ENCODING }} From: {{ @from }} To: {{ @to }} Subject: {{ @subject }} <p>Welcome, and thanks for joining {{ @site }}!</p>
将上述电子邮件模板保存为welcome.txt。关联的F3代码如下:
$f3->set('from','<[email protected]>'); $f3->set('to','<[email protected]>'); $f3->set('subject','Welcome'); ini_set('sendmail_from',$f3->get('from')); mail( $f3->get('to'), $f3->get('subject'), Template::instance()->render('email.txt','text/html') );
提示:如果您的脚本与IMAP服务器通信,请将SMTP mail()函数替换为imap_mail()。
这不是很酷吗?当然,如果您有一组电子邮件收件人,您将使用数据库来填充firstName、lastName和email令牌。
这里有一个使用F3的SMTP插件的替代解决方案:
$mail=new SMTP('smtp.gmail.com',465,'SSL','[email protected]','secret'); $mail->set('from','<[email protected]>'); $mail->set('to','"Slasher" <[email protected]>'); $mail->set('subject','Welcome'); $mail->send(Template::instance()->render('email.txt'));
多语言支持
F3自带支持多种语言。
首先,创建一个具有以下结构的字典文件(每个语言一个文件):
<?php return [ 'love'=>'I love F3', 'today'=>'Today is {0,date}', 'pi'=>'{0,number}', 'money'=>'Amount remaining: {0,number,currency}' ];
将其保存为dict/en.php
。让我们创建另一个字典,这次是为德语。保存文件为dict/de.php
:
<?php return [ 'love'=>'Ich liebe F3', 'today'=>'Heute ist {0,date}', 'money'=>'Restbetrag: {0,number,currency}' ];
字典不过是键值对。F3根据语言文件中的键自动实例化框架变量。因此,很容易将它们嵌入模板中的令牌。使用F3模板引擎:
<h1>{{ @love }}</h1> <p> {{ @today,time() | format }}.<br /> {{ @money,365.25 | format }}<br /> {{ @pi }} </p>
以及更长的版本,它使用PHP作为模板引擎:
<?php $f3=Base::instance(); ?> <h1><?php echo $f3->get('love'); ?></h1> <p> <?php echo $f3->get('today',time()); ?>.<br /> <?php echo $f3->get('money',365.25); ?> <?php echo $f3->get('pi'); ?> </p>
接下来,我们指示F3在dict/
文件夹中查找字典:
$f3->set('LOCALES','dict/');
但是框架如何确定使用哪种语言?F3会首先通过查看HTTP请求头来自动检测,特别是浏览器发送的Accept-Language
头。
要覆盖此行为,您可以通过触发F3使用用户或应用程序指定的语言来强制使用语言:
$f3->set('LANGUAGE','de');
注意:在上面的示例中,key pi仅存在于英语字典中。框架始终将英语(en
)用作回退来填充指定(或检测)语言中不存在的键。
您还可以为语言变体(如en-US
、es-AR
等)创建字典文件。在这种情况下,F3将首先使用语言变体(如es-AR
)。如果变体中不存在键,框架将在根语言(es
)中查找该键,然后使用en
语言文件作为最终的回退。一旦引用,字典键值对就变成了F3变量。请确保键不与通过$f3->set()
、$f3->mset()
或$f3->config()
实例化的任何框架变量冲突。
您注意到我们上一个示例中的特殊'Today is {0,date}'
模式吗?F3的多语言功能依赖于ICU项目的字符串/消息格式化规则。框架使用其自己的ICU字符串格式化实现的子集。不需要在服务器上激活PHP的intl
扩展。
还有一点:F3还可以将.ini样式格式化的文件作为字典加载:
love="I love F3" today="Today is {0,date}" pi="{0,number}" money="Amount remaining: {0,number,currency}"
将其保存为dict/en.ini
,以便框架可以自动加载它。
数据清理
默认情况下,视图处理程序和模板引擎都会转义所有渲染的变量,即转换为HTML实体以保护您免受可能的车载和代码注入攻击。另一方面,如果您希望从应用程序代码中将有效的HTML片段传递到模板中:
$f3->set('ESCAPE',FALSE);
这可能会产生不良效果。您可能不希望所有变量都未转义通过。Fat-Free允许您单独转义变量。对于F3模板:
{{ @html_content | raw }}
在PHP模板的情况下:
<?php echo View::instance()->raw($html_content); ?>
作为F3变量自动转义功能的补充,框架还允许您对从HTML表单获取的用户输入进行清理:
$f3->scrub($_GET,'p; br; span; div; a');
此命令将从指定的变量中删除所有标签(除了第二个参数指定的那些)和不受欢迎的字符。如果变量包含数组,则递归地清理数组中的每个元素。如果第二个参数传递了星号(*),则$f3->scrub()
允许所有HTML标签通过而不受影响,并简单地删除不受欢迎的控制字符。
数据库
连接到数据库引擎
无脂肪(Fat-Free)设计用于简化与SQL数据库交互的过程。如果你不是那种喜欢深入研究SQL细节的人,而更倾向于面向对象的数据处理,可以直接跳到本教程的下一节。然而,如果你需要进行一些复杂的数据处理和数据库性能优化任务,SQL是必经之路。
使用熟悉的$f3->set()
命令与MySQL、SQLite、SQL Server、Sybase和Oracle等SQL引擎建立通信。连接到SQLite数据库的示例:-
$db=new DB\SQL('sqlite:/absolute/path/to/your/database.sqlite');
另一个例子,这次是MySQL:-
$db=new DB\SQL( 'mysql:host=localhost;port=3306;dbname=mysqldb', 'admin', 'p455w0rD' );
查询数据库
好吧。很简单,不是吗?这基本上就是你在普通PHP中做同样事情的方法。你只需要知道你要连接的数据库的DSN格式。请参阅PHP手册中的PDO部分。
让我们继续我们的PHP代码:-
$f3->set('result',$db->exec('SELECT brandName FROM wherever')); echo Template::instance()->render('abc.htm');
嗯,这里发生了什么事?我们不应该设置PDO、语句、游标等吗?简单的回答是:你不需要。F3通过在后台处理所有繁重的工作来简化一切。
这次我们创建一个HTML模板,如abc.htm
,其中至少包含以下内容:-
<repeat group="{{ @result }}" value="{{ @item }}"> <span>{{ @item.brandName }}</span> </repeat>
在大多数情况下,SQL命令集足以生成Web就绪的结果,因此你可以直接在模板中使用result
数组变量。尽管如此,Fat-Free不会阻止你深入了解其SQL处理器的内部结构。事实上,F3的DB\SQL
类直接从PHP的PDO
类继承,因此如果你需要一些细粒度的控制,你仍然可以访问每个过程中涉及到的底层PDO组件和原语。
事务
这里还有一个例子。你不仅可以向$db->exec()
命令提供一个SQL语句作为参数,还可以传递一个SQL语句数组:-
$db->exec( [ 'DELETE FROM diet WHERE food="cola"', 'INSERT INTO diet (food) VALUES ("carrot")', 'SELECT * FROM diet' ] );
F3足够智能,知道如果你传递一个SQL指令数组,这表示一个SQL批处理事务。你不需要担心SQL回滚和提交,因为如果事务过程中发生任何错误,框架将自动将数据库恢复到初始状态。如果成功,F3将提交对数据库所做的所有更改。
你也可以通过编程方式启动和结束一个事务:-
$db->begin(); $db->exec('DELETE FROM diet WHERE food="cola"'); $db->exec('INSERT INTO diet (food) VALUES ("carrot")'); $db->exec('SELECT * FROM diet'); $db->commit();
如果任何语句遇到错误,将发生回滚。
要获取所有发出的数据库指令列表:-
echo $db->log();
参数化查询
将字符串参数传递到SQL语句中充满了危险。考虑以下情况:-
$db->exec( 'SELECT * FROM users '. 'WHERE username="'.$f3->get('POST.userID'.'"') );
如果POST
变量userID
未经任何数据清理过程,恶意用户可以传递以下字符串并永久损坏你的数据库:-
admin"; DELETE FROM users; SELECT "1
幸运的是,参数化查询可以帮助你减轻这些风险:-
$db->exec( 'SELECT * FROM users WHERE userID=?', $f3->get('POST.userID') );
如果F3检测到查询参数/令牌的值是字符串,则底层数据访问层会对字符串进行转义,并根据需要添加引号。
如果我们按照这种方式编写上一节中的示例,它将更加安全,防止SQL注入:-
$db->exec( [ 'DELETE FROM diet WHERE food=:name', 'INSERT INTO diet (food) VALUES (?)', 'SELECT * FROM diet' ], [ array(':name'=>'cola'), array(1=>'carrot'), NULL ] );
CRUD(但风格独特)
F3包含易于使用的对象关系映射器(ORM),它们位于你的应用程序和你的数据之间 - 使得你编写处理常见数据操作(如创建、检索、更新和删除(CRUD)SQL和NoSQL数据库中的信息)的程序变得更加容易和快捷。数据映射器通过将PHP对象交互映射到相应的后端查询来完成大部分工作。
假设你有一个包含应用程序用户表的现有MySQL数据库。(SQLite、PostgreSQL、SQL Server、Sybase也可以。)它将使用以下SQL命令创建:-
CREATE TABLE users ( userID VARCHAR(30), password VARCHAR(30), visits INT, PRIMARY KEY(userID) );
注意: MongoDB 是一个 NoSQL 数据库引擎,本质上是无模式的。F3 有自己的快速轻量级 NoSQL 实现,称为 Jig,它使用 PHP 序列化或 JSON 编码的平面文件。这些抽象层不需要严格的数据结构。字段可能因记录而异。它们也可以在运行时定义或删除。
现在回到 SQL。首先,我们与数据库建立通信。
$db=new DB\SQL( 'mysql:host=localhost;port=3306;dbname=mysqldb', 'admin', 'wh4t3v3r' );
要从我们的表中检索一条记录:
$user=new DB\SQL\Mapper($db,'users'); $user->load(['userID=?','tarzan']);
第一行实例化了一个数据映射器对象,它与数据库中的 users
表交互。在幕后,F3 获取 users
表的结构并确定哪些字段被定义为主键。此时,映射器对象还没有数据(干燥状态),所以 $user
仅仅是一个结构化对象 - 但它包含执行基本 CRUD 操作和一些额外方法的方法。要使用包含字符串值 tarzan
的 userID
字段从我们的用户表中检索记录,我们使用 load() 方法
。这个过程称为“自动填充”数据映射器对象。
很简单,不是吗?F3 理解 SQL 表已经在数据库引擎内部存在结构定义。与其它框架不同,F3 不需要额外的类声明(除非您想扩展数据映射器以适应复杂对象),不需要多余的 PHP 数组/对象属性到字段的映射(重复劳动),不需要代码生成器(如果数据库结构发生变化,需要重新生成代码),不需要愚蠢的 XML/YAML 文件来配置模型,不需要多余的命令仅用于检索单个记录。在 F3 中,仅对 MySQL 中的 varchar
字段进行简单的调整不需要更改您的应用程序代码。符合 MVC 和“关注点分离”,数据库管理员对数据(和结构)的控制程度与模板设计师对 HTML/XML 模板的控制程度相同。
如果您更喜欢使用 NoSQL 数据库,查询语法的相似性是表面的。在 MongoDB 数据映射器的情况下,等效代码将是:
$db=new DB\Mongo('mongodb://localhost:27017','testdb'); $user=new DB\Mongo\Mapper($db,'users'); $user->load(['userID'=>'tarzan']);
使用 Jig,语法与 F3 的模板引擎类似:
$db=new DB\Jig('db/data/',DB\Jig::FORMAT_JSON); $user=new DB\Jig\Mapper($db,'users'); $user->load(['@userID=?','tarzan']);
智能 SQL ORM
框架在对象实例化期间自动将表中的 visits
字段映射到数据映射器属性,即 $user=new DB\SQL\Mapper($db,'users');
。一旦对象创建,$user->password
和 $user->userID
分别映射到表中的 password
和 userID
字段。
您不能使用 ORM 添加或删除映射字段,或更改表的结构。您必须在 MySQL 或您使用的数据库引擎中进行此操作。在您的数据库引擎中做出更改后,当您运行应用程序时,Fat-Free 会自动将新的表结构同步到您的数据映射器对象。
F3 直接从数据库模式推导数据映射器结构。无需猜测。它理解 MySQL、SQLite、MSSQL、Sybase 和 PostgreSQL 数据库引擎之间的差异。
SQL 标识符不应使用保留词,且应限于字母数字字符 A-Z
、0-9
和下划线符号 (_
)。在数据定义中包含空格(或特殊字符)并由引号包围的列名与 ORM 不兼容。它们不能正确地表示为 PHP 对象属性。
假设我们想要增加用户的访问次数并更新用户表中的相应记录,我们可以添加以下代码:
$user->visits++; $user->save();
如果我们想要插入记录,我们遵循以下过程:
$user=new DB\SQL\Mapper($db,'users'); // or $user=new DB\Mongo\Mapper($db,'users'); // or $user=new DB\Jig\Mapper($db,'users'); $user->userID='jane'; $user->password=password_hash('secret', PASSWORD_BCRYPT, [ 'cost' => 12 ]); $user->visits=0; $user->save();
我们仍然使用相同的save()
方法。但F3是如何知道何时插入或更新记录的呢?当数据映射器对象被记录检索自动填充时,框架会跟踪记录的主键(或者在MongoDB和Jig的情况下是_id
)——这样它就知道应该更新或删除哪个记录——即使主键的值发生了变化。一个程序化填充的数据映射器——其值不是从数据库检索的,而是由应用程序填充的——将不会记住其主键中的先前值。对MongoDB和Jig来说,也是如此,但使用对象_id
作为参考。因此,当我们实例化上面的$user
对象并使用程序中的值填充其属性——而没有从用户表中检索任何记录时,F3就知道应该插入这条记录。
在调用save()
后,映射器对象不会为空。如果您想向数据库中添加新记录,您必须首先对映射器进行脱水操作:-
$user->reset(); $user->userID='cheetah'; $user->password=password_hash('unknown', PASSWORD_BCRYPT, [ 'cost' => 12 ]); $user->save();
在不调用reset()
的情况下,第二次调用save()
将仅更新映射器当前指向的记录。
SQL表的注意事项
尽管在数据库中的所有表中都有主键的问题是有争议的,但F3不会阻止您创建一个与没有主键的表通信的数据映射器对象。唯一的缺点是:您无法删除或更新映射记录,因为F3根本无法确定您指的是哪个记录,再加上位置引用并不可靠。行ID在不同的SQL引擎之间不可移植,并且可能不会被PHP数据库驱动程序返回。
要从我们的表中删除映射记录,请在自动填充的数据映射器上调用erase()
方法。例如:-
$user=new DB\SQL\Mapper($db,'users'); $user->load(['userID=? AND password=?','cheetah','ch1mp']); $user->erase();
Jig的查询语法会略有相似:-
$user=new DB\Jig\Mapper($db,'users'); $user->load(['@userID=? AND @password=?','cheetah','chimp']); $user->erase();
而MongoDB的等效操作为:-
$user=new DB\Mongo\Mapper($db,'users'); $user->load(['userID'=>'cheetah','password'=>'chimp']); $user->erase();
天气报告
要找出我们的数据映射器是否被填充:-
if ($user->dry()) echo 'No record matching criteria';
超越CRUD
我们已经涵盖了CRUD处理器。还有一些额外的功能您可能觉得有用:-
$f3->set('user',new DB\SQL\Mapper($db,'users')); $f3->get('user')->copyFrom('POST'); $f3->get('user')->save();
请注意,我们还可以使用Fat-Free变量作为映射器对象的容器。copyFrom()
方法使用来自框架数组变量的元素填充映射器对象,这些数组的键必须与映射器对象的属性名称相同,这些属性又对应于记录的字段名称。因此,当Web表单被提交(假设HTML的name属性设置为userID
),输入字段的值将传输到$_POST['userID']
,由F3复制到其POST.userID
变量中,并保存到数据库中的映射字段$user->userID
。如果它们都具有相同名称的元素,这个过程就会变得非常简单。数组键(即模板令牌名称)、框架变量名称和字段名称的一致性是关键:)
另一方面,如果我们想检索记录并将字段值复制到框架变量中供以后使用,例如模板渲染:-
$f3->set('user',new DB\SQL\Mapper($db,'users')); $f3->get('user')->load(['userID=?','jane']); $f3->get('user')->copyTo('POST');
然后我们可以将{{ @POST.userID }}分配给同一输入字段的value属性。总的来说,HTML输入字段将看起来像这样:-
<input type="text" name="userID" value="{{ @POST.userID }}"/>
save()
、update()
、copyFrom()
数据映射器方法以及load()
和erase()
参数化变体的方法可以防止SQL注入。
导航和分页
默认情况下,数据映射器的load()
方法仅检索与指定条件匹配的第一个记录。如果您有多个与第一个加载的记录满足相同条件的记录,您可以使用skip()
方法进行导航:-
$user=new DB\SQL\Mapper($db,'users'); $user->load('visits>3'); // Rewritten as a parameterized query $user->load(['visits>?',3]); // For MongoDB users:- // $user=new DB\Mongo\Mapper($db,'users'); // $user->load(['visits'=>['$gt'=>3]]); // If you prefer Jig:- // $user=new DB\Jig\Mapper($db,'users'); // $user->load('@visits>?',3); // Display the userID of the first record that matches the criteria echo $user->userID; // Go to the next record that matches the same criteria $user->skip(); // Same as $user->skip(1); // Back to the first record $user->skip(-1); // Move three records forward $user->skip(3);
您可以使用$user->next()
作为$user->skip()
的替代,如果$user->prev()
让您觉得更有意义,那么可以用来代替$user->skip(-1)
。
使用dry()
方法来检查您是否已经操作超出了结果集的限制。dry()
方法会在您尝试在第一条记录上调用skip(-1)
时返回TRUE。同样,如果对满足检索条件的最后一条记录调用skip(1)
,它也会返回TRUE。
load()
方法接受第二个参数:一个包含键值对(例如)的选项数组:
$user->load( ['visits>?',3], [ 'order'=>'userID DESC' 'offset'=>5, 'limit'=>3 ] );
如果您使用的是MySQL,查询将转换为:
SELECT * FROM users WHERE visits>3 ORDER BY userID DESC LIMIT 3 OFFSET 5;
这是以小批量形式呈现数据的一种方式。以下是另一种分页显示结果的方法:
$page=$user->paginate(2,5,['visits>?',3]);
在上面的场景中,F3将检索符合'visits>3'
条件的记录。然后它将结果限制为5条记录(每页),从页面偏移量2开始(基于0)。框架将返回一个包含以下元素的数组:
[subset] array of mapper objects that match the criteria
[count] number of subsets available
[pos] actual subset position
如果paginate()
的第一个参数是负数或超过找到的子集数量,则返回的实际子集位置将为NULL。
虚拟字段
有些情况下,您需要检索字段的计算值,或者从另一个表中引用的交叉引用值。这就是虚拟字段的作用。SQL微型ORM允许您对现有字段派生的数据进行操作。
假设我们定义了以下表:
CREATE TABLE products productID VARCHAR(30), description VARCHAR(255), supplierID VARCHAR(30), unitprice DECIMAL(10,2), quantity INT, PRIMARY KEY(productID) );
没有totalprice
字段,因此我们可以让框架从数据库引擎请求两个字段的算术乘积:
$item=new DB\SQL\Mapper($db,'products'); $item->totalprice='unitprice*quantity'; $item->load(['productID=:pid',':pid'=>'apple']); echo $item->totalprice;
上述代码片段定义了一个名为totalprice
的虚拟字段,该字段通过将unitprice
乘以quantity
来计算。SQL映射器保存了这个规则/公式,所以当从数据库检索记录时,我们可以像使用常规映射字段一样使用虚拟字段。
您也可以拥有更复杂的虚拟字段:
$item->mostNumber='MAX(quantity)'; $item->load(); echo $item->mostNumber;
这次框架检索了数量最高的产品(请注意,load()
方法没有定义任何条件,因此将处理表中的所有记录)。当然,如果要将表达式限制为特定的一组符合指定条件的记录,虚拟字段mostNumber
仍然会给出正确的数字。
您还可以从另一个表中获取值:
$item->supplierName= 'SELECT name FROM suppliers '. 'WHERE products.supplierID=suppliers.supplierID'; $item->load(); echo $item->supplierName;
每次从产品表加载记录时,ORM都会将products
表中的supplerID
与suppliers
表中的supplierID
进行交叉引用。
要销毁虚拟字段,请使用unset($item->totalPrice);
。如果totalPrice
虚拟字段已定义,则isset($item->totalPrice)
表达式返回TRUE;否则返回FALSE。
请记住,虚拟字段必须在数据检索之前定义。ORM不会执行实际的计算,也不会从另一个表中派生结果。这一切都是由数据库引擎完成的。
寻找,就会找到
如果您不需要逐条记录导航,您可以一次性检索整个批次的记录:
$frequentUsers=$user->find(['visits>?',3],['order'=>'userID']);
Jig映射器的查询语法有轻微的相似之处:
$frequentUsers=$user->find(['@visits>?',3],['order'=>'userID']);
使用MongoDB映射器的等效代码:
$frequentUsers=$user->find(['visits'=>['$gt'=>3]],['userID'=>1]);
find()
方法在users
表中搜索符合条件的记录,按userID
排序,并将结果作为映射器对象数组返回。find('visits>3')
与load('visits>3')
不同。后者指的是当前的$user
对象。find()
对skip()
没有任何影响。
重要:将空条件、NULL或空字符串作为find()
或load()
的第一个参数声明将检索所有记录。请确保您知道自己在做什么——您可能会超出PHP的内存限制,尤其是在大表或集合中。
find()
方法具有以下语法:
find( $criteria, [ 'group'=>'foo', 'order'=>'foo,bar', 'limit'=>5, 'offset'=>0 ] );
find()
返回一个对象数组。每个对象都是一个与指定条件匹配的记录的映射器。
$place=new DB\SQL\Mapper($db,'places'); $list=$place->find('state="New York"'); foreach ($list as $obj) echo $obj->city.', '.$obj->country;
如果您需要将映射器对象转换为关联数组,请使用cast()
方法:
$array=$place->cast(); echo $array['city'].', '.$array['country'];
要获取与特定条件匹配的表中记录的数量,请使用count()
方法。
if (!$user->count(['visits>?',10])) echo 'We need a better ad campaign!';
还有一个类似于find()
但提供更多字段控制的select()
方法。它具有类似SQL的语法:
select( 'foo, bar, MIN(baz) AS lowest', 'foo > ?', [ 'group'=>'foo, bar', 'order'=>'baz ASC', 'limit'=>5, 'offset'=>3 ] );
与find()
方法类似,select()
不会更改映射对象的内容。它仅作为查询映射表的便利方法。这两种方法的返回值都是映射对象数组。使用dry()
来确定这些方法是否找到了记录是不适当的。如果没有记录与find()
或select()
条件匹配,则返回值为空数组。
性能分析
如果您想找出由您的应用程序直接(或通过映射对象间接)发出的哪些SQL语句导致性能瓶颈,您可以这样做:
echo $db->log();
F3跟踪所有发送到底层SQL数据库驱动器的命令,以及每个语句完成所需的时间——这正是您调整应用程序性能所需的信息。
有时候这还不够
在大多数情况下,您可以依靠我们迄今为止讨论过的数据映射方法带来的便利。如果您需要框架执行一些重负载任务,您可以通过声明自己的具有自定义方法的类来扩展SQL映射器——但您无法避免在核心SQL上动手脚:
class Vendor extends DB\SQL\Mapper { // Instantiate mapper function __construct(DB\SQL $db) { // This is where the mapper and DB structure synchronization occurs parent::__construct($db,'vendors'); } // Specialized query function listByCity() { return $this->select( 'vendorID,name,city',['order'=>'city DESC']); /* We could have done the the same thing with plain vanilla SQL:- return $this->db->exec( 'SELECT vendorID,name,city FROM vendors '. 'ORDER BY city DESC;' ); */ } } $vendor=new Vendor; $vendor->listByCity();
以这种方式扩展数据映射器是构建应用程序的DB相关模型的一种简单方法。
优点和缺点
如果您熟悉SQL,您可能会说:ORM中的所有内容都可以用老式SQL查询处理。确实如此。我们可以通过使用数据库触发器和存储过程来不使用额外的监听器。我们可以使用连接表来完成关系查询。ORM只是不必要的开销。但重点在于——数据映射器通过使用对象来表示数据库实体提供了额外的功能。作为开发者,您可以更快地编写代码并提高生产率。结果程序将更干净,如果不是更短。但您必须权衡好处与速度的妥协——尤其是在处理大型和复杂的数据存储时。记住,所有ORM——无论它们有多薄——都将始终只是另一个抽象层。它们仍然必须将工作传递给底层的SQL引擎。
设计上,F3的ORM不提供直接将对象彼此连接的方法,即SQL连接——因为这会打开一个麻烦的罐头。这使得您的应用程序比应有的更复杂,并且由于对象继承和多态(阻抗不匹配)与它们映射到的数据库实体,通过急切或懒加载技术获取的对象可能会发生死锁甚至不同步。在SQL映射器中,可以通过使用虚拟字段以间接方式完成它——但您必须自行程序化并承担风险。
如果您想将“纯”面向对象的概念应用到您的应用程序中以表示所有数据(因为“一切都是对象”),请记住,数据几乎总是比应用程序存活得更久。您的程序可能已经在数据失去价值之前就过时了。不要通过使用与数据模式和物理结构差异太大的交织对象和类来在程序中添加另一层复杂性。
在您的应用程序中将多个对象编织在一起以操作数据库中的底层表之前,请考虑以下几点:在数据库引擎中创建视图以表示关系和触发器以定义对象行为更加高效。关系型数据库引擎被设计用来处理视图、连接表和触发器。它们不是愚蠢的数据存储。在视图中连接的表将显示为一个单一的表,Fat-Free可以像常规表一样自动映射视图。在PHP中将JOIN复制为关系对象与数据库引擎的机器代码、关系代数和优化逻辑相比要慢。此外,在我们的应用程序中重复连接表是一个数据库设计需要审计的明确迹象,视图应被视为数据检索的组成部分。如果一个表经常从另一个表中交叉引用数据,考虑规范化您的结构或创建一个视图。然后创建一个映射对象来自动映射该视图。这样更快,也省力。
请考虑在您的数据库引擎内部创建的以下SQL视图:
CREATE VIEW combined AS SELECT projects.project_id AS project, users.name AS name FROM projects LEFT OUTER JOIN users ON projects.project_id=users.project_id AND projects.user_id=users.user_id;
您的应用程序代码变得更简单,因为它不必维护两个映射对象(一个用于项目表,另一个用于用户表)来从两个连接的表中检索数据:
$combined=new DB\SQL\Mapper($db,'combined'); $combined->load(['project=?',123]); echo $combined->name;
提示:按照设计使用工具。Fat-Free已经有一个易于使用的SQL助手。如果您需要更大的锤子,请使用它。尽量在方便性和性能之间寻求平衡。如果您正在处理复杂和遗留数据结构,SQL始终是您的备选方案。
插件
关于F3插件
插件不过是自动加载的类,它们使用框架内置功能来扩展F3的功能和功能。如果您想贡献,请留下笔记在Google Groups托管Fat-Free讨论区,或者在FreeNode #fatfree
IRC频道中告诉我们。其他人可能正在进行类似的项目。如果我们统一我们的努力,框架社区将非常感激。
CAPTCHA图像
可能会有这样的情况,您想使您的表单更安全,以防止垃圾邮件机器人及恶意自动化脚本。F3提供了一个captcha()
方法,用于生成只有人类才能识别的随机文本图像。
$img = new Image(); $img->captcha('fonts/CoolFont.ttf',16,5,'SESSION.captcha_code'); $img->render();
此示例基于您希望使用的TrueType字体生成一个随机图像。fonts/
文件夹是应用程序UI
路径下的一个子文件夹。第二个参数表示字体大小,第三个参数定义了要生成的十六进制字符数。
最后一个参数代表一个F3变量名。这是F3将存储CAPTCHA图像的字符串等效物的位置。为了使字符串重载安全,我们指定了一个会话变量:SESSION.captcha_code
,它映射到$_SESSION['captcha_code']
,您可以使用它稍后验证表单提交中的输入元素是否与此字符串匹配。
从另一个网站获取数据
我们已经涵盖了框架中几乎所有的功能,以运行独立的Web服务器。对于大多数应用程序,这些功能将为您服务得很好。但如果您的应用程序需要从网络上的另一个Web服务器获取数据呢?F3有一个Web插件来帮助您处理这种情况:
$web=new Web; $request=$web->request('http://www.google.com/'); // another way to do it:- $request=Web::instance()->request('http://www.google.com/');
这个简单的示例向位于www.google.com的页面发送HTTP请求,并将其存储在$request
PHP变量中。request()
方法返回一个数组,包含HTTP响应,其中$request['headers']
和$request['body']
分别表示响应头和主体。我们可以使用F3::set命令保存内容,或者直接将输出echo到我们的浏览器。从网络上检索另一个HTML页面可能没有实际用途。但它在ReSTful应用程序中可能特别有用,比如查询CouchDB服务器。
$host='localhost:5984'; $web->request($host.'/_all_dbs'), $web->request($host.'/testdb/',['method'=>'PUT']);
您可能已经注意到,您可以向request()
方法传递一个包含额外选项的数组:
$web->request( 'https://www.example.com:443?'. http_build_query( [ 'key1'=>'value1', 'key2'=>'value2' ] ), [ 'headers'=>[ 'Accept: text/html,application/xhtml+xml,application/xml', 'Accept-Language: en-us' ], 'follow_location'=>FALSE, 'max_redirects'=>30, 'ignore_errors'=>TRUE ] );
如果框架变量 CACHE
被启用,并且如果远程服务器指示您的应用程序缓存对 HTTP 请求的响应,F3 将遵守此请求,并在框架每次接收到来自您应用程序的类似请求时检索缓存响应,从而像浏览器一样行为。
Fat-Free 将使用您的 Web 服务器上可用的任何手段来运行 request()
方法:PHP 流包装器(allow_url_fopen
)、cURL 模块或低级套接字。
处理文件下载
F3 提供了一个将文件发送到 HTTP 客户端的实用工具,即满足下载请求。您可以使用它来隐藏下载文件的真实路径。这增加了一些安全层,因为如果用户不知道文件名和位置,他们将无法下载文件。以下是实现方法:
$f3->route('GET /downloads/@filename', function($f3,$args) { // send() method returns FALSE if file doesn't exist if (!Web::instance()->send('/real/path/'.$args['filename'])) // Generate an HTTP 404 $f3->error(404); } );
远程和分布式应用程序
如果需要另一个 Web 服务器代表您的计算机处理数据,从而利用分布式计算的力量,您也可以在复杂的 SOAP 或 XML-RPC 应用程序中使用 request()
方法。W3Schools.com 有一个关于 SOAP 的优秀教程。另一方面,TutorialsPoint.com 给出了一个关于 XML-RPC 的良好概述。
优化
缓存引擎
缓存静态 Web 页面——即可以跳过某些路由处理器的代码,并且不需要重新处理模板——是减少您的 Web 服务器工作负载的一种方法,以便它可以专注于其他任务。您可以通过向 $f3->route()
方法提供第三个参数来激活框架的缓存引擎。只需指定缓存 Web 页面过期前的秒数即可:
$f3->route('GET /my_page','App->method',60);
以下是工作原理。在这个示例中,当 F3 检测到第一次访问 URL /my_page
时,它执行由第二个参数表示的路由处理器,并将所有浏览器输出保存到框架的内置缓存(服务器端)。类似的指令会自动发送到用户的 Web 浏览器(客户端),因此,在 60 秒期间内,浏览器只需从本地检索页面。框架使用缓存的目的完全不同——在 60 秒时间框架内为询问相同 Web 页面的其他用户提供框架缓存的页面。它跳过执行路由处理器,并直接从磁盘提供之前保存的页面。当有人尝试在 60 秒计时器过期后访问相同的 URL 时,F3 将使用新副本刷新缓存。
具有静态数据的 Web 页面最有可能成为缓存的对象。如果 $f3->route()
方法中的第三个参数为零或未指定,Fat-Free 不会在指定的 URL 缓存 Web 页面。F3 符合 HTTP 规范:只有 GET 和 HEAD 请求可以缓存。
在设计应用程序时,请注意以下重要事项。除非您了解客户端缓存的潜在副作用,否则不要缓存 Web 页面。确保您在与应用程序的用户会话状态无关的 Web 页面上激活缓存。
例如,您以这种方式设计了自己的网站,使得所有网页在用户未登录您的应用程序时都显示以下菜单选项:“首页”
、“关于我们”
和“登录”
。一旦用户登录,您希望菜单选项变为:“首页”
、“关于我们”
和“注销”
。如果您指示Fat-Free缓存“关于我们”
页面(包括菜单选项)的内容,它将这样做,并也会向HTTP客户端发送相同的指令。无论用户的会话状态如何,即登录或注销,用户的浏览器都会在该会话状态下对该页面进行快照。在缓存超时到期之前,用户对“关于我们”
页面的未来请求将显示在页面最初保存时相同的菜单选项。现在,用户可能已经登录,但菜单选项仍然与未发生此类事件时相同。这不是我们希望从应用程序中获得的行为。
以下是一些提示:
- 不要缓存动态页面。很明显,您不希望缓存经常变化的数据。然而,您可以在包含每小时、每天甚至每年更新的数据的页面上启用缓存。出于安全原因,框架仅限制HTTP
GET
路由的缓存引擎使用。它不会缓存提交的表单!不要在看起来静态的Web页面上启用缓存。在我们的例子中,“关于我们”内容可能是静态的,但菜单不是。 - 在仅在一个会话状态下可用的页面上启用缓存。如果您想缓存
“关于我们”
页面,请确保它在用户未登录时才可用。 - 如果您有RAMdisk或快速固态驱动器,请配置
CACHE
全局变量,使其指向该驱动器。这将使您的应用程序运行得像一级方程式赛车一样。
注意:在您的应用程序推出(即发布或生产状态)之前,不要将超时值设置得太长。您对任何PHP脚本所做的更改可能不会对显示的输出产生预期的效果,如果页面存在于框架缓存中且过期期限尚未到期。如果您修改了受缓存计时器影响的页面生成程序,并且希望这些更改立即生效,您应该通过删除缓存目录(或CACHE
全局变量指向的任何路径)中的文件来清除缓存。F3将在必要时自动刷新缓存。在客户端,您几乎无能为力,只能指示用户清除浏览器的缓存或等待缓存期限到期。
PHP需要正确设置,以便F3缓存引擎能够正常工作。您的操作系统时区应与php.ini
文件中的date.timezone
设置同步。
与路由类似,Fat-Free还允许您缓存数据库查询。速度提升可能相当显著,尤其是在涉及静态数据或很少更改的数据库内容复杂SQL语句时。要激活数据库查询缓存,使框架不必每次都重新执行SQL语句,只需在F3::sql命令中添加第三个参数——缓存超时即可。例如:
$db->exec('SELECT * from sizes;',NULL,86400);
如果我们预期这个数据库查询的结果在24小时内总是Small
、Medium
和Large
,我们指定86400
秒作为第二个参数,这样Fat-Free就不必每天执行查询超过一次。相反,框架会将结果存储在缓存中,在指定的24小时内每次请求都会从缓存中检索它,并在计时器到期时重新执行查询。
SQL数据映射器也使用缓存引擎来优化表结构与表示它们的对象之间的同步。默认值为60
秒。如果您在数据库引擎中更改了表结构,您需要等待缓存计时器到期才能在应用程序中看到效果。您可以通过指定数据映射器构造函数的第三个参数来更改此行为。如果您不期望对表结构进行任何进一步的更改,请将其设置为高值。
$user=new DB\SQL\Mapper($db,'users',86400);
默认情况下,Fat-Free的缓存引擎是禁用的。您可以启用它,并允许它自动检测APC、WinCache或XCache。如果它找不到适当的后端,F3将使用文件系统,即tmp/cache/
文件夹:
$f3->set('CACHE',TRUE);
禁用缓存很简单:
$f3->set('CACHE',FALSE);
如果您希望覆盖自动检测功能,可以这样做 - 例如,在F3也支持Memcached后端的情况下:
$f3->set('CACHE','memcache=localhost:11211');
您还可以使用缓存引擎来存储自己的变量。这些变量将在HTTP请求之间持续存在,并保留在缓存中,直到引擎收到删除它们的指令。要将值存储在缓存中:
$f3->set('var','I want this value saved',90);
$f3->set()
方法的第三个参数指示框架将变量存储在缓存中,持续时间为90秒。如果您的应用程序在此期间发出$f3->get('var')
,F3将自动从缓存中检索值。类似地,$f3->clear('var')
将从缓存和RAM中清除值。如果您想确定变量是否存在于缓存中,`$f3->exists('var'));`返回两个可能值之一:如果框架变量不在缓存中,则返回FALSE;或返回表示变量保存时间的整数(Unix时间,以微秒为单位)。
保持JavaScript和CSS健康饮食
Fat-Free还提供了Web插件中的JavaScript和CSS压缩器。它可以将所有CSS文件合并到一个样式表(或JavaScript文件合并到一个脚本)中,从而减少Web页面上的组件数量。减少对Web服务器的HTTP请求次数,从而加快页面加载速度。首先您需要准备您的HTML模板,以便可以利用此功能。例如:
<link rel="stylesheet" type="text/css" href="/minify/css?files=typo.css,grid.css" />
对您的JavaScript文件做同样的事情:
<script type="text/javascript" src="/minify/js?&files=underscore.js"> </script>
当然,我们需要设置一个路由,以便您的应用程序可以处理对Fat-Free CSS/JavaScript压缩器的必要调用:
$f3->route('GET /minify/@type', function($f3,$args) { $f3->set('UI',$args['type'].'/'); echo Web::instance()->minify($_GET['files']); }, 3600 );
就是这样!minify()
读取每个文件(在我们的CSS示例中是typo.css
和grid.css
,在我们的JavaScript示例中是underscore.js
),删除所有不必要的空白和注释,将相关项合并为一个Web页面组件,并为用户Web浏览器附加一个远期到期日期,以便缓存数据。确保PARAMS.type
变量基础指向正确的路径。否则,压缩器内的URL重写机制找不到CSS/JavaScript文件。
客户端缓存
在我们的示例中,框架向客户端Web浏览器发送一个远期到期日期,因此任何对相同CSS或JavaScript块的请求都将来自用户的硬盘。在服务器端,F3将检查每个请求,看CSS或JavaScript块是否已经被缓存。我们指定的路由的缓存刷新周期为3600
秒。此外,如果Web浏览器发送一个If-Modified-Since
请求头,而框架看到缓存没有变化,F3将发送一个HTTP 304 Not Modified
响应,因此实际上没有内容被传输。如果没有If-Modified-Since
头,Fat-Free如果没有缓存文件,将渲染缓存文件中的输出。否则,将执行相关代码。
提示:如果您不经常修改您的Javascript/CSS文件(例如,如果您使用jQuery、MooTools、Dojo等JavaScript库),请考虑在指向您的Javascript/CSS压缩处理程序的路径上添加一个缓存计时器(F3::route()的第三个参数),这样Fat-Free就不会在每次收到此类请求时压缩和合并这些文件。
PHP代码加速
想使您的网站运行得更快吗?Fat-Free与Alternative PHP Cache (APC)、XCache或WinCache配合使用效果最佳。这些PHP扩展通过优化您的PHP脚本(包括框架代码)来提高您应用程序的性能。
带宽限制
一个快速的应用程序可以处理所有HTTP请求,并以最短的时间响应它们,但这并不总是好主意——特别是如果您的带宽有限或您的网站流量特别大。尽快提供服务也会使您的应用程序容易受到拒绝服务(DOS)攻击。F3具有带宽限制功能,允许您控制Web页面被服务的速度。您可以指定处理请求所需的时间:
$f3->route('/throttledpage','MyApp->handler',0,128);
在此示例中,框架将以128KiBps的速度服务Web页面。
在应用层面进行带宽限制对于登录页面特别有用。对于字典攻击缓慢响应是减轻这种安全风险的好方法。
单元测试
防弹代码
健壮的应用程序是全面测试的结果。验证您的程序的每个部分是否符合规范,并达到最终用户的期望意味着尽早找到并修复错误。
如果您对单元测试方法知之甚少,您可能直接将代码片段嵌入到现有程序中,以帮助您进行调试。当然,这意味着您必须在程序运行后将其删除。遗留代码片段、设计不佳和实现错误可能会在您稍后推出应用程序时变成错误。
F3使您能够轻松调试程序,而不会干扰您的常规思维过程。该框架不需要您构建复杂的OOP类、重量级的测试结构或侵入性程序。
单元(或测试夹具)可以是函数/方法或类。让我们看看一个简单的示例:
function hello() { return 'Hello, World'; }
将其保存到名为hello.php
的文件中。现在我们如何知道它真的按预期运行?让我们创建我们的测试过程:
$f3=require('lib/base.php'); // Set up $test=new Test; include('hello.php'); // This is where the tests begin $test->expect( is_callable('hello'), 'hello() is a function' ); // Another test $hello=hello(); $test->expect( !empty($hello), 'Something was returned' ); // This test should succeed $test->expect is_string($hello), 'Return value is a string' ); // This test is bound to fail $test->expect( strlen($hello)==13, 'String length is 13' ); // Display the results; not MVC but let's keep it simple foreach ($test->results() as $result) { echo $result['text'].'<br />'; if ($result['status']) echo 'Pass'; else echo 'Fail ('.$result['source'].')'; echo '<br />'; }
将其保存到名为test.php
的文件中。这样我们就可以保持hello.php
的完整性。
现在是我们单元测试过程的核心。
F3的内置Test
类跟踪每个expect()
调用的结果。$test->results()
的输出是一个数组,其中包含数组,具有text
(反映expect()
的第二个参数)、status
(表示测试结果的布尔值)和source
(特定测试的文件名/行号)的键,以帮助调试。
Fat-Free让您可以以任何您想要的方式显示测试结果。您可以将输出显示为纯文本,甚至是一个漂亮的HTML模板。那么我们如何运行我们的单元测试?如果您将test.php
保存到文档根目录,您只需打开浏览器并指定地址http://localhost/test.php
即可。就这么简单。
模拟HTTP请求
F3让您能够在PHP程序内部模拟HTTP请求,以便您可以测试特定路由的行为。以下是一个简单的模拟请求:
$f3->mock('GET /test?foo=bar');
为了模拟POST请求并提交模拟的HTML表单:
$f3->mock('POST /test',['foo'=>'bar']);
预期最坏的情况发生
一旦你掌握了测试应用中最小单元的方法,你就可以继续测试更大的组件、模块和子系统——沿途检查各个部分是否正确地相互通信。测试可管理的代码块可以导致更可靠的程序,按预期工作,并将测试过程融入到你的开发周期中。你需要问自己的问题是:我已经测试了所有可能的场景吗?通常情况下,那些没有被考虑的情况很可能是导致错误的原因。单元测试在很大程度上有助于减少这些发生。即使对每个固定装置进行一些测试,也可以大大减少头痛。另一方面,完全不进行单元测试来编写应用程序会带来麻烦。
快速参考
系统变量
string AGENT
- 自动检测到的HTTP用户代理,例如
Mozilla/5.0 (Linux; Android 4.2.2; Nexus 7) AppleWebKit/537.31
。
bool AJAX
TRUE
如果检测到XML HTTP请求,否则FALSE
。
string AUTOLOAD
- 搜索路径,用于在运行时尝试自动加载框架中用户定义的PHP类。接受管道(
|
)、逗号(,
)或分号(;
)作为路径分隔符。
string BASE
- 到
index.php
主/前端控制器的路径。
string BODY
- 用于ReSTful后处理的HTTP请求正文。
bool/string CACHE
- 缓存后端。除非分配了如
'memcache=localhost'
(并且PHP memcache模块存在),否则F3会自动检测APC、WinCache和XCache的存在,并在设置为TRUE时使用第一个可用的PHP模块。如果这些PHP模块都不可用,则使用基于文件系统的后端(默认目录:tmp/cache
)。如果分配了FALSE
值,则框架禁用缓存引擎。
bool CASELESS
- 默认情况下,路由模式匹配输入URI是不区分大小写的。设置为
FALSE
以使其区分大小写。
array COOKIE, GET, POST, REQUEST, SESSION, FILES, SERVER, ENV
- 框架的PHP全局变量的等价物。变量可以在整个应用程序中使用。然而,由于安全风险,不建议在模板中直接使用。
integer DEBUG
- 堆栈跟踪的详细程度。分配1到3的值以增加详细程度。零(0)抑制堆栈跟踪。这是默认值,应该在生产服务器上分配此设置。
string DNSBL
- 逗号分隔的DNS黑名单服务器列表。如果用户的IPv4地址在指定的服务器上列出,框架会生成
403禁止
错误。
array DIACRITICS
- 外国到ASCII字符的翻译键值对。
string ENCODING
- 用于文档编码的字符集。默认值是
UTF-8
。
array ERROR
- 关于最后一次发生的HTTP错误的信息。
ERROR.code
是HTTP状态码。ERROR.status
包含错误的简要描述。ERROR.text
提供更多细节。对于HTTP 500错误,使用ERROR.trace
检索堆栈跟踪。
bool ESCAPE
- 用于启用/禁用自动转义。
string EXEMPT
- 逗号分隔的IPv4地址列表,这些地址免于DNSBL查找。
string FALLBACK
- 如果不存在翻译,则使用的语言(和字典)。
bool HALT
- 如果为TRUE(默认值),则在检测到非致命错误后,框架停止执行。
array HEADERS
- 服务器接收到的HTTP请求头。
bool HIGHLIGHT
- 启用/禁用堆栈跟踪的语法高亮。默认值:
TRUE
(需要code.css
样式表)。
string HOST
- 服务器主机名。如果
$_SERVER['SERVER_NAME']
不可用,则使用gethostname()
的返回值。
string IP
- 远程IP地址。如果HTTP客户端位于代理服务器之后,框架将从头部中获取地址。
数组JAR
- 默认cookie参数。
字符串LANGUAGE
- 当前活动语言。该值用于在由
LOCALES
指向的文件夹中加载适当的语言翻译文件。如果设置为NULL
,则从HTTPAccept-Language
请求头部自动检测语言。
字符串LOCALES
- 语言字典的位置。
字符串LOGS
- 自定义日志的位置。
混合ONERROR
- 用作自定义错误处理器的回调函数。
字符串PACKAGE
- 框架名称。
数组PARAMS
- 在
route()
模式中定义的标记的捕获值。PARAMS.0
包含相对于Web根的捕获URL。
字符串PATTERN
- 包含与当前请求URI匹配的路由模式。
字符串PLUGINS
- F3插件的路径。默认值是框架代码所在的文件夹,即
base.php
的路径。
整数PORT
- Web服务器使用的TCP/IP监听端口。
字符串PREFIX
- 字符串预置于语言字典术语之前。
布尔QUIET
- 用于抑制或启用标准输出和错误消息的开关。在单元测试中特别有用。
布尔RAW
- 禁用自动将HTTP请求体存储到
BODY
中。当处理来自php://input
的大数据且不会适合内存时,应设置为TRUE。默认值:FALSE
字符串REALM
- 完整的规范URL。
字符串RESPONSE
- 最后HTTP响应的主体。F3填充此变量,无论
QUIET
设置如何。
字符串ROOT
- 到文档根文件夹的绝对路径。
数组ROUTES
- 包含定义的应用程序路由。
字符串SCHEME
- 服务器协议,即
http
或https
。
字符串SERIALIZER
- 默认序列化器。通常设置为
php
,除非自动检测到PHPigbinary
扩展。如果需要,分配json
。
字符串TEMP
- 用于缓存、文件系统锁定、编译的F3模板等的临时文件夹。默认是Web根目录内的
tmp/
文件夹。根据您站点的安全策略相应调整。
字符串TZ
- 默认时区。更改此值将自动调用底层的
date_default_timezone_set()
函数。
字符串UI
- 用于
View
和Template
类的render()
方法的用户界面文件的搜索路径。默认值是Web根目录。接受管道(|
)、逗号(,
)或分号(;
)作为多个路径的分隔符。
回调UNLOAD
- 由框架在脚本关闭时执行。
字符串UPLOADS
- 文件上传保存的目录。
字符串URI
- 当前HTTP请求URI。
字符串VERB
- 当前HTTP请求方法。
字符串VERSION
- 框架版本。
模板指令
@token
- 将
@token
替换为等效F3变量的值。
{{ mixed expr }}
- 评估。
expr
可能包括模板令牌、常量、运算符(一元、算术、三元和关系)、括号、数据类型转换器和函数。如果不是模板指令的属性,则结果将被输出。
{{ string expr | raw }}
- 渲染未转义的
expr
。F3默认自动转义字符串。
{{ string expr | esc }}
- 渲染转义的
expr
。这是默认框架行为。只有当全局变量ESCAPE
设置为FALSE
时,才需要| esc
后缀。
{{ string expr, arg1, ..., argN | format }}
- 渲染ICU格式的
expr
并将逗号分隔的参数传递给它,其中arg1, ..., argn
是以下之一:-'date'
、'time'
、'number, integer'
、'number, currency'
或'number, percent'
。
<include
[ if="{{ bool condition }}" ]
href="{{ string subtemplate }}"
/>
- 获取
subtemplate
的内容,并在模板中当前位置插入,如果可选条件为TRUE
。
<exclude>text-block</exclude>
- 在运行时删除
text-block
。用于在模板中嵌入注释。
<ignore>text-block</ignore>
- 以原始形式显示
text-block
,不对其进行模板引擎的解释/修改。
<check if="{{ bool condition }}">
<true>true-block</true>
<false>false-block</false>
</check>
- 评估条件。如果为
TRUE
,则渲染true-block
。否则,使用false-block
。
<loop
from="{{ statement }}"
to="{{ bool expr }}"
[ step="{{ statement }}" ]>
text-block
</loop>
- 一次性评估
from
语句。检查to
属性中的表达式是否为TRUE
,渲染text-block
并评估step
语句。重复迭代,直到to
表达式为FALSE
。
<repeat
group="{{ array @group|expr }}"
[ key="{{ scalar @key }}" ]
value="{{ mixed @value }}"
[ counter="{{ scalar @key }}" ]>
text-block
</repeat>
- 根据数组变量
@group
或表达式expr
中的元素数量重复渲染text-block
。与等效PHPforeach()
语句中的键值对相同,@key
和@value
函数在同一个方式下工作。在counter
属性中,代表key
的变量每次迭代时增加1
。
<switch expr="{{ scalar expr }}">
<case value="{{ scalar @value|expr }}" break="{{ bool TRUE|FALSE }}">
text-block
</case>
.
.
.
</switch>
- PHP switch-case跳转表结构的等效。
{* text-block *}
<exclude>
的别名。
API 文档
最新文档位于http://fatfreeframework.com/。它包含框架组件的各种用法示例。
支持和许可
官方讨论论坛提供技术支持:https://groups.google.com/forum/#!forum/f3-framework
。如果您需要实时支持,可以通过Slack或IRC与开发团队和其他F3社区成员交谈。我们在FreeNode的#fatfree
频道(chat.freenode.net
)上。访问http://webchat.freenode.net/
加入对话。如果您没有IRC客户端,也可以下载Firefox Chatzilla插件或Pidgin以参与实时聊天。您还可以在Stack Overflow找到帮助。
夜间构建
F3使用Git进行版本控制。要克隆GitHub上最新的代码仓库
git clone git://github.com/bcosca/fatfree-core.git
如果您只需要测试用例的zip包,其中包含所有单元测试,请在此处下载。
要提交错误报告,请访问https://github.com/bcosca/fatfree-core/issues
。
公平许可
Fat-Free Framework是免费的,并以开源软件的形式发布,受GNU公共许可证(GPL v3)条款约束。您必须遵守许可证才能使用软件、文档和示例。如果本许可证的条款和条件对您的使用过于限制,则可以通过非常合理的费用提供替代许可。
如果您认为此软件是您的编程工具箱中的一件强大武器,它能为您节省大量时间和金钱,并在商业收益或您的企业组织中使用,请考虑向项目捐款。已经在这个项目上花费了大量的时间、精力和金钱。您的捐款有助于使项目保持活力并激励开发团队。捐赠者和赞助者享有优先支持(工作日24小时响应时间)。
致谢
Fat-Free Framework是由社区驱动的软件。没有以下个人和组织的帮助和支持,它不可能成为今天的模样
- GitHub
- JetBrains
- Stehlik & Company
- bodalgo.com
- Square Lines, LLC
- Mirosystems
- Talis Group, Ltd.
- Tecnilógica
- G Holdings, LLC
- S2 Development, Ltd.
- Store Machine
- PHP Experts, Inc.
- Meins und Vogel GmbH
- Online Prepaid Services
- Frugal Photographer
- Christian Knuth
- Florent Racineux
- Sascha Ohms
- Lars Brandi Jensen
- Eyðun Lamhauge
- Jermaine Maree
- Sergey Zaretsky
- Daniel Kloke
- Brian Nelson
- Roberts Lapins
- 博里斯·古列维奇
- 何塞·玛丽亚·加里多·迪亚兹
- 当恩·康福特
- 约翰·维伯格
- 波维拉斯·穆斯特伊基斯
- 安德鲁·斯努克
- 贾法尔·阿姆贾德
- 泰勒·麦克劳
- 雷蒙德·柯克兰
- 尤里·杰拉斯缅科
- 威廉·斯塔姆
- 山姆·乔治
- 史蒂夫·瓦西乌拉
- 安德烈亚斯·柳恩格伦
- 萨尚克·塔德帕利
- 查德·毕肖普
- 布拉德利·斯拉维克
- 利·布鲁
- 亚历山大·沙提洛
- 贾斯汀·诺埃尔
- 伊万·科瓦奇
- 托尼的互联网解决方案
- 查尔斯·斯蒂格勒
- 阿提拉·范·德·韦尔德
- Indoblo 商业有限公司
- 延斯·尼梅耶
- 拉古·维埃尔·登杜库里
- NovelLead B.V.
- 埃米尔·阿尔普
- 多米尼克·施瓦茨
- 斯文·扎尔伦德
- LucidStorm
- Nevatech
- 马特·维格罗斯
- 马克西米利安·苏梅
- 卡斯帕·弗雷
- FocusHeart
- 菲利普·劳伦斯
- 彼得·贝弗威克
- 朱迪思·格拉夫
- 兰达尔·欣茨
- 弗朗茨·约瑟夫
- 比斯瓦吉特·纳亚克
- R 摩汉
- 迈克尔·梅斯纳
- 贾森·博塞斯
- 德米特里·切尔诺夫
- 马雷克·托马内克
- 西蒙内·科西亚尼奇
- 艾伦·霍olding
- 菲利普·希施
- 奥雷利安·博特曼斯
- 克里斯蒂安·特雷普托夫
- 库巴拉耶夫·德米特里(德米特里·库巴拉耶夫)
- 亚历山德鲁·卡泰林·特兰达菲尔
- 利·哈里森
- 德米特里耶夫·伊万(伊万·德米特里耶夫)
- IT_GAP
- 谢尔盖耶夫·安德烈
- 史蒂文·J·米克松
- 罗兰·法特
- 贾斯汀·帕克
- 科斯塔斯·门尼科
- 马蒂厄-菲利普·布尔热
- 瑞安·麦基洛普
- 克里斯·克拉克
- 安南·廷·昂
- 艾利·阿贡
- 谢雷金·安德鲁
- 马雷克·托马内克
- 迪吉企业
- uonick
- 卡米尔·基布利斯
- 马尔斯·优
- 马丁·拉丁诺夫
- 马尔科夫·叶夫根尼
- 安德烈斯·埃斯皮诺萨·阿尔塞
- 马修·威廉姆森
- 安德鲁·布鲁克斯
- 史蒂夫·科夫
- 史蒂文·维滕
- 西尔万·西霍尔泽
- 托尼·舒恩布赫纳
- 马雷克·托马内克
- 德克斯特·弗雷瓦尔德
- 查德·韦斯特
- 邦德·阿金马德
- AlpiSol - Ernaldo Pisati
- 亚当·威尔金斯
- 米哈伊·弗拉维乌·莫拉尔
- 卡罗琳娜·R·莫拉
- 安德烈斯·埃斯皮诺萨·阿尔塞
- 扬·克雷尔拉克
- 埃里克·舒尔茨
- 里卡多·安德拉德
- 德里克·洛文
- 迈克尔·尼尔森
- 丹尼斯·巴赫
- 伦纳德·奥斯曼尼
特别感谢那些表示希望保持匿名的人,但他们愿意分享他们的时间,贡献代码,发送捐款,推广框架给更广泛的受众,并提供鼓励和定期的财务援助。他们的慷慨是 F3 的主要动机。
法律声明
通过向本项目捐赠,您表示您已确认、理解、接受并同意本通知中包含的条款和条件。您对 Fat-Free Framework 项目的捐赠是自愿的,不是任何服务、商品或优势的费用,向项目捐赠不使您有权获得任何服务、商品或优势。我们有权以任何合法方式并出于我们认为合法的任何合法目的使用您捐赠给 Fat-Free Framework 项目的资金,并且我们没有义务向任何一方披露方式或目的,除非适用法律要求。尽管 Fat-Free Framework 是免费软件,但我们所知,本项目没有免税地位。Fat-Free Framework 不是一个在任何国家注册的非营利公司或注册慈善机构。您的捐赠可能或可能不是可抵扣税款的;请咨询您的税务顾问。除非适用法律要求,否则我们将不会在未经您同意的情况下公布/披露您的姓名和电子邮件地址。您的捐赠不可退款。
版权(c)2009-2022 F3::Factory/Bong Cosca <[email protected]>
在 Beerpay 上支持
嘿,兄弟!帮帮我买点 🍻!