PHP的O语法
Requires
- php: >=5.3.6
- ext-mbstring: *
This package is not auto-updated.
Last update: 2024-09-14 13:40:32 UTC
README
PHP的O语法
这是PHP元编程的一次实验,以提供一个更合理的API。此库需要PHP 5.3和mbstring模块。
要开始使用它,请在页面顶部包含以下内容
<?php namespace O; include "O.php";
您可以使用composer安装"jsebrech/o",然后像这样加载
<?php namespace O; include "vendor/autoload.php"; O::init();
也可以分别加载下面描述的每个组件
include("path/to/O/StringClass.php");
echo O\s("foo")->replace("foo", "bar");
目录
字符串和数组
O的核心是三个字母的函数:s()
、a()
和o()
。
s()
函数用于向字符串添加方法
echo s("abc")->len();
// 3
echo s("abc")->substr(2);
// c
s()
将所有标准字符串函数转换为具有相同行为的方法
s($haystack)->pos($needle)
代替str_pos($haystack, $needle)
,还有->ipos()
、->rpos()
和->ripos()
s($haystack)->explode($delimiter)
代替explode($delimiter, $haystack)
- 类似地:
->trim()
、->ltrim()
、->rtrim()
、->pad()
、->len()
、->tolower()
、->toupper()
、->substr()
、->replace()
、->ireplace()
、->preg_match()
、->preg_match_all()
、->preg_replace()
、->in_array()
。 - 最后,
->html()
是围绕html_special_chars()
的安全包装。
支持数组索引操作符
- 在PHP 5.3中:
$s = s("abc"); echo $s[2]; // "c"
- 在PHP 5.4中:
echo s("abc")[2]; // "c"
基本上,这是标准PHP字符串API,具有以下优点
- 方法语法消除了haystack/needle混淆。
- 一切都是UTF-8意识,使用mbstring函数和自动字符集头。
s()
函数还实现了JavaScript的字符串API
->charAt()
、->indexOf()
、->lastIndexOf()
、->match()
、->replace()
、->split()
、->substr()
、->substring()
、->toLowerCase()
、->toUpperCase()
、->trim()
、->trimLeft()
、->trimRight()
和->valueOf()
。
a()
对数组做同样的事情。
实现的方法包括:->count()
、->has()
(代替in_array()
)、->search()
、->shift()
、->unshift()
、->key_exists()
、->implode()
、->keys()
、->values()
、->pop()
、->push()
、->slice()
、->splice()
、->merge()
、->map()
、-reduce()
、->sum()
、->begin()
(代替reset()
)、->next()
、->current()
、->each()
和->end()
。
s()
函数示例,字符串相似度算法
// adapted from
// http://cambiatablog.wordpress.com/2011/03/25/algorithm-for-string-similarity-better-than-levenshtein-and-similar_text/
function stringCompare($a, $b) {
$a = s($a); $b = s($b);
$i = 0;
$segmentCount = 0;
$segments = array();
$segment = '';
while ($i < $a->len()) {
if ($b->pos($a[$i]) !== FALSE) {
$segment = $segment.$a[$i];
if ($b->pos($segment) !== FALSE) {
$segmentPosA = $i - s($segment)->len() + 1;
$segmentPosB = $b->pos($segment);
$positionDiff = abs($segmentPosA - $segmentPosB);
$positionFactor = ($a->len() - $positionDiff) / $b->len();
$lengthFactor = s($segment)->len()/$a->len();
$segments[$segmentCount] = array(
'segment' => $segment,
'score' => ($positionFactor * $lengthFactor)
);
} else {
$segment = '';
$i--;
$segmentCount++;
};
} else {
$segment = '';
$segmentCount++;
};
$i++;
};
$getScoreFn = function($v) { return $v['score']; };
$totalScore = a(a($segments)->map($getScoreFn))->sum();
return $totalScore;
}
echo stringCompare("joeri", "jori"); // 0.9
$looksLikeO = mb_convert_encoding("℺", "UTF-8", "HTML-ENTITIES");
echo stringCompare("joeri", "j".$looksLikeO."eri"); // 0.8
注意,最后一行证明了s()
方法具有UTF-8意识,因为0.8代表一个字符的差异。
对象和类型
o()
函数用于将数组或字符串转换为对象。字符串被视为JSON数据。
$o = o('{"key":"value"}');
echo $o->key; // outputs "value"
它可以用来将对象或JSON数据转换为指定的类型
class IntKeyValue {
/** @var int */
public $key;
};
$o = o('{"key":"5"}')->cast("IntKeyValue");
echo getclass($o) . " " . gettype($o->key); // O\IntKeyValue integer
您正在转换到的类上的属性可以具有类型注解,描述要转换的类型
/** @var float */
:转换为数字/** @var string[] */
:转换为字符串数组/** @var MyType */
:转换为嵌套复杂类型/** @var array[int]MyType */
:转换为具有int键和MyType值的数组
支持的基本类型有
- void:成为NULL
- bool/boolean
- int/integer:转换失败时成为NULL
- float/double:转换失败时成为NULL
- 字符串
- 混合:不转换值
- 资源:如果不是资源,则变为NULL
- 对象:使用o()转换为stdObject(接受JSON字符串)
- DateTime:将字符串或整数转换为DateTime实例
任何未能转换为正确类型的部分数据都变为NULL。这是强制JSON输入为正确类型的一种简单方法。
提示
- 您可以使用
convertValue($value, $type)
将任何值转换为任何类型。该->cast()
方法是对该功能的一个方便包装。 - 您可以通过将其转换为自身的类型来“修复”任何对象上的属性类型。(例如,确保整数不是秘密的字符串)
您还可以通过将其转换为定义的类型来过滤$_REQUEST
数组
class RequestParams {
/** @var string */
public $foo = "";
/** @var int */
public $bar = 1;
}
$request = o($_REQUEST)->cast("RequestParams");
print_r($request);
当您用?foo=test调用该脚本时,它将输出
O\RequestParams Object
(
[foo] => test
[bar] => 1
)
foo
参数从$_REQUEST
数组中获取,但bar
从类型定义中获取其默认值。如果指定了bar,则如果可能,它将自动转换为整数(否则变为NULL)。
输入验证
如前一小节所述,o()->cast()
方法是一种强制输入为特定类型的方便方法。然而,为了正确定义您的输入,您不仅要验证类型,还要验证值的范围。
为此,php-o实现了JSR-303 (Java Bean Validation)。
用示例来说明最容易
class Validatable {
/**
* @var string
* @NotNull
* @Size(min=0,max=5)
*/
public $text = "";
/**
* @var float
* @Min(0)
*/
public $positive = 0;
}
$obj = o(array("text" => "123456", "positive" => -1))->cast("Validatable");
$validation = Validator::validate($obj);
print_r($validation);
这将输出以下错误
Array
(
[0] => O\ConstraintViolation Object
(
[message] => Size must be between 0 and 5
[constraint] => Size
[rootObject] => O\Validatable Object
(
[text] => 123456
[positive] => -1
)
[propertyPath] => text
[invalidValue] => 123456
)
[1] => O\ConstraintViolation Object
(
[message] => Must be >= 0
[constraint] => Min
[rootObject] => O\Validatable Object
(
[text] => 123456
[positive] => -1
)
[propertyPath] => positive
[invalidValue] => -1
)
)
简而言之,Validator::validate方法将执行指定注释中每个属性的检查。结果是包含验证错误的数组,如果所有属性都有效,则该数组为空。
支持的注释如下
- @Null:可以用于在子类中覆盖@NotNull。
- @NotNull:属性不接受NULL作为值。如果您不指定此选项,NULL是一个有效值(即使它违反其他验证规则)。
- @NotEmpty:与@NotNull相同,并且字符串不能为"",或空白,数组必须至少有一个项目。
- @Valid:递归验证此属性(具有类型注释的对象属性)
- @AssertTrue
- @AssertFalse
- @Min(value):属性必须大于等于该值
- @Max(value):属性必须小于等于该值
- @Size(min=value,max=value):数组或字符串长度必须满足这些约束(支持只指定min或max)
- @DecimalMin(value):与@Min相同,但可以处理大数字
- @DecimalMax(value):与@Max相同,但可以处理大数字
- @Digits(integer=value,fraction=value):指定的数字最多有这么多整数或小数位数
- @Past:日期必须在过去(支持DateTime实例、日期字符串和整数时间戳)
- @Future:日期必须在将来
Validator是一个可插拔的框架。您可以轻松地添加自己的注释。查看O源代码了解如何。
链式操作
c()
函数实现了非默认流式API的对象。换句话说,它包装了一个对象,使得该对象上的方法返回一个可连缀的对象。
这意味着您可以进行类似以下操作
echo c(s("ababa"))->explode("b")->implode("c");
// outputs acaca
简而言之,您可以执行类似jQuery的方法链。
如果您在任何时候想要获取链式对象内部的对象,您可以使用->raw()
方法
$s = c(s("123abcxxx"))->substr(3)->rtrim("x")->raw();
// $s === "abc"
您可以在任何类型上使用 c()
函数,而不仅仅是 O 提供的特殊类型(例如在 DateTime 类型上)。如果返回值是原始类型(如字符串或数组),则会将其转换为智能类型。
echo c(new \DateTime())->format("Y-m-d")->explode("-")->pop();
// contrived example to output the current day
提供了简写函数。
- cs() == c(s())
- ca() == c(a())
- co() == c(o())
会话处理
O 默认情况下设置会话以使其安全。
当您执行 session_start()
时,O 保证以下内容:
- 会话 cookie 具有httpOnly标志,如果会话是通过 HTTPS 创建的,还具有secure标志。
- 会话 ID 不会在 URL 中传递,而只通过 cookie 传递。
- 会话名称已从默认名称更改为。
- 会话 ID 在第一次请求时更改,以防止会话固定。
提供了一些便利功能来防止 CSRF 攻击。要使用这些功能
-
将此代码放入您的表单中
<input type="hidden" name="csrftoken" value="" />
-
将处理表单的所有内容放在此 if 语句内
if (is_csrf_protected()) { ...
虽然它并不完美,但它应该足以作为基本预防措施。
还有一个会话包装类,可以提供面向对象的感觉。
$session = new Session();
echo $session->foo; // == $_SESSION["foo"], isset implicitly performed
echo $session->getCSRFToken(); // == get_csrf_token();
if (!$session->isCSRFProtected()) die(); // == is_csrf_protected();
PDO
为了改进其 API,PDO 被包装。
将 PDO 语句的 fetch 方法直接添加到 PDO 对象中。
$db = new O\PDO("sqlite::memory:");
$rows = $db->fetchAll(
"select * from test where id <> :id",
array("id" => 2) // bound parameter by name
);
$row = $db->fetchRow(
"select * from test where id = ?",
array(3) // bound parameter by position
);
$col = $db->fetchColumn(
"select description, id from test where id <> :id",
array("id" => 1), // NULL to skip
1 // return second column
);
它还添加了一个新的 fetch 方法,用于获取单个值。
$value = $db->fetchOne(
"select description from test where id = :id",
array("id" => 2)
);
参数绑定支持绑定多个参数。
匿名绑定
$value = $db->prepare(
"select count(*) from test where id <> ? and id <> ?"
)->bindParams(array(2, 3))->execute()->fetchColumn(0);
命名绑定(通过对象或关联数组)
$params = new StdClass();
$params->id = 4;
$params->desc = "foo";
$stmt = $db->prepare(
"select description from test where id = :id and description <> :desc");
$value = $stmt->bindParams($params)->execute()->fetchColumn(0);
请注意,API 是流畅的(允许链式调用)。
您可以通过 fluent 选项禁用此功能。
$db = new O\PDO("sqlite::memory:", "", "", array("fluent" => false));
还有基本 CRUD 操作的简写方法。
$insertId = $db->insert(
"test", // table
array("description" => "foo")
);
// uses PDO::lastInsertId to return the id
$count = $db->update(
"test",
array("description" => "foo"), // set to this
"id >= :id1 and id <= :id2", // where
array("id1" => 2, "id2" => 6) // where parameters
);
$count = $db->delete(
"test",
"id >= :id1 and id <= :id2",
array("id1" => 2, "id2" => 6)
);
您可以将分析器附加到它以获取每个查询的分析。
$profiler = new O\PDOProfiler();
$db->setProfiler($profiler);
$db->query("select count(*) from test where id = :id", array("id" => 6));
$profiles = $profiler->getProfiles();
print_r($profiles);
-->
Array(
[0] => Array(
[0] => 7.7009201049805E-5 = elapsed time
[1] => 1419201522.886 = start time
[2] => select count(*) from test where id = :id
[3] => Array([:id] => 6)
)
)
示例应用程序
有一个 示例应用程序,展示了 O 如何在实际中应用。这还展示了如何通过 o()->render()
方法使用 HTML 模板。还有一个 示例应用程序,展示了如何构建 Web 服务。