lukebaker/activerecord-php

PHP中ActiveRecord的实现。

0.4.0 2015-04-12 03:17 UTC

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约定。

安装

  1. 创建你的数据库和表,如果你还没有的话。(记得使用Rails的表和列命名约定)

  2. 下载 最近的ActiveRecord发布版

    git clone https://github.com/lukebaker/activerecord-php.git
    
  3. 将其解压到项目中的models/目录内,或者将克隆的activerecord-php/目录移动到你的models/目录下。

  4. 现在应该有一个models/activerecord-php/目录,编辑models/activerecord-php/config.php以适应你的需求。

  5. 运行 models/activerecord-php/generate.php

  6. 这应该在models/目录内生成模型存根。编辑这些模型文件以告诉ActiveRecord表之间的关系。不要编辑*Base.php文件,因为每次运行generate.php时它们都会被覆盖。

  7. 使用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;