lukebaker / activerecord-php
PHP中ActiveRecord的实现。
Requires
- php: >= 5.0.0
- zendframework/zend-json: 2.4.0
This package is not auto-updated.
Last update: 2024-09-28 17:12:39 UTC
README
动机
在享受Ruby on Rails的ActiveRecord模式实现的同时,我仍主要需要在PHP环境下工作,于是我编写了此代码。当我开始这个项目时,PHP中已经存在一些ORM选项。然而,我对任何一个都没有特别满意。我的目标是创建一个与Rails语法非常相似、易于安装且快速的实现。
要求
- PHP5
- 表和列的命名遵循Rails约定。
安装
-
创建你的数据库和表,如果你还没有的话。(记得使用Rails的表和列命名约定)
-
下载 最近的ActiveRecord发布版 或
git clone https://github.com/lukebaker/activerecord-php.git
-
将其解压到项目中的models/目录内,或者将克隆的activerecord-php/目录移动到你的models/目录下。
-
现在应该有一个models/activerecord-php/目录,编辑models/activerecord-php/config.php以适应你的需求。
-
运行 models/activerecord-php/generate.php
-
这应该在models/目录内生成模型存根。编辑这些模型文件以告诉ActiveRecord表之间的关系。不要编辑*Base.php文件,因为每次运行generate.php时它们都会被覆盖。
-
使用ActiveRecord,通过包含你想要使用的模型
require_once 'models/Post.php';
示例
创建
$p = new Post(array('title' => 'First Post!11!', 'body' => 'This is the body of my post')); $p->save(); # saves this post to the table $p2 = new Post(); $p2->title = "Second Post"; $p2->body = "This is the body of the second post"; $p2->save(); # save yet another post to the db
检索
$p = Post::find(1); # finds the post with an id = 1 $p->title; # title of this post $p->body; # body of this post # returns the 10 most recent posts in an array, assuming you have a column called "timestamp" $posts = Post::find('all', array('order' => 'timestamp DESC', 'limit' => 10));
更新
$p = Post::find(1); $p->title = "Some new title"; $p->save(); # saves the change to the post # alternatively, the following is useful when a form submits an array $_POST['post'] = array('title' => 'New Title', 'body' => 'New body here!'); $p = Post::find(1); $p->update_attributes($_POST['post']); # saves the object with these attributes updated
删除
$p = Post::find(1); $p->destroy();
关系
$p = Post::find(1); # call to $p->comments results in query to get all comments for this post # a subsequent call to $p->comments would not result in a query, but use results from previous query foreach ($p->comments as $comment) { echo $comment->content; }
文档
虽然这个文档尝试记录ActiveRecord的大多数功能,但它可能并不完全完整。我已经尝试为ActiveRecord中存在的所有功能创建测试。要查看和/或运行这些测试,请检查Subversion存储库中的devel/分支。换句话说,可能有一些功能在这里没有记录,但在测试中使用。
例如,假设我们正在构建一个博客。你将会有模型类,每个类是数据库表的模型。每个模型类都在一个单独的文件中。这些文件的存根由generate.php自动为你生成。每次你更新数据库架构时,你都需要再次运行generate.php。它不会覆盖你修改过的文件,但会覆盖*Base.php文件。一旦生成了模型存根,你就可以使用它们并单独处理表。然而,为了使用ActiveRecord的关系特定功能,你需要在你的模型中指定关系,如下文“关联”部分中概述。
关联
在ActiveRecord中,我们在模型类中指定表之间的关系。有三种关系类型,1:1,1:多和多对多。
1:1
在我们的例子中,博文与slug有一个1:1的关系。这是如何在Post和Slug类中指定它的方式。
/* inside Post.php */ protected $has_one = array('slug'); /* inside Slug.php */ protected $belongs_to = array('post');
在1:1关系中,我们必须对关系的每一侧稍作不同指定,以便ActiveRecord知道关系的“方向”。我们使用belongs_to来指定包含外键(例如post_id)的模型。关系的另一侧使用has_one。由于一个对象可能有多个1:1关系,我们使用数组来允许额外的表。注意slug和post的单数使用。代码尽可能地像英语一样可读,因此当我们做1:many关系时,你会看到复数字符串。指定此关系后,你可以对你的模型做一些额外的事情。现在,在每个slug和post对象上,你可以使用→post和→slug来获取其post和slug,分别作为ActiveRecord对象。你也可以使用此机制分配slug或post。此外,保存将级联到关系。
$slug = Slug::find('first'); # SQL query to grab first slug $slug->post; # an SQL query occurs behind the scenes to find the slug's post $p = Post::find('first', array('include' => 'slug')); # SQL join $p->slug; # no SQL query here because we already got this post's slug in the SQL join in the previous line $p = Post::find('first'); $s = new Slug(array('slug' => 'super-slug')); $p->slug = $s; # assign a slug to this post $p->slug->slug = 'foobar'; $p->save(); # cascading save (post and slug are saved)
1:many
在我们的例子中,一篇帖子有多个评论,但评论只有一个帖子。以下是如何在Post和Comment类中指定它。
/* inside Post.php */ protected $has_many = array('comments'); /* inside Comment.php */ protected $belongs_to = array('post');
注意,我们使用了复数的“comments”来指定has_many,以及单数的“post”来指定belongs_to。注意评论表包含外键(post_id),因此是一个belongs_to关系。一旦我们这样做,Comment就可以像1:something关系一样做同样的事情(参见1:1)。Post现在有一些添加到1:1关系中的功能的轻微变化。现在,当访问comments属性时,你会得到一个属于此Post的评论ActiveRecord对象数组。
$p = Post::find('first'); echo $p->comments[0]->body;
你还可以通过调用→comment_ids来获取属于此帖子的评论ID列表。你可以用类似的方式设置ID。
$p = Post::find('first'); $foo = $p->comment_ids; # foo is now an array of comment ids that belong to this post array_pop($foo); # pop off last comment id array_push($foo, 23); # and another comment id to $foo $p->comment_ids = $foo; /* this will remove the comment we popped off of foo and add the comment we pushed onto foo to this post */
你还可以将新对象推送到关系中。
$c = new Comment(array('author' => 'anon', 'body' => 'first comment!!11')); $p->comments_push($c); # this call saves the new comment and associates with this post
在这个例子中,我们可能希望当评论被销毁或与其帖子解除关联时,评论也被销毁。你可以通过稍微不同地指定关系来实现这一点。你可以在任何类型的关联上这样做。在Post模型中,你可以这样做。
/* inside Post.php */ protected $has_many = array(array('comments' => array('dependent' => 'destroy')));
many:many
一个many:many关系将有一个中间表(因此也是一个模型),将两个其他表连接在一起。在我们的例子中,帖子和类别之间存在many:many关系。我们的中间表是categorizations。以下是它是如何指定的
/* inside Categorization.php */ protected $belongs_to = array('post', 'category'); /* inside Post.php */ protected $has_many = array( 'categorizations', array('categories' => array('through' => 'categorizations'))); /* inside Category.php */ protected $has_many = array( 'categorizations', array('posts' => array('through' => 'categorizations')));
由于categorizations表包含外键post_id和category_id,它与这些表有belongs_to关系。Post模型与categorizations有常规的has_many关系,与categories有特殊的has_many关系。我们指定该关系通过哪个表(categorizations),也就是说,哪个表是关系的中间表。类别到帖子关系也是类似指定的。现在,帖子和类别可以使用1:many关系中记录的特殊has_many方法。
与模型一起工作
本节适用于所有模型,无论它们可能有哪些关联。
创建
$p = new Post(array('title' => 'First Post!11!', 'body' => 'This is the body of my post')); $p->save(); # saves this post to the table $p2 = new Post(); $p2->title = "Second Post"; $p2->body = "This is the body of the second post"; $p2->save(); # save yet another post to the db
检索
检索数据涉及找到你想要查看的行,然后根据需要获取列数据。find方法的第一参数应该是以下之一
- 一个ID号码
- 一个ID号码数组
- 字符串“first”
- 字符串“all”
当第一参数是ID号码或字符串“first”时,结果将是一个ActiveRecord对象。否则,它将是一个ActiveRecord对象数组。find方法通过使用“命名参数”接受键值对数组,提供了许多不同的选项作为第二个参数。你可以传递以下键和合适的值
- limit
- order
- group
- offset
- select
- conditions
- include(用于关联)
$p = Post::find(1); # finds the post with an id = 1 $p->title; # title of this post $p->body; # body of this post # returns the 10 most recent posts in an array, assuming you have a column called "timestamp" $posts = Post::find('all', array('order' => 'timestamp DESC', 'limit' => 10));
更新
$p = Post::find(1); $p->title = "Some new title"; $p->save(); # saves the change to the post # alternatively, the following is useful when a form submits an array $_POST['post'] = array('title' => 'New Title', 'body' => 'New body here!'); $p = Post::find(1); $p->update_attributes($_POST['post']); # saves the object with these attributes updated
删除
$p = Post::find(1); $p->destroy();
钩子
以下钩子可用,只需在要使用的模型中定义同名方法即可
- before_save
- before_create
- after_create
- before_update
- after_update
- after_save
- before_destroy
- after_destroy
查询值转义
ActiveRecord会在可能的情况下正确转义传递给查询的值。然而,当你执行如下操作时,它无法进行正确的引号引用。
$p = Post::find('first', array('conditions' => "title = {$_GET['title']}"));
相反,你可以使用quote静态方法来这样引用该值。
$title = ActiveRecord::quote($_GET['title']); $p = Post::find('first', array('conditions' => "title = $title"));
手动查询
尽管希望很少,但有时你可能需要手动指定一些查询。你可以使用query静态方法。这个方法返回一个包含所有行的关联数组。
ActiveRecord::query("SELECT COUNT(*) FROM bar as b1, bar as b2 where b2.id != b1.id");
示例表结构
-- -- Table structure for table `categories` -- CREATE TABLE `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) TYPE=MyISAM; -- -- Table structure for table `categorizations` -- CREATE TABLE `categorizations` ( `id` int(11) NOT NULL AUTO_INCREMENT, `post_id` int(11) DEFAULT NULL, `category_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) TYPE=MyISAM; -- -- Table structure for table `comments` -- CREATE TABLE `comments` ( `id` int(11) NOT NULL AUTO_INCREMENT, `author` varchar(255) DEFAULT NULL, `body` text, `post_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) TYPE=MyISAM; -- -- Table structure for table `posts` -- CREATE TABLE `posts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) DEFAULT NULL, `body` text, PRIMARY KEY (`id`) ) TYPE=MyISAM; -- -- Table structure for table `slugs` -- CREATE TABLE `slugs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `slug` varchar(255) DEFAULT NULL, `post_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) TYPE=MyISAM;