PHP的O语法

v0.2.0 2014-12-25 20:44 UTC

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("&#x213A;", "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 攻击。要使用这些功能

  1. 将此代码放入您的表单中

    <input type="hidden" name="csrftoken" value="" />

  2. 将处理表单的所有内容放在此 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 服务。