enygma / modler
基本模型库
Requires
- php: >=5.4
Requires (Dev)
- codeclimate/php-test-reporter: dev-master
- phpunit/phpunit: 4.2.6
README
Modler 是一组脚本,提供了一些最基本的模型和集合处理功能。
安装
您可以通过 Composer 使用 require
命令安装此库
composer require enygma/modler
类
模型
Model 类帮助使用属性和关系来建模对象。模型通过一组 properties
定义,这些属性可以是字面值或与其他模型的关联。以下是一个简单模型的示例
<?php class TestModel extends \Modler\Model { protected $properties = array( 'test' => array( 'description' => 'Test Property 1' 'type' => 'varchar' ), 'relateMe' => array( 'description' => 'Relation Property 1', 'type' => 'relation', 'relation' => array( 'model' => '\\MyApp\\OtherModel', 'method' => 'findByTestValue', 'local' => 'test' ) ) ); } ?>
在此示例中,有两个属性:test
和 relateMe
。其中 test
是一个简单的属性,允许轻松获取和设置数据
<?php $model = new TestModel(); $model->test = 'foo'; ?>
关系属性
关系属性 relateMe
要复杂一些。它被定义为一种“关系”,而 relation
配置包含三个信息
- 要调用的
model
- 在另一个模型上调用的
method
- 作为参数发送的
local
属性
所有属性都是懒加载的,所以只有在你使用代码中的 relateMe
时才会执行对该属性的调用
<?php $model = new TestModel(); $otherModel = $model->relateMe; ?>
当使用 relateMe
属性时,会在 OtherMethod
类的实例上调用 findByTestValue
方法,参数是当前 test
属性中的值。如果没有定义,则传递 null
。然后返回结果模型。因此,您可以执行一些有趣的链式操作
<?php $model = new TestModel(); echo $model->relateMe->otherModelProperty; ?>
如果 findByTestValue
方法在其内部使用 load
方法来加载数据,那么如果找到数据,您应该会得到一个值返回
<?php class OtherModel extends \Modler\Model { protected $properties = array( 'otherModelProperty' => array( 'type' => 'varchar', 'description' => 'Just another property' ) ); public function findByTestValue($value) { /** some SQL that extracts the data */ $this->load($result); } } ?>
记住,当您使用属性时,您将得到更新后的模型实例,而不是被调用方法的返回值。您需要访问返回模型的属性以从中获取数据。
关系“返回值”
您还可以使关系返回一个值而不是类的实例或模型的实例。要这样做,只需将 return
值设置为“value”并使用正常的方法 return
<?php class TestModel extends \Modler\Model { 'property1' => array( 'description' => 'Property #1', 'type' => 'varchar' ), 'relateMe' => array( 'description' => 'Return by value relation', 'type' => 'relation', 'relation' => array( 'model' => '\\MyApp\\OtherModel', 'method' => 'findByTestReturnValue', 'local' => 'test', 'return' => 'value' ) ) } class OtherModel extends \Modler\Model { public function findByTestReturnValue($property1) { return 'this is a value'; } } // Then, in the "relateMe" property we'd get... if ($testModel->relateMe === 'this is a value') { echo 'Valid match!'; } ?>
在这种情况下,我们调用 \MyApp\OtherModel::findByTestReturnValue
并请求返回值而不是获取设置值的模型。
数据验证
Modeler 还可以进行一些简单的数据验证,围绕所需的值。您可以在属性配置中设置所需状态
<?php class OtherModel extends \Modler\Model { protected $properties = array( 'prop1' => array( 'description' => 'Property #1 (Required)', 'type' => 'varchar', 'required' => true ) ); } $other = new OtherModel(); try { $other->verify(); } catch (\Exception $e) { echo 'ERROR: '.$e->getMessage(); } ?>
如果未设置值(使用 isset
检查),将抛出异常,其中包含有关缺少哪个属性的详细信息。如果您在任何地方遇到异常,但想忽略验证中的值,您可以使用 verify
方法的可选参数
<?php
$ignore = array('prop1');
try {
$other->verify($ignore);
echo 'Success!';
} catch (\Exception $e) {
echo 'Never gets here.';
}
?>
使用 MySQL 模型
Modler 还附带了一个 MySQL 版本的模型类,其中包含一些常见的数据库功能。它包括您与模型和从数据库加载模型所需的一些基础知识。它遵循某种 Active Record 方法,但有一些差异。以下是如何创建一个简单的 MySQL 模型
<?php class UserModel extends \Modler\Model\Mysql { protected $tableName = 'users'; protected $properties = [ 'username' => ['column' => 'username'] ]; } ?>
您可以看到这里有一些关键差异:首先是定义模型需要与之一起工作的 MySQL 表的 tableName
属性,其次是 properties
中的 column
属性。它将字段链接到数据库中的列,并让 Modler 知道在哪里加载数据。
您可以使用大多数预期的功能来加载和使用对象。
<?php $db = new PDO('mysql:hostname=127.0.0.1;dbname="testdb', 'username', 'password'); $user = new UserModel($db); // Finding a user by the "id" column, loads the data into the user $user->findById(1); // Or using custom criteria $user->find(['username' => 'foobar']); // Creating a new user $user = new UserModel($db); $user->username = 'newuser1'; $user->save(); // Updating the username property and saving $user->username = 'foobarbaz'; $user->save(); // Deleting a user $user = new UserModel($db); $user->findById(1); $user->delete(); ?>
您会注意到,在我们可以对用户对象进行任何操作之前,我们需要调用其上的两个find
方法之一,要么是findById
,要么是简单的find
。这将定位用户详情并将它们拉入当前对象。否则,Modler不知道要操作什么。
集合
Collection类被设计成一组模型。它是一个通用容器,可以迭代。它内部没有特殊的逻辑,不需要内容是模型(甚至是一个对象)。
它们的使用非常直接。
<?php $hats = new HatsCollection(); $hats->add(array('test' => 'foo')); foreach ($hats as $value) { print_r($value); // will output the "test" => "foo" data } echo 'Items in collection: '.count($hats)."\n"; ?>
如果您在这个集合中使用Modler模型,您可以做一些有趣的事情。上面的例子提到了Model类上的toArray
方法。集合也有一个,但它包含一个expand
参数。如果此参数设置为true
(默认为false
),它将对集合中的每个项目调用toArray
方法,并返回这些结果。例如
<?php $hat1 = new HatModel(); $hat1->type = 'fedora'; $hat2 = new HatModel(); $hat2->type = 'sombrero'; $hats = new HatsCollection(); $hats->add($hat1); $hats->add($hat2); $result = $hats->toArray(true); /** * Result will be array( * array('type' => 'fedora'), * array('type' => 'sombrero') * ) */ ?>
对Hats
模型也会调用toArray
,将它们转换为它们的数组版本并附加到输出中。
与模型类似,自定义集合方法可能不会返回一个值。相反,它们应该填充它们所在的数据。请参见下一节,其中有一个可能会更清晰的例子。
使用MySQL集合
与Modler附带包含的MySQL模型类似,也存在一个简单的MySQL集合。它主要是一个方便的方法,用于获取数据并使其更容易加载到数据库中。如下例所示(findColorsByType
),您可以轻松地将查询结果推入一组对象并将它们添加到集合中。以下是一个例子
<?php class UserCollection extends \Modler\Collection\Mysql { public function findUsersByType($type) { $sql = 'select username from users where type = :type'; $results = $this->fetch($sql, array('type' => $type)); foreach ($results as $result) { $user = new Usermodel($this->getDb(), $result); $this->add($user); } } } ?>
然后您将有一组与您查询的类型匹配的UserModel
对象。请注意,查询处理使用绑定参数/预编译语句来执行。
结合它们
让我们使用模型提供的关联将它们结合起来并填充一个集合
<?php class HatsCollection extends \Modler\Collection { public function findColorsByType($type) { foreach ($results as $result) { $color = new \Modler\Color($result); $this->add($color); } } } class HatModel extends \Modler\Model { protected $properties = array( 'type' => array( 'description' => 'Hat Type', 'type' => 'varchar' ), 'colors' => array( 'description' => 'Hat Colors', 'type' => 'relation', 'relation' => array( 'model' => '\\Modler\\HatsCollection', 'method' => 'findColorsByType', 'local' => 'type' ) ) ); } $hat = new HatModel(); $hat->type = 'toque'; // This returns a Hats Collection with the data populated $colors = $hat->colors; ?>
现在当我们访问$hat->colors
属性时,我们将从$colors
中获取一个实例,它是加载了数据的HatsCollection
。然后您可以像使用正常集合一样使用它,并使用toArray
来获取其内容。
过滤
集合还允许您根据自定义逻辑进行一些基本的过滤。您可以使用filter
方法对数据进行通过/失败检查,并从集合中添加/删除内容。以下是一个例子
<?php $this->collection->add(array('foo' => 'bar')); $this->collection->add(array('baz' => 'test')); $filtered = $this->collection->filter(function($value) { return (isset($value['foo'])); }); print_r($filtered); ?>
在这种情况下,我们检查foo
数组键是否设置。它仅在一种情况下(值为"bar")设置,因此结果集合filtered
中只包含这个元素。您传递给的可调用函数应接受一个参数,即值,并应返回一个布尔值,表示检查的通过/失败状态。值不能通过此方法修改。
切片
您也可以在不使用toArray
导出全部内容的情况下获取集合数据的部分。slice
方法可以轻松获取您想要的数据部分。第一个参数是起始索引,第二个(可选)是返回的项目数量。它使用array_slice函数。
<?php $this->collection->add('foo'); $this->collection->add('bar'); $this->collection->add('baz'); $this->collection->add('test'); // This will return: array('bar', 'baz', 'test') $sliced = $this->collection->slice(1); // This will return: array('baz') $sliced = $this->collection->slice(2, 1); ?> ``` ### Guarding and Array Filtering `Modler` also includes two other concepts around the contents of the model data: guarding and filtering (not the same as the `filter` method above). **Guarding** lets you protect properties from being set when the `load` method is called or when set as a property on the object. This helps to prevent mass assignment security issues. For example, we make a model with two properties and guard the `id` value so it can never be overriden: ```php <?php class HatModel extends \Modler\Model { protected $properties = array( 'id' => array( 'description' => 'Hat ID', 'type' => 'integer', 'guard' => true ), 'type' => array( 'description' => 'Hat Type', 'type' => 'varchar' ) ); } // Now if we try to set it... $hat = new HatModel(); $hat->id = 1234; var_export($hat->id); // result is NULL // Calling load has the same effect $hat->load(array('id' => 1234)); ?> ``` There is a second optional paramater for the `load` method that turns off this guarding and allows the setting of all values (useful when loading objects from a datasource): ```php <?php $data = array('id' => 1234); $hat->load($data, false); var_export($hat->id); // result is 1234 ?> ``` The other feature, array filtering, works with the `toArray` method and lets you filter out values when you call it. This can be useful if you have an object with sensitive data like password hashes that don't need to be stored. Here's an example: ```php <?php class UserModel extends \Modler\Model { protected $properties = array( 'username' => array( 'description' => 'Username', 'type' => 'varchar' ), 'password' => array( 'description' => 'Password', 'type' => 'varchar' ) ); } // To get the result without the password, we call it with the filter paramater $user = new UserModel( array('username' => 'foobar', 'password' => password_hash('foobar')) ); $result = $user->toArray(array('password')); print_r($result); // results in an array with just array('username' => 'foobar') ?> ``` ### Array "taking" and ordering There's also a similar interface to the `slice` method on Modler collections called `take`. With slice you can get more specific with the section of the results but if you need something simpler, you can just "take" a few off the top: ```php <?php // Add a bunch of items to our collection $data = array('foo', 'bar', 'baz', 'quux', 'foobar', 'barbaz'); $collection = new \Modler\Collection(); foreach ($data as $value) { $collection->add($value); } // You can limit the current collection and return $collection = $collection->take(3); // this returns 3 echo 'count: '.count($collection); // This returns Array('foo', 'bar', 'baz') print_r($collection->toArray()); ?> ``` **NOTE:** The collection returned is a new instance. The data in the previous collection is untouched. You can also *sort* the data in the current collection (this does modify the collection dataset) either as a set of strings or by a property on an object. First, as strings: ```php <?php // Add a bunch of items to our collection $data = array('foo', 'bar', 'baz', 'quux', 'foobar', 'barbaz'); $collection = new \Modler\Collection(); foreach ($data as $value) { $collection->add($value); } $collection->order(); // returns Array('bar', 'barbaz', 'baz', 'foo', 'foobar', 'quux'); print_r($collection->toArray()); ?> ``` The default sort order is descending, but you can tell it to sort ascending: ```php <?php $collection->order(\Modler\Collection::SORT_ASC); ?> ``` And, if you want to sort objects, you provide the name of the property as the second parameter: ```php <?php // Add a bunch of items to our collection $data = array('foo', 'bar', 'baz', 'quux', 'foobar', 'barbaz'); $collection = new \Modler\Collection(); foreach ($data as $value) { $collection->add($value); } $collection->order(\Modler\Collection::SORT_DESC, 'test'); /** * Returns set of objects ex: * [0] => stdClass Object * ( [test] => bar ) * * [1] => stdClass Object * ( [test] => barbaz ) * * [2] => stdClass Object * ( [test] => baz ) */ print_r($collection->toArray()); ?> ```