abgit / myfw
一个迷你 PHP 框架
Requires
- php: ^7.3|^8.0
- aptoma/twig-markdown: 3.*
- bryanjhv/slim-session: ~3.0
- defuse/php-encryption: 2.*
- ezyang/htmlpurifier: 4.*
- league/html-to-markdown: 4.*
- linusu/bitcoin-address-validator: 0.*
- michelf/php-markdown: 1.*
- pavlakis/php-server-interface-middleware: 0.*
- pavlakis/slim-cli: 1.*
- slim/slim: ^3.0
- slim/twig-view: 2.*
Requires (Dev)
- roave/security-advisories: dev-master
README
要使用 myfw,我们只需要
-
在此处下载最新稳定版本 这里。
-
在我们的根目录添加一个 htaccess 文件
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L]
- 包含 myfw.php,初始化并运行。
require 'myfw.php'; $app = new myfw(); ... $app->run();
环境
myfw 至少可以在两种环境中使用:Web 或 Cron。
Web 环境。 当在 Web 环境中使用时,我们需要添加 get()
、post()
或 map()
方法,并在末尾添加一个 run()
。 get()
、post()
或 map()
方法需要至少两个参数:要匹配的 URL 和要执行的功能。
require 'myfw.php'; $app = new myfw(); $app->get( '/', function(){ ... }); $app->run();
Cron 环境。 当在 Cron 环境中使用时,我们只需要添加 cron()
方法。Cron 方法有两个参数,一个是在命令行执行 Cron 时传递的字符串,以及要执行的功能。
require 'myfw.php'; $app = new myfw(); $app->cron( 'somearg', function(){ ... }); $app->cron( 'somearg2', function(){ ... });
要执行上一个 somearg
Cron,我们只需要加载文件并在命令行中指定 somearg
: /bin/usr/php file.php somearg
。
hello world
让我们创建一个简单的示例。从我们的模板目录加载一个模板文件,分配一个 URL 变量并显示。
require 'myfw.php'; // init and set templates directory $app = new myfw(); $app->config( 'templates.path', __DIR__ . '/templates' ); // on root $app->get( '/', function() use ($app){ $app->render( 'mytemplate', array( 'str' => 'Hello world' ) ); }); $app->run();
我们需要通过使用 config()
告知 myfw 我们模板文件的位置,以便 render()
可以加载它们。
参数
当使用动态 URL 时,我们必须使用变量并在函数中传递它们。例如:根据 URL 显示新闻条目。
require 'myfw.php'; // init and set template directory $app = new myfw(); $app->config( 'templates.path', __DIR__ . '/templates' ); $app->get( '/news/:id', function($id) use ($app){ $app->render( 'mytemplate', array( 'str' => 'My news id is ' . $id ) ); }); $app->run();
由于 myfw 集成了 twig 模板引擎,对于之前的示例,我们需要一个 templates/mytemplate.tpl
文件
{{ str }}
条件
对于每个动态参数,我们必须添加一个模式(例如::id
),以及一个函数参数(例如:function($id)
)。作为良好的实践,我们应该始终过滤这些参数以控制参数限制、SQL 注入和其他问题。myfw 有一个 setConditions()
方法,应该描述 所有 参数,并放置在所有操作之前。
require 'myfw.php'; $app = new myfw(); $app->setConditions( array( 'id' => '[0-9]{1,5}', 'arg1' => '[0-9a-zA-Z]{3,50}', 'arg2' => 'a|b' ) ); $app->get( '/news/:id', function($id) use ($app){ ... }); $app->get( '/other/:arg1/:arg2', function($arg1,$arg2) use ($app){ ... }); $app->run();
作为替代,我们可以使用 conditions()
方法自定义每个操作。例如
$app->get( '/news/:id', function($id) use ($app){ ... })->conditions( array( 'id' => '[0-9]{1,5}' ) );
模式
大多数时候,我们至少有两个环境: 开发(当我们的应用程序在本地执行并启用了某些调试时)和 生产(当我们的应用程序在远程服务器上执行时)。这意味着我们需要根据我们的模式分配不同的配置选项。为此,我们只需要使用 configureMode()
预定义每个模式的配置选项,并分配模式。让我们预定义具有不同配置设置的 开发 和 生产 模式
require 'myfw.php'; $app = new myfw(); // choose mode $app->config( 'mode', 'production' ); $app->configureMode( 'production', function () use ($app) { $app->config( array( 'log.enable' => true, 'debug' => false )); }); $app->configureMode( 'development', function () use ($app) { $app->config( array( 'log.enable' => false, 'debug' => true )); }); ... $app->run();
定义要使用模式的方法至少有 3 种
- 硬编码:这是之前的示例,其中我们硬编码模式:
$app->config( 'mode', 'production' );
; - 通过 Apache 环境变量:我们需要自定义我们的
apache 配置
并添加一个特殊的变量SLIM_MODE
并分配模式值;这种方法不需要额外的$app->config()
调用; - 通过 PHP 环境变量:我们需要自定义我们的
php.ini
配置并添加一个变量。可以具有任何名称,例如:app.mode = "development"
并将其添加到我们的代码中:$app->config( 'mode', get_cfg_var( 'app.mode' ) );
;
组
有时创建具有相同前缀的URL操作是有用的。我们不必分别指定每个操作,可以将其分组。这有几个优点:代码变得更加简洁,我们可以创建影响所有子操作的全球功能。
$app = new myfw(); $app->group('/backend', function() use ($app){ $app->get('/adduser/:id', function ($id){ ... }); $app->get('/showuser/:id', function ($id){ ... }); }); $app->run();
表单
在我的fw中,创建表单和处理所有逻辑流程非常容易。我们只需要定义表单行为,myfw就会处理一切。我们应该
- 创建表单元素、规则和(可选)过滤器;
- 在表单首次显示时显示的默认值;
- 表单提交且有效时执行的操作
require 'myfw.php'; $app = new myfw(); $app->map( '/contact', function() use ($app){ $app->form()->addText( 'name', ... ) ->addSelect( 'chooser', ... ) ... ->addSubmit(); $app->form()->setDefault( function(){ return array( 'name' => ..., 'chooser' => ... ); }); $app->form()->onSubmittedAndValid( function() use ($app){ $formvalues = $app->form()->getValues(); // do something with $formvalues }); $app->render( 'mytplfile', array( 'form' => $app->form() ) ); )->via( 'GET', 'POST' ); $app->run();
注意,我们不需要创建新的变量来处理表单对象,所有内部myfw功能都是动态处理的。表单对象具有许多可探索的功能。
数据库
myfw对数据库的处理非常特别。每个调用都被视为一个MySQL存储过程调用。没有硬编码的SQL,没有ORM。所有SQL代码都存储在MySQL数据库中,我们只需要调用一个过程。为什么是存储过程?来自 w3resouce.com
- 存储过程速度快。MySQL服务器利用缓存的优势,就像预定义语句一样。主要速度提升来自减少网络流量。如果你有一个重复的任务,需要检查、循环、多个语句,并且没有用户交互,你可以通过调用存储在服务器上的单个过程来完成。
- 存储过程是可移植的。当你用SQL编写存储过程时,你知道它将在MySQL运行的每个平台上运行,而不需要强制你安装额外的运行时环境包,或者设置操作系统中的程序执行权限,或者如果你有不同类型的计算机,则需要部署不同的包。这就是用SQL编写而不是用Java、C或PHP等外部语言编写的好处。
- 存储过程始终作为数据库中的“源代码”可用。并且将数据与操作数据的进程关联起来是有意义的。
- _ 存储过程是可迁移的!MySQL严格遵循SQL:2003标准。其他(DB2、Mimer)也遵循。
// load a news item. call findnewsid() mysql procedure $app->db()->findOne( $result, 'findnewsid(nid|int)', array( 'nid' => $id ) );
我们只需要使用db()
,因为我们只想检索一行,我们可以使用findone()
方法来执行mysql findnewsid
过程。因为此过程需要一个整数参数,我们必须描述它,包括参数名称nid
和参数类型int
,并在数组中包含我们的参数值列表。数组值键必须与参数名称定义匹配。db方法始终返回调用状态(如果调用已执行并且从数据库发送了有效结果,则为true
,如果调用无法完成或从数据库发送了无效结果,则为false
)。调用结果存储在作为第一个参数传递的$result
变量中。
其他方法
示例:获取所有元素(选择)
// load all news. call findnews() mysql procedure $app->db()->findAll( $results, 'findnews()' );
示例:更新或插入元素
$app->db()->query( 'newsinsert(id|int,title|str|255)', array( 'id' => $id, 'title' => $title ) );
组合db和表单对象
以下是如何将表单对象与db对象组合的简单示例。我们将创建一个表单,从数据库获取默认值,将db值分配给表单元素,如果表单提交且表单元素值有效,我们将更新数据库中的项目。
require 'myfw.php'; $app = new myfw(); $app->setConditions( array( 'id' => '[0-9]{1,5}' ) ); $app->map( '/item/:id', function($id) use ($app){ $app->form()->addText( 'title', ... )->addSubmit(); $app->form()->setDefault( function () use($app,$id){ if( $app->db()->findOne( $result, 'findnewsid(nid|int)', array( 'nid' => $id ))) return $result; $app->form()->setErrorMessage( 'News item not available' )->hide(); }); $app->form()->onSubmittedAndValid( function() use ($app,$id){ if( $app->db()->apply( 'newsitemupdate(nid|int,title|str|255)', array( 'nid' => $id, 'title' => $app->form()->getValue( 'title' ) ) ) $app->form()->setSubmitMessage( 'News item details changed.' ); }); $app->render( ... ); )->via( 'GET', 'POST' );
i18n
国际化支持在PHP逻辑和模板侧处理。myfw使用PHP内置的gettext来处理翻译,在PHP和模板侧都使用_
和_n
两个函数。
主要目标是
- 在PHP和模板环境中都有标准方式;
- 与PoEdit字符串提取兼容。
php示例
- 简单
_( "hello world" );
例如:输出已翻译的"hello world"
;
- 变量
_n( "welcome %s to %s", array( $name, $portal ) );
例如:如果 $name
是 david
并且 $portal
是 domain.com
,则输出翻译后的 "欢迎david来到domain.com"
。
- 单数/复数
_n( "1 orange", "lots of oranges", $counter );
例如:如果 $counter
是 1
,则输出 "1个橙子"
,如果 $counter
是 5
,则输出 "很多橙子"
;
- 带有变量的单数/复数翻译
_n( "1 orange", "%s oranges", $counter, $counter );
例如:如果 $counter
是 1
,则输出 "1个橙子"
,如果 $counter
是 5
,则输出 "5个橙子"
;
- 根据上下文带有变量的单数/复数翻译
_n( "1 orange in %s tree", "%s oranges in %s trees", $counter, array( 'big' ), array( $counter, 'small' ) );
例如:如果 $counter
是 1
,则输出 "1个橙子在大的树上"
,如果 $counter
是 5
,则输出 "5个橙子在小的树上"
;
模板示例
- 简单
_( "hello world" )
- 简单的带变量
_n( "welcome %s to %s", [name, portal] )
- 单数/复数
_n( "1 orange", "lots of oranges", counter )
- 带有变量的单数/复数翻译
_n( "1 orange", "%s oranges", counter, counter )
- 根据上下文带有变量的单数/复数翻译
_n( "1 orange in %s tree ", "%s oranges in %s trees", counter, ['big'], [counter, 'small'] )
配置
为了在myfw中初始化i18n支持,我们需要设置i18n并通过使用i18n的setPath()
来设置翻译文件路径,对于我们的LC_MESSAGES/lang/*.mo
文件,可选的还有domain
和codeset
。然后,我们只需要更改我们的动作中的语言。
$app = new myfw(); $app->i18n()->setPath( __DIR__ . '/../i18n' ); $app->get('/:lang/something', function( $lang ) use ($app){ $app->i18n()->setLang( $lang ); .. });
i18n()也支持会话。这样我们就可以使用会话值(如果存在的话)
$app->i18n()->setLang( 'en_US', true, true );
在之前的示例中,我们更改语言为en_US
,如果可用则使用会话值(而不是en_US
),并将en_US
保存到会话中供将来使用。
PoEdit设置
PoEdit是最好的开源软件之一,用于处理所有翻译事宜。其中一个最有用的特性是能够将字符串提取出来以计算*.po文件。我们只需要做的是
- 在Python设置中添加
*.tpl
扩展名。转到文件
>首选项
> 选择Python
>编辑
> 将分隔的扩展名列表
更改为:*.py;*.tpl
- 创建一个新文件:
文件
>新建
;或者打开一个现有文件:文件
>打开
; - 创建关键字:
目录
>属性...
> 点击标签源关键字
> 添加_n:1,2
并添加_n:1
规则
myfw有一个内置库来检查模式。这些只是一些简单的布尔方法,用于检查一个字符串参数并返回true/false。这些方法在添加表单元素时与表单对象集成,这样我们就可以检查它的值,但我们也可以单独使用它们。
表单集成
$app->map( '/contact', function() use ($app){ $app->form()->addText( 'myelement', 'Label', array( 'required' => 'element is required' ) ) ... ->addSubmit(); ... )->via( 'GET', 'POST' ); $app->run();
在添加表单元素时,我们可以将规则数组作为第三个元素分配。这是一个数组,其中每个键是规则名称(方法名称),值是一个包含消息和附加选项的数组。
$app->map( '/contact', function() use ($app){ $app->form()->addText( 'myelement', 'Label', array( 'required' => 'element is required' ) ) ->addText( 'mydescription', 'Description', array( 'required' => 'Descrition is required', maxlen => array( 'Description is too big', 200 ) ) ) ... ->addSubmit(); ... )->via( 'GET', 'POST' ); $app->run();
独立
if( $app->rules()->maxlen( $x, 250 ) ){ ... }
myfw 支持的规则
- 必需
- 数字
- 最大长度
- 正则表达式
- 电子邮件
- 非电子邮件
- 金钱
- IP
- MD5
- 标签
- 仅字母
- 字符
- 最大超链接数
- 值
- 最大长度
- 最小长度
- 无标点符号
- 字母数字
- 严格字母数字
- 验证码
- 选择有效
- 匹配字段
- 不匹配字段
- 字段必需
- HTTP URL
- 域名
- 子域名
- 十六进制颜色
过滤器
与规则库类似,过滤器库可以在三个环境中使用:表单、模板和独立。
表单过滤器
在添加表单元素时。如果表单提交有效,则检索值时将过滤元素值。
// add form elements $app->form()->addText( 'myelement', 'Label', array(..), array( 'trim' ) ); $app->form()->onSubmittedAndValid( function() use ($app){ // get values (filtered) $arr = $app->form()->getValues(); });
模板过滤器
myfilters库中所有可用的过滤器以及原生twig过滤器都可以在模板中使用。
native upper filter
{{ 'welcome'|upper }}
myfw md5 filter
{{ 'welcome'|md5 }}
独立过滤器
过滤器库可以直接在PHP中调用,也可以作为独立使用。
$filteredvalue = $app->filters()->gravatar( 'someemail' );
可用的过滤器:
-
绝对值
-
批处理
-
首字母大写
-
转换编码
-
日期
-
修改日期
-
默认值
-
转义
-
第一个
-
格式化
-
连接
-
JSON编码
-
键
-
最后一个
-
长度
-
小写
-
换行符转换为换行
-
数字格式化
-
合并
-
大写
-
原始数据
-
替换
-
反转
-
四舍五入
-
切片
-
排序
-
分割
-
删除标签
-
标题化
-
删除空格
-
URL编码
-
删除空格
-
SHA1
-
MD5
-
换行符转换为换行
-
浮点值
-
整数值
-
缩短
-
十六进制颜色
-
状态颜色
-
扩展
-
订单
-
t
-
m
-
rnumber
-
bcloudname
-
gravatar
-
url
-
域名
-
markdown
-
之前
-
xss
会话
会话库包含处理php $_SESSION
的简单方法。我们应使用这个简单的库来存储、删除、获取会话信息,而不是直接访问。
// set $key $app->session()->set( $key, $label ); // set $key if not exists only $app->session()->setcheck( $key, $label ); // get $key value $k = $app->session()->get( $key ); // check if $key exists if( $app->session()->exists( $key ) ){ .. } // delete $key if( $app->session()->delete( $key ) ){ .. } // set pluralkey 'admin - x' ( same as $_SESSION[ 'admin' ][ 'x' ] ) $app->session()->set( array( 'admin', 'x' ), $valueA ); $app->session()->set( array( 'admin', 'y' ), $valueB ); // get pluralkey 'admin - x' $valueA = $app->session()->get( array( 'admin', 'x' ) ); $valueB = $app->session()->get( array( 'admin', 'y' ) );
缓存
这是一个处理apc或redis操作的简单库。在访问、设置或删除键之前,请确保使用$app->config()
设置环境。
// default apc ti to leave $app->config( 'apc.ttl', 600 ); // default redis time to leave $app->config( 'redis.ttl', 600 ); // cache set using apc $app->cache()->set( APP_CACHEAPC, $key, $value ); or $app->cache()->apcset( $key, $value ); // cache set using redis $app->cache()->set( APP_CACHEREDIS, $key, $value ); or $app->cache()->redisset( $key, $value ); // cache apc delete $app->cache()->delete( APP_CACHEAPC, $key ); // cache redis delete $app->cache()->delete( APP_CACHEREDIS, $key );
缓存引擎具有内置的中间件架构来缓存操作。如果我们有几乎静态的动作,我们可以激活iscache
,这样就可以显示缓存的版本。
require 'myfw.php'; $app = new myfw(); ... $app->get( '/news/:id', 'iscache', function($id) use ($app){ ... $app->render( 'sometemplate', array( .. ) ); }); ...
在先前的例子中,如果存在/news/:id
,则将显示所有HTML,不会进行任何额外的处理。如果缓存为空或第一次,则render()
将自动渲染模板并存储在缓存中,以便下次调用/news/:id
时显示缓存的版本。注意,如果
- 服务器检测到apc引擎;
- 是HTTP请求;
- 在动作中没有创建任何
$app->form()
;
购物车
购物车库基于会话库处理简单的购物车功能。有一些可用的方法,如getItems
、getTotalItems
、getTotal
、addItem
、isItem
、isItemValue
、addExtra
、getExtra
、removeItem
、updateItemProperty
、getItemPropertyValue
、checkItemProperty
和clear
。
日志记录
当我们需要检查应用程序内部行为时,日志记录很有用。在您的配置中设置日志级别,然后进行日志记录。
$app->config( 'log.init', true ); $app->config( 'log.level', \Slim\Log::DEBUG ); $app->cron( 'somecron', function() use ($app){ ... $app->log()->debug( 'my critical log line' ); ... $app->log()->warning( 'my warning log line' ); });
目前,日志使用内部php print
输出,这在使用cron环境时很有用。
邮件器
邮件引擎是一个用于发送邮件的非常简单的库。此库与rackspace mailgun api集成以进行投递,因此需要一些额外的config()
参数。
// default params $app->config( 'email.from', $from ); $app->config( 'email.to', $to ); $app->config( 'email.subject', $subject ); // mailgun specific $app->config( 'email.mailgunkey', $key ); $app->config( 'email.mailgundomain', $domain ); // send email $app->mailer()->send( $from, $to, $subject, $text ); // send simple email with default email.from config $app->mailer()->sendsystem( $to, $subject, $text ); // send simple email with default email.from, email.to and email.subject config $app->mailer()->sendinternal( $text );
HTML支持
邮件引擎支持模板。这意味着我们可以将模板分配给邮件库,这样当我们发送电子邮件时,就会根据该HTML模板计算电子邮件。
我们需要在模板目录中创建一个模板文件,添加一个content
标签,该标签将由邮件引擎注入消息,并通知邮件器模板文件名。
mymail.tpl
文件
some header
{{content}}
some footer
$app->config( 'email.template', 'mymail' );
邮件头
如果我们需要指定额外的邮件头,我们有一个配置参数用于此目的。如果需要添加“bcc”头,则很有用。
$app->config( 'email.headers', array( 'bcc' => 'someemail' ) );
urlFor
urlFor()
是创建应用程序URL的方法,这样您就可以自由更改路由模式,而不会破坏您的应用程序。此方法可以在PHP环境(这是原生slim方法)或模板环境中使用。
只需为每个动作添加一个名称。
$app = new myfw(); $app->get('/new/:name/go', function ($name) { echo "Some news item $name!"; })->name('newitem');
PHP环境示例
$url = $app->urlFor( "newsitem", array( "name" => $someitem ) );
模板环境示例
<a href="{{ urlFor( 'newsitem', {'name':someitem} ) }}">
例如,如果someitem
变量包含xpto
,则两个环境都会计算/new/xpto/go
URL。
病毒总览
病毒总览是谷歌基于某些杀毒分类检查文件和网站的谷歌云服务。要使用此库,我们必须设置病毒总览API密钥并使用getInfo方法分配网站信息。
// setup virustotal key $app->config( 'vtotal.api', $key ); if( $app->vtotal()->getInfo( $info, "domain.com" ) ){ // read $info ... }
transloadit
这是一个tranloadit云服务的集成库。我们只需要设置凭据,使用createAssembly()
创建一个assembly,或者使用request()
请求assembly状态信息。
// setup key and secret $app->config( 'transloadit.k', $key ); $app->config( 'transloadit.s', $secret ); // compute transloadit assembly $assembly = array( 'params' => array( 'template_id' => 'abababababababababababababa', 'steps' => array( 'mystep' => array( 'param' => $paramvar, ), 'otherstep' => array( 'otherparam' => $otherparamvar ) ) ) ); // transloadit assembly webservice call if( $app->transloadit()->createAssembly( $result, $assembly ) ){ ... }
Transloadit createAssembly()
只是初始化assembly。然后,transloadit云服务将处理它,我们需要在一段时间后检索结果。为此,我们需要使用request()
与我们在createAssembly
中检索的assembly URL,即$result
。
if( $app->transloadit()->request( $returninfo, $assemblyUrl ) ){ // do something with $resultinfo }
brightcloud
Brightcloud是一个用于分类域的云服务。目前库检索一个域的信息。我们需要设置凭据并在getInfo()
方法中提供信息。
$app->config( 'bcloud.k', $key ); $app->config( 'bcloud.s', $secret ); if( $app->bcloud()->getInfo( $result, "domain.com" ) ){ // do something with $result }
--