diff --git a/README.md b/README.md new file mode 100644 index 00000000000..c6de9700aaf --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Laravel 4 中文文档 + +Laravel 4终于在2013年5月29号发布了正式版,很多Laravel粉丝也将进入3到4的升级旅程。现在,是时候用中文文档帮大家顺利了解Laravel 4了。 + +## 如何参与翻译 + +此文档会定期与英文文档进行同步,根据差异修订中文文档,因此,如果你看到的某篇文档已经大部分被翻译,但是某些内容保留了英文原文,那就是新修订的部分,你可以随时clone本文档,补足中文翻译后直接提交Pull Request即可。 + +所有为本文档贡献的朋友都将展示在“贡献者名单”。 + +## Laravel 4.0 中文文档翻译任务分配 + +- 王赛 + - introduction.md + - redis.md + - routing.md + - packages.md + - contributing.md + - configuration.md + - lifecycle.md + - cache.md + - artisan.md + - documentation.md + - mail.md + - events.md + - routing.md + - errors.md + - responses.md + - commands.md +- /;!痛在远方 + - eloquent.md +- 徐小花 + - html.md + - templates.md + - installation.md + - controllers.md + - session.md + - queues.md +- Mking + - pagination.md + - database.md + - queries.md + - requests.md + - helpers.php +- 羊小闲 + - validation.md +- 何文祥 + - localization.md + - security.md + - testing.md + - quick.md + - ioc.md + - facades.md + + +## 版权协议 + +所有中文文档遵循 [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/) 协议。对于任何形式的使用,请保留译者的署名、网址、联系方式等信息。 + +原始英文文档请参照[Laravel Docs](https://github.com/laravel/docs)。 + + diff --git a/cache.md b/cache.md index 4a52ea55812..fa3eaf11b43 100644 --- a/cache.md +++ b/cache.md @@ -91,9 +91,9 @@ Cache sections allow you to group related items in the cache, and then flush the **Accessing A Cache Section** - Cache::section('people')->put('John', $john); + Cache::section('people')->put('John', $john, $minutes); - Cache::section('people')->put('Anne', $anne); + Cache::section('people')->put('Anne', $anne, $minutes); You may also access cached items from the section, as well as use the other cache methods such as `increment` and `decrement`: diff --git a/cn/artisan.md b/cn/artisan.md new file mode 100644 index 00000000000..a6d398e155f --- /dev/null +++ b/cn/artisan.md @@ -0,0 +1,38 @@ +# Artisan CLI + +- [简介](#introduction) +- [用法](#usage) + + +## 简介 + +Artisan是Laravel中自带的命令行工具的名称。它提供了一些开发过程中有用的命令用。它是基于强大的Symfony Console 组件开发的。 + + +## 用法 + +执行 `list` 命令可以列出所有可用的Artisan命令: + +**列出所有可用的命令** + + php artisan list + +每个命令都包含有 "help" 信息,用以提供此命令所允许的参数和选项。只需在命令前边添加 `help` 即可: + +**查看某个命令的帮助文档** + + php artisan help migrate + +通过指定 `--env` 选项,你可以指定命令执行时的配置环境: + +**指定配置环境** + + php artisan migrate --env=local + +你可以使用 `--version` 选项查看当前安装的Laravel的版本: + +**显示当前的Laravel版本** + + php artisan --version + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) diff --git a/cn/cache.md b/cn/cache.md new file mode 100644 index 00000000000..c624e30cf1a --- /dev/null +++ b/cn/cache.md @@ -0,0 +1,120 @@ +# 缓存 + +- [配置](#configuration) +- [缓存用法](#cache-usage) +- [增加 & 减少](#increments-and-decrements) +- [缓存区](#cache-sections) +- [数据库缓存](#database-cache) + + +## 配置 + +Laravel 对不同的缓存机制提供了一套统一的API。缓存配置信息存放于`app/config/cache.php`文件。在该配置文件中,你可以指定整个应用程序所使用的缓存驱动器。Laravel自身支持大多数流行的缓存服务器,例如[Memcached](http://memcached.org)和[Redis](http://redis.io)。 + +缓存配置文件还包含了其他配置项,文件里都有详细说明,因此,请务必查看这些配置项和其描述信息。默认情况下,Laravel被配置为使用`file`缓存驱动,它将数据序列化,并存放于文件系统中。在大型应用中,强烈建议使用基于内存的缓存系统,例如Memcached或APC。 + + +## 缓存用法 + +**将某一数据存入缓存** + + Cache::put('key', 'value', $minutes); + +**当某一数据不在缓存中是才将其保存** + + Cache::add('key', 'value', $minutes); + +**检查缓存中是否有某个key对应的数据** + + if (Cache::has('key')) + { + // + } + +**从缓存中取得数据** + + $value = Cache::get('key'); + +**从缓存中取得数据,如果数据不存,则返回指定的默认值** + + $value = Cache::get('key', 'default'); + + $value = Cache::get('key', function() { return 'default'; }); + +**将数据永久地存于缓存中** + + Cache::forever('key', 'value'); + +有时你可能想从缓存中取得某项数据,但是还希望在数据不存在时存储一项默认值。那就可以通过 `Cache::remember`方法实现: + + $value = Cache::remember('users', $minutes, function() + { + return DB::table('users')->get(); + }); + +还可以将`remember`和`forever`方法结合使用: + + $value = Cache::rememberForever('users', function() + { + return DB::table('users')->get(); + }); + +注意:所有存在于缓存中的数据都是经过序列化的,因此,你可以存储任何类型的数据。 + +**从缓存中删除某项数据** + + Cache::forget('key'); + + +## 增加 & 减少 + +除了`文件`和`数据库`驱动器,其他驱动器都支持`增加`和`减少`操作: + +**让某个值增加** + + Cache::increment('key'); + + Cache::increment('key', $amount); + +**让某个值减少** + + Cache::decrement('key'); + + Cache::decrement('key', $amount); + + +## 缓存区 + +> **注意:** `文件`或`数据库`缓存驱动都不支持缓存区。 + +缓存区允许你将相关数据项分组存放,然后可以对整个区进行清空操作。要想访问缓存区,需要使用`section`方法: + +**访问缓存区** + + Cache::section('people')->put('John', $john, $minutes); + + Cache::section('people')->put('Anne', $anne, $minutes); + +你也可以从缓存区中取得缓存项,也可以使用其他的缓存方法,例如`increment`和`decrement`: + +**从缓存区中取得数据项** + + $anne = Cache::section('people')->get('Anne'); + +你可以使用flush方法清空整个缓存区: + + Cache::section('people')->flush(); + + +## 数据库缓存 + +当使用`数据库`缓存驱动时,你需要设置一个数据表来缓存数据。以下是表`Schema`定义案例: + + Schema::create('cache', function($table) + { + $table->string('key')->unique(); + $table->text('value'); + $table->integer('expiration'); + }); + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) \ No newline at end of file diff --git a/cn/commands.md b/cn/commands.md new file mode 100644 index 00000000000..2c97ef93686 --- /dev/null +++ b/cn/commands.md @@ -0,0 +1,135 @@ +# Artisan 开发 + +- [简介](#introduction) +- [构建命令](#building-a-command) +- [注册命令](#registering-commands) +- [调用其他命令](#calling-other-commands) + + +## 简介 + +除了 Artisan 已经提供的命令,你也可以为应用程序构建自己的命令。你可以将自己定制的命令保存在 `app/commands` 目录下,同样,你也可以选择任意的存储目录,只要你的命令能够按照 `composer.json` 中的设置被自动加载即可。 + + +## 构建命令 + +### 生成类 + +为了创建一个新命令,你可以使用Artisan中的 `command:make` 命令生成一个骨架作为你的起点: + +**生成一个命令类** + + php artisan command:make FooCommand + +默认情况下,生成的类文件被存放在 `app/commands` 目录下,同时你也可以指定自定义目录和命名空间: + + php artisan command:make FooCommand --path=app/classes --namespace=Classes + +### 实现命令 + +一旦命令被生成,你应当填写这个类的 `name` 和 `description` 属性,这些属性将在展示命令的 `list` 页面显示出来。 + +当命令运行的时候 `fire` 函数将被调用。你可以在这个函数里实现任何业务逻辑。 + +### 参数 & 选项 + +可以通过 `getArguments` 和 `getOptions` 函数为命令定义任何接受的参数和选项。这两个函数都将返回一个数组。 + +当定义 `arguments`,数组定义值表示如下: + + array($name, $mode, $description, $defaultValue) + +参数 `mode` 的值可以是 `InputArgument::REQUIRED`, `InputArgument::OPTIONAL` 中的任意一个。 + +当定义 `options`,数组定义值表示如下: + + array($name, $shortcut, $mode, $description, $defaultValue) + +对于选项,参数 `mode` 的值可以是`InputOption::VALUE_REQUIRED`, `InputOption::VALUE_OPTIONAL`, `InputOption::VALUE_IS_ARRAY`, `InputOption::VALUE_NONE` 中的一个。 + +`VALUE_IS_ARRAY` 模式表明,该开关可用于多次调用该命令时: + + php artisan foo --option=bar --option=baz + +`VALUE_NONE` 选项表明该选项值只能用来作为一个开关: + + php artisan foo --option + +### 获取输入 + +当命令执行的时候,你显然需要获取该命令所接收的参数和选项。要做到这一点,你可以使用 `argument` 和 `option` 函数: + +**获取一个参数的值** + + $value = $this->argument('name'); + +**获取所有参数** + + $arguments = $this->argument(); + +**获取一个选项的值*** + + $value = $this->option('name'); + +**获取所有选项** + + $options = $this->option(); + +### 输出 + +你可以使用`info`、`comment`、`question` 和 `error`方法将输出发送到控制台。这些函数中的每一个将根据它们的目的使用合适的 ANSI 颜色。 + +**发送信息到终端** + + $this->info('Display this on the screen'); + +**发送错误消息到终端** + + $this->error('Something went wrong!'); + +### 询问输入 + +你可以使用 `ask` 和 `confirm` 函数提示用户输入: + +**询问用户输入** + + $name = $this->ask('What is your name?'); + +**询问用户输入密码** + + $password = $this->secret('What is the password?'); + +**询问用户确认** + + if ($this->confirm('Do you wish to continue? [yes|no]')) + { + // + } + +你也可以为 `confirm` 指定一个默认值,应该是 `true` 或者 `false`: + + $this->confirm($question, true); + + +## 注册命令 + +一旦你的命令完成后,你需要使用 Artisan 进行注册,这样才能够被使用。这通常在 `app/start/artisan.php` 文件中完成。在这个文件中,你可以使用 `Artisan::add` 函数注册命令: + +**注册一个 Artisan 命令** + + Artisan::add(new CustomCommand); + +如果你的命令在应用程序的 [IoC 容器](/docs/ioc) 中注册,你可以使用 `Artisan::resolve` 函数使它对 Artisan 可用: + +**注册一个在 IoC 容器中的命令** + + Artisan::resolve('binding.name'); + + +## 调用其他命令 + +有时你可能希望在你的命令中调用其他命令,你可以通过使用 `call` 函数实现: + +**调用其他命令** + + $this->call('command.name', array('argument' => 'foo', '--option' => 'bar')); diff --git a/cn/configuration.md b/cn/configuration.md new file mode 100644 index 00000000000..9c6c78d4b12 --- /dev/null +++ b/cn/configuration.md @@ -0,0 +1,94 @@ +# 配置 + +- [简介](#introduction) +- [环境配置](#environment-configuration) +- [维护模式](#maintenance-mode) + + +## 简介 + +所有关于Laravel框架的配置文件都存放在`app/config`目录里。所有文件里的配置选项都有说明文档,因此你可以轻松的查看这些文件,并熟悉这些配置项。 + +当你需要在运行时访问配置项时,可以使用`Config`类: + +**获取一个配置项的值** + + Config::get('app.timezone'); + +如果配置项不存在,你还可以指定返回的默认值: + + $timezone = Config::get('app.timezone', 'UTC'); + +注意"点"式语法可以用来访问不同文件里的配置项的值。你还可以在运行时为配置项赋值。: + +**为配置项赋值** + + Config::set('database.default', 'sqlite'); + +在程序运行时设置的配置值只在本次请求中有效,不会对以后的请求造成影响。 + + +## 环境配置 + +通常应用程序根据不同的运行环境确定不同的配置项的值是非常有用的。例如,你也许希望在开发机器与生产机器上使用不同的缓存驱动(cache driver)。根据环境来改变配置就能很容易的达到此目的。 + +在`config`目录下创建一个和你的环境名同名的目录,例如`local`。然后,创建配置文件,这些配置文件中包含你想覆盖的配置选项。例如,要在本地环境中覆盖缓存驱动(cache driver),你可以在`app/config/local`目录中创建`cache.php`文件并包含如下内容: + + 'file', + + ); + +> **注意:** 不要使用'testing'作为环境名,它是专门为单元测试所保留的。 + +注意,你不需要为基础配置文件中的_所有_配置项指定值,只需指定你需要覆盖的配置选项即可。环境配置文件将会以"cascade"方式覆盖基本配置文件。 + +接下来,我们需要指导框架如何确定其运行环境。默认环境总是`produciton`。然而,你可以在安装目录的根目录下的`bootstrap/start.php` 文件中设置其他的环境。在该文件中,你可以找到 `$app->detectEnvironment`方法的调用。传入的数组参数就是用来确定当前运行环境的。你可以根据需要添加其他的环境或机器名。 + + detectEnvironment(array( + + 'local' => array('your-machine-name'), + + )); + + 在此案例中,'local' 是运行环境的名称,'your-machine-name' 是服务器的主机名。在Linux和Mac上,可以通过 `hostname` 命令来确定所用机器的主机名。 + +你还可以在调用`detectEnvironment`时传递一个`闭包(Closure)` , 这样你就可以自己检查环境: + + $env = $app->detectEnvironment(function() + { + return $_SERVER['MY_LARAVEL_ENV']; + }); + +可以通用调用`environment`方法来获取当前的应用环境: + +**或当前的应用环境** + + $environment = App::environment(); + + +## 维护模式 + +当应用处于维护模式时,所有的路由都会指向一个自定义的视图。这对于更新应用时做临时"禁用"很方便。`App::down`方法在`app/start/global.php`文件里进行了定义,它将在维护模式时将该方法输出的内容展示给用户。 + +要开启维护模式,只需执行Artisan 的 `down`命令: + + php artisan down + +要关闭维护模式,只需执行 `up` 命令: + + php artisan up + +当你的应用处于维护模式时,如需展示一个自定义的视图,只要在`app/start/global.php`文件中加入如下代码即可: + + App::down(function() + { + return Response::view('maintenance', array(), 503); + }); + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) \ No newline at end of file diff --git a/cn/contributing.md b/cn/contributing.md new file mode 100644 index 00000000000..5808dbeddd5 --- /dev/null +++ b/cn/contributing.md @@ -0,0 +1,35 @@ +# 为Laravel做贡献 + +- [简介](#introduction) +- [Pull Requests](#pull-requests) +- [编码指南](#coding-guidelines) + + +## 简介 + +Laravel是免费、开源的软件,这意味着任何人都可以为其开发和进步贡献力量。Laravel源代码目前托管在[Github](https://github.com/laravel)上,Github提供非常容易的途径fork项目和合并你的贡献。 + + +## Pull Requests + +pull request 的处理过程对于新特性和bug是不一样的。在你发起一个新特性的pull request之前,你应该先创建一个带有`[Proposal]`标题的issue。这个proposal应当描述这个新特性,以及实现方法。提议将会被审查,有可能会被采纳,也有可能会被拒绝。当一个提议被采纳,将会创建一个实现新特性的pull request。没有遵循上述指南的pull request将会被立即关闭。 + +为bug创建的Pull requests不需要创建建议issue。如果你有解决bug的办法,请详细描述你的解决方案。 + +对文档的增加和修改也可以提交到Github上的[文档源码库](https://github.com/laravel/docs)。 + +### 提交新特性 + +如果你希望Laravel中出现某个新特性,你可以在Github中创建一个带有`[Request]`标题的issue。该建议将会被核心代码贡献者审查。 + + +## 编码指南 + +Laravel 遵循 [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) 和 [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) 编码标准. 除了上述这些标准,下面还列出了一些其他一些应当遵循的编码标准: + +- 命名空间的声明应该和` +## 王赛 +- 邮箱:wangsai@bootcss.com +- 网址: [Bootstrap中文网](http://www.bootcss.com) & [Laravel中文网](http://www.golaravel.com) +- Github: [https://github.com/wangsai/](https://github.com/wangsai/) + + +## 苏小林 +- QQ:780860343 +- 邮箱:780860343@qq.com +- 博客:[http://www.cnblogs.com/MaybeYouLME/](http://www.cnblogs.com/MaybeYouLME/) +- Github:[https://github.com/iloveyuedu/](https://github.com/iloveyuedu/) + + +## 徐小花 +- 邮箱:kisexu@gmail.com +- 网址:[kisexu.com](http://kisexu.com) +- 开源项目: [花密](http://flowerpassword.com) + + +## 何文祥 +- 邮箱:erhuabushuo@gmail.com +- 网址:[hewenxiang.cn](http://hewenxiang.cn) +- Github: [https://github.com/erhuabushuo/](https://github.com/erhuabushuo/) + + +## 田爽 +- 邮箱:exiats@gmail.com +- Github: [https://github.com/silenceExia/](https://github.com/silenceExia/) + + +## 黄当武 +- 邮箱:jasonhuanghust@gmail.com +- Github: [https://github.com/JasonHuangHuster/](https://github.com/JasonHuangHuster/) \ No newline at end of file diff --git a/cn/controllers.md b/cn/controllers.md new file mode 100644 index 00000000000..ec8c93c8512 --- /dev/null +++ b/cn/controllers.md @@ -0,0 +1,181 @@ +# 控制器 + +- [基础控制器](#basic-controllers) +- [控制器过滤器](#controller-filters) +- [RESTful 控制器](#restful-controllers) +- [资源控制器](#resource-controllers) +- [处理空方法](#handling-missing-methods) + + +## 基础控制器 (Basic Controllers) + +与其把所有路由逻辑写在一个 `routes.php` 文件中,你也许更希望用控制器类来组织它们。控制器可以把相关的路由逻辑组织在一个类中,而且可以使用由框架提供的更为强大的功能,比如自动[依赖注入](/docs/ioc)。 + +控制器一般储存在 `app/controllers` 目录下,这个目录默认已经被注册在 `composer.json` 文件的 `classmap` 属性中。 + +这是一个基础控制器的例子: + + class UserController extends BaseController { + + /** + * Show the profile for the given user. + */ + public function showProfile($id) + { + $user = User::find($id); + + return View::make('user.profile', array('user' => $user)); + } + + } + +所有控制器需要继承 `BaseController` 类。 `BaseController` 类也储存在 `app/controllers` 下,通常用来放置公用的控制器逻辑。 `BaseController` 类继承自框架的 `Controller` 类。现在,你可以在路由中像这样调用控制器操作: + + Route::get('user/{id}', 'UserController@showProfile'); + +如果你需要使用PHP命名空间嵌套组织控制器,你可以在定义路由时使用类名的全称: + + Route::get('foo', 'Namespace\FooController@method'); + +你也可以在控制器路由中指定名称: + + Route::get('foo', array('uses' => 'FooController@method', + 'as' => 'name')); + +你可以使用 `URL::action` 方法获取一个控制器操作的链接: + + $url = URL::action('FooController@method'); + +你可以使用 `currentRouteAction` 方法获取当前控制器操作的名称: + + $action = Route::currentRouteAction(); + + +## 控制器过滤器 (Controller Filters) + +[过滤器](/docs/routing#route-filters)可以在控制器路由中指定,就像这样一个"标准的"路由: + + Route::get('profile', array('before' => 'auth', + 'uses' => 'UserController@showProfile')); + +不过,你也可以在控制器内部指定过滤器: + + class UserController extends BaseController { + + /** + * Instantiate a new UserController instance. + */ + public function __construct() + { + $this->beforeFilter('auth'); + + $this->beforeFilter('csrf', array('on' => 'post')); + + $this->afterFilter('log', array('only' => + array('fooAction', 'barAction'))); + } + + } + +你也可以使用闭包来指定内联的控制器过滤器: + + class UserController extends BaseController { + + /** + * Instantiate a new UserController instance. + */ + public function __construct() + { + $this->beforeFilter('auth', array('except' => 'getLogin')); + + $this->beforeFilter('csrf', array('on' => 'post')); + + $this->afterFilter('log', array('only' => + array('fooAction', 'barAction'))); + } + + } + + +## RESTful 控制器 (RESTful Controllers) + +Laravel框架中,你可以使用简单的REST命名规范,轻松定义单个路由去处理控制器的每个操作。首先,使用 `Route::controller` 方法定义路由: + +**定义一个 RESTful 控制器** + + Route::controller('users', 'UserController'); + +`controller` 方法接受两个参数。第一个是基础URI控制器句柄,第二个是控制器的类名。接下来,就可以在控制器中添加带有相应HTTP动词前缀的方法: + + class UserController extends BaseController { + + public function getIndex() + { + // + } + + public function postProfile() + { + // + } + + } + + `index` 方法会应答带有这个控制器句柄的根URI(例如这个例子里的是 `users` )。 + +如果你的控制器操作名称包含多个单词,你可以使用 "破折号" 语法来获得URI。例如,下面 `UserController` 控制器中的这个操作会用来应答 `users/admin-profile` URI: + + public function getAdminProfile() {} + + +## 资源控制器 (Resource Controllers) + +资源控制器让围绕资源构建RESTful模式控制器变得更简单。比如,你可能希望创建一个的控制器,用来管理通过你的应用储存的图片( "photos" )。通过Artisan命令行输入 `controller:make` 命令以及路由中的 `Route::resource` 方法快速创建一个控制器。 + +如果要通过命令行创建控制器,使用如下命令: + + php artisan controller:make PhotoController + +现在我们可以为这个控制器注册一个资源模式的路由: + + Route::resource('photo', 'PhotoController'); + +这一个路由声明创建了多个路由规则,用来处理各种图像(photo)资源的RESTful操作。同样地,刚刚创建的控制器中已经包含了许多对应的方法。每个方法都带有注释说明,告诉你分别是用来处理什么URI和HTTP动词的。 + +**资源控制器中不同操作的用途** + +Verb | Path | Action | Route Name +----------|-----------------------------|--------------|--------------------- +GET | /resource | index | resource.index +GET | /resource/create | create | resource.create +POST | /resource | store | resource.store +GET | /resource/{resource} | show | resource.show +GET | /resource/{resource}/edit | edit | resource.edit +PUT/PATCH | /resource/{resource} | update | resource.update +DELETE | /resource/{resource} | destroy | resource.destroy + +有时你也许只需要处理其中一部分资源操作: + + php artisan controller:make PhotoController --only=index,show + + php artisan controller:make PhotoController --except=index + +同样,你也许需要在路由中制定那一部分要处理的操作: + + Route::resource('photo', 'PhotoController', + array('only' => array('index', 'show'))); + + Route::resource('photo', 'PhotoController', + array('except' => array('create', 'store', 'update', 'delete'))); + + +## 处理空方法 (Handling Missing Methods) + +当控制器中没有任何方法匹配请求时,就会调用一个全局响应的方法。这个方法命名为 `missingMethod` ,它接收请求的参数数组作为方法的唯一参数。 + +**定义一个空方法** + + public function missingMethod($parameters) + { + // + } \ No newline at end of file diff --git a/cn/database.md b/cn/database.md new file mode 100644 index 00000000000..b8e7d10ef3d --- /dev/null +++ b/cn/database.md @@ -0,0 +1,89 @@ +# 数据库使用基础 + +- [配置](#configuration) +- [执行查询语句](#running-queries) +- [事务](#database-transactions) +- [同时使用多个数据库系统](#accessing-connections) +- [查询日志](#query-logging) + + +## 配置 + +在Laravel中连接和使用数据库非常简单。 数据库配置在 `app/config/database.php` 文件中. 所有受支持的数据库系统都列在了配置文件中,在配置文件中可以同时配置多个数据库系统的连接信息, 并指定默认使用哪个数据库连接。 + +Laravel 目前支持四种数据库系统,分别是: MySQL, Postgres, SQLite, 和 SQL Server。 + + +## 执行crud语句 + +完成数据库配置后, 就可以直接使用DB类执行sql语句了. + +**执行 select 语句** + + $results = DB::select('select * from users where id = ?', array(1)); + +`select` 方法总是返回一个包含查询结果的 `array`。 + +**执行 Insert 语句** + + DB::insert('insert into users (id, name) values (?, ?)', array(1, 'Dayle')); + +**执行 Update 语句** + + DB::update('update users set votes = 100 where name = ?', array('John')); + +**执行 Delete 语句** + + DB::delete('delete from users'); + +> **注意:** `update` 和 `delete` 语句返回操作所影响的数据的行数(int)。 + +**执行非crud语句** + + DB::statement('drop table users'); + +可以使用 `DB::listen` 方法监听数据库操作: + +**监听数据库操作事件** + + DB::listen(function($sql, $bindings, $time) + { + // + }); + + +## 事务 + +将需要在事务模式下执行的查询放入 `transaction` 方法内即可: + + DB::transaction(function() + { + DB::table('users')->update(array('votes' => 1)); + + DB::table('posts')->delete(); + }); + + +## 同时使用多个数据库系统 + +有时可能需要同时使用多个数据库系统(MySQL,Postgres,SQLite,SQL Server), 通过DB类的 `DB::connection` 方法来切换: + + $users = DB::connection('foo')->select(...); + +你可能需要在数据库系统的层面上操作数据库,使用PDO实例即可: + + $pdo = DB::connection()->getPdo(); + +使用reconnect方法重新连接一个指定的数据库系统: + DB::reconnect('foo'); + + +## 查询日志 + +Laravel默认会为当前请求执行的的所有查询生成日志并保存在内存中。 因此, 在某些特殊的情况下, 比如一次性向数据库中插入大量数据, 就可能导致内存不足。 在这种情况下,你可以通过 `disableQueryLog` 方法来关闭查询日志: + + DB::connection()->disableQueryLog(); + +调用 `getQueryLog` 方法可以同时获取多个查询执行后的日志: + + $queries = DB::getQueryLog(); diff --git a/cn/documentation.md b/cn/documentation.md new file mode 100644 index 00000000000..ad4e8b57b45 --- /dev/null +++ b/cn/documentation.md @@ -0,0 +1,43 @@ +- 序言 + - [简介](/docs/introduction) + - [快速入门](/docs/quick) + - [为Laravel做贡献](/docs/contributing) +- 开始学习 + - [安装](/docs/installation) + - [配置](/docs/configuration) + - [Request的生命周期](/docs/lifecycle) + - [路由](/docs/routing) + - [请求与输入](/docs/requests) + - [视图 & Response](/docs/responses) + - [控制器](/docs/controllers) + - [错误 & 日志](/docs/errors) +- 深度历险 + - [缓存](/docs/cache) + - [核心扩展](/docs/extending) + - [事件](/docs/events) + - [Facades](/docs/facades) + - [表单 & HTML](/docs/html) + - [有用的工具函数](/docs/helpers) + - [IoC 容器](/docs/ioc) + - [本地化](/docs/localization) + - [邮件](/docs/mail) + - [扩展包开发](/docs/packages) + - [分页](/docs/pagination) + - [队列](/docs/queues) + - [安全](/docs/security) + - [Session](/docs/session) + - [模版](/docs/templates) + - [单元测试](/docs/testing) + - [验证](/docs/validation) +- 数据库 + - [基本用法](/docs/database) + - [查询构造器](/docs/queries) + - [Eloquent ORM](/docs/eloquent) + - [结构生成器](/docs/schema) + - [迁移 & 数据填充](/docs/migrations) + - [Redis](/docs/redis) +- Artisan 命令行工具 + - [概览](/docs/artisan) + - [Artisan 开发](/docs/commands) +- 贡献者 + - [中文翻译贡献者名单](/docs/contributors) \ No newline at end of file diff --git a/cn/eloquent.md b/cn/eloquent.md new file mode 100644 index 00000000000..4d4bcf3f605 --- /dev/null +++ b/cn/eloquent.md @@ -0,0 +1,1025 @@ +# Eloquent ORM + +- [简介](#introduction) +- [基本用法](#basic-usage) +- [集体赋值](#mass-assignment) +- [插入、更新、删除](#insert-update-delete) +- [软删除](#soft-deleting) +- [时间戳](#timestamps) +- [查询范围](#query-scopes) +- [关系](#relationships) +- [查询关系](#querying-relations) +- [预先加载](#eager-loading) +- [插入相关模型](#inserting-related-models) +- [触发父模型时间戳](#touching-parent-timestamps) +- [与数据透视表工作](#working-with-pivot-tables) +- [集合](#collections) +- [访问器和调整器](#accessors-and-mutators) +- [日期调整器](#date-mutators) +- [模型事件](#model-events) +- [模型观察者](#model-observers) +- [转为数组或JSON](#converting-to-arrays-or-json) + + +## 简介 + +Laravel 自带的 Eloquent ORM 为您的数据库提供了一个优雅的、简单的 ActiveRecord 实现。每一个数据库的表有一个对应的 "Model" 用来与这张表交互。 + +在开始之前,确认已在 `app/config/database.php` 文件中配置好数据库连接。 + + +## 基本用法 + +首先,创建一个 Eloquent 模型。模型通常在 `app/models` 目录,但是您可以自由地把它们放在任何地方,只要它能根据您的 `composer.json` 文件自动加载。 + +**定义一个 Eloquent 模型** + + class User extends Eloquent {} + +注意我们并没有告诉 Eloquent 我们为 `User` 模型使用了哪一张表。类名的小写、复数的形式将作为表名,除非它被显式地指定。所以,在这种情况下,Eloquent 将假设 `User` 模型在 `users` 表中保存记录。您可以在模型中定义一个 `table` 属性来指定一个自定义的表名: + + class User extends Eloquent { + + protected $table = 'my_users'; + + } + +> **注意:** Eloquent 将假设每张表有一个名为 `id` 的主键。您可以定义 `primaryKey` 属性来覆盖这个约定。同样,您可以定义一个 `connection` 属性来覆盖在使用这个模型时所用的数据库连接。 + +一旦模型被定义,您可以开始在表中检索和创建记录。注意在默认情况下您将需要在表中定义 `updated_at` 和 `created_at` 字段。如果您不希望这些列被自动维护,在模型中设置 `$timestamps` 属性为 `false`。 + +**获取所有记录** + + $users = User::all(); + +**根据主键获取一条记录** + + $user = User::find(1); + + var_dump($user->name); + +> **注意:** 所有在 [查询构建器] 中适用的函数在 Eloquent 模型的查询中同样适用。 + +**根据主键获取一条记录或者抛出一个异常** + +有时您可能希望当记录没有被找到时抛出一个异常,允许您使用 `App::error` 处理器捕捉这些异常并显示404页面。 + + $model = User::findOrFail(1); + + $model = User::where('votes', '>', 100)->firstOrFail(); + +注册错误处理器,请监听 `ModelNotFoundException`: + + use Illuminate\Database\Eloquent\ModelNotFoundException; + + App::error(function(ModelNotFoundException $e) + { + return Response::make('Not Found', 404); + }); + +**使用 Eloquent 模型查询** + + $users = User::where('votes', '>', 100)->take(10)->get(); + + foreach ($users as $user) + { + var_dump($user->name); + } + +当然,您也可以使用查询构建器的统计函数。 + +**Eloquent 统计** + + $count = User::where('votes', '>', 100)->count(); + +如果您无法通过通过连贯的接口产生查询,可以使用 `whereRaw`: + + $users = User::whereRaw('age > ? and votes = 100', array(25))->get(); + +**指定查询的数据库连接** + +您可能需要在运行一个 Eloquent 查询的时候指定数据库连接,只需要使用 `on` 函数: + + $user = User::on('connection-name')->find(1); + + +## 集体赋值 + +当创建一个新的模型,您可以传递属性的数组到模型的构造函数。这些属性将通过集体赋值分配给模型。这是很方便的,但把用户的输入盲目地传给模型可能是一个**严重的**安全问题。如果把用户输入盲目地传递给模型,用户可以自由地修改**任何**或者**全部**模型的属性。基于这个原因,默认情况下所有 Eloquent 模型将防止集体赋值。 + +首先,在模型中设置 `fillable` 或 `guarded` 属性。 + +`fillable` 属性指定哪些属性可以被集体赋值。这可以在类或接口层设置。 + +**在模型中定义 Fillable 属性** + + class User extends Eloquent { + + protected $fillable = array('first_name', 'last_name', 'email'); + + } + +在这个例子中,只有三个被列出的属性可以被集体赋值。 + +`fillable` 的反义词是 `guarded`,将做为一个黑名单而不是白名单: + +**在模型中定义 Guarded 属性** + + class User extends Eloquent { + + protected $guarded = array('id', 'password'); + + } + +在这个例子中,`id` 和 `password` 属性将**不**被允许集体赋值。所有其他属性将被允许集体赋值。您可以使用 guard 方法阻止**所有**属性被集体赋值: + +**阻止所有属性集体赋值** + + protected $guarded = array('*'); + + +## 插入、更新、删除 + +为了从模型中向数据库中创建一个新的记录,简单地创建一个模型实例并调用 `save` 函数。 + +**保存一个新的模型** + + $user = new User; + + $user->name = 'John'; + + $user->save(); + +> **注意:** 通常您的 Eloquent 模型将有自动递增的键。然而,如果您希望指定您自定义的键,在模型中设置 `incrementing` 属性为 `false`。 + +您也可以使用 `create` 函数在一行代码中保存一个新的模型。被插入的模型实例将从函数中返回。但是,在您这样做之前,您需要在模型中指定 `fillable` 或者 `guarded` 属性,因为所有 Eloquent 模型默认阻止集体赋值。 + +在保存或创建一个使用自增ID的新模型之后,可以通过对象的 `id` 属性获取此自增ID: + + $insertedId = $user->id; + +**在模型中设置 Guarded 属性** + + class User extends Eloquent { + + protected $guarded = array('id', 'account_id'); + + } + +**使用模型的 Create 函数** + + $user = User::create(array('name' => 'John')); + +为了更新一个模型,您可以检索它,改变一个属性,然后使用 `save` 函数: + +**更新一个检索到的模型** + + $user = User::find(1); + + $user->email = 'john@foo.com'; + + $user->save(); + +有时您可能希望不仅保存模型,还有它的所有关系。为此,您可以使用 `push` 函数: + +**保存一个模型和关系** + + $user->push(); + +您也可以在一组模型上运行更新: + + $affectedRows = User::where('votes', '>', 100)->update(array('status' => 2)); + +删除一个模型,在实例中调用 `delete` 函数: + +**删除一个存在的模型** + + $user = User::find(1); + + $user->delete(); + +**根据主键删除一个模型** + + User::destroy(1); + + User::destroy(array(1, 2, 3)); + + User::destroy(1, 2, 3); + +当然,您可以在一组模型中运行删除查询: + + $affectedRows = User::where('votes', '>', 100)->delete(); + +如果您希望简单的在一个模型中更新时间戳,可以使用 `touch` 函数: + +**只更新模型的时间戳** + + $user->touch(); + + +## 软删除 + +当软删除一个模型,它并没有真的从数据库中删除。相反,一个 `deleted_at` 时间戳在记录中被设置。为一个模型开启软删除,在模型中指定 `softDelete` 属性: + + class User extends Eloquent { + + protected $softDelete = true; + + } + +为了在您的表中添加一个 `deleted_at` 字段,您可以在迁移中使用 `softDeletes` 函数: + + $table->softDeletes(); + +现在,当您在一个模型中调用 `delete` 函数,`deleted_at` 字段将被设置为当前的时间戳。在使用软删除的模型中查询,被软删除的模型将不被包含进查询结果中。为了强制已删除的模型出现在结果集中,在查询中使用 `withTrashed` 函数: + +**强制软删除的模型到结果集中** + + $users = User::withTrashed()->where('account_id', 1)->get(); + +如果您希望在结果集中**只**包含软删除的模型,您可以使用 `onlyTrashed` 函数: + + $users = User::onlyTrashed()->where('account_id', 1)->get(); + +恢复一个已被软删除的记录,使用 `restore` 函数: + + $user->restore(); + +您也可以在查询中使用 `restore` 函数: + + User::withTrashed()->where('account_id', 1)->restore(); + +`restore` 函数也可以在关系中被使用: + + $user->posts()->restore(); + +如果您希望从数据库中真正删除一个模型,您可以使用 `forceDelete` 函数: + + $user->forceDelete(); + +`forceDelete` 函数也可以在关系中被使用: + + $user->posts()->forceDelete(); + +检测一个给定的模型实例是否被软删除,可以使用 `trashed` 函数: + + if ($user->trashed()) + { + // + } + + +## 时间戳 + +默认情况下,Eloquent 在数据的表中自动地将维护 `created_at` 和 `updated_at` 字段。只需简单的添加这些 `timestamp` 字段到表中,Eloquent 将为您做剩余的工作。如果您不希望 Eloquent 维护这些字段,在模型中添加以下属性: + +**禁止自动时间戳** + + class User extends Eloquent { + + protected $table = 'users'; + + public $timestamps = false; + + } + +如果您希望定制时间戳的格式,可以在模型中重写 `getDateFormat` 函数: + +**提供一个定制的时间戳格式** + + class User extends Eloquent { + + protected function getDateFormat() + { + return 'U'; + } + + } + + +## 查询范围 + +范围允许您容易在模型中重用查询逻辑。定义一个范围,简单的用 `scope` 为模型添加前缀: + +**定义一个查询范围** + + class User extends Eloquent { + + public function scopePopular($query) + { + return $query->where('votes', '>', 100); + } + + public function scopeWomen($query) + { + return $query->whereGender('W'); + } + + } + +**使用一个查询范围** + + $users = User::popular()->women()->orderBy('created_at')->get(); + +**动态范围** + +有时您可能希望定义一个接受参数的范围。只需要添加您的参数到您的范围函数: + + class User extends Eloquent { + + public function scopeOfType($query, $type) + { + return $query->whereType($type); + } + + } + +然后在范围函数调用中传递参数: + + $users = User::ofType('member')->get(); + + +## 关系 + +当然,您的数据库可能是彼此相关的。比如,一篇博客文章可能有许多评论,或者一个订单与下订单的用户相关。Eloquent 使得管理和处理这些关系变得简单。Laravel 提供了四种类型的关系: +- [一对一](#one-to-one) +- [一对多](#one-to-many) +- [多对多](#many-to-many) +- [多态关系](#polymorphic-relations) + + +### 一对一 + +一个一对一关系是一种非常基础的关系。比如,一个 `User` 模型可以有一个 Phone`。我们可以在 Eloquent 中定义这个关系: + +**定义一个一对一关系** + + class User extends Eloquent { + + public function phone() + { + return $this->hasOne('Phone'); + } + + } + +传递给 `hasOne` 函数的第一个参数是相关模型的名字。一旦这个关系被定义,我们可以使用 Eloquent 的 [动态属性] 获取它: + + $phone = User::find(1)->phone; + +这条语句所产生的 SQL 语句如下: + + select * from users where id = 1 + + select * from phones where user_id = 1 + +注意 Eloquent 假设关系的外键基于模型的名字。在这个例子中假设 `Phone` 模型使用一个 `user_id` 外键。如果您希望覆盖这个惯例,您可以为传递 `hasOne` 函数传递第二个参数: + + return $this->hasOne('Phone', 'custom_key'); + +在 `Phone` 模型定义逆向关系,我们使用 `belongsTo` 函数: + +**定义一个逆向关系** + + class Phone extends Eloquent { + + public function user() + { + return $this->belongsTo('User'); + } + + } + +在上面的例子中,Eloquent 将在 `phones` 表中寻找 `user_id` 字段。如果您想定义一个不同的外键字段,您可以通过 `belongsTo` 函数的第二个参数传递它: + + class Phone extends Eloquent { + + public function user() + { + return $this->belongsTo('User', 'custom_key'); + } + + } + + +### 一对多 + +一个一对多关系的例子是一篇博客文章有许多评论。我们可以像这样定义关系模型: + + class Post extends Eloquent { + + public function comments() + { + return $this->hasMany('Comment'); + } + + } + +现在我们可以通过 [动态属性](#dynamic-properties) 访问文章的评论: + + $comments = Post::find(1)->comments; + +如果您需要添加进一步的约束检索哪些评论,我们可以调用 `comments` 函数连接链式条件: + + $comments = Post::find(1)->comments()->where('title', '=', 'foo')->first(); + +再次,如果您想覆盖默认的外键,可以给 `hasMany` 函数传递第二个参数: + + return $this->hasMany('Comment', 'custom_key'); + +在 `Comment` 模型中定义逆向关系,我们使用 `belongsTo` 函数: + +**定义逆向关系** + + class Comment extends Eloquent { + + public function post() + { + return $this->belongsTo('Post'); + } + + } + + +### 多对多 + +多对多关系更复杂的关系类型。这种关系的一个例子是一个用户和角色,这个角色也可被其他用户共享。比如,许多用户有 "Admin" 的角色。这个关系需要三个表:`users`, `roles` 以及 `role_user`。`role_user` 表来源于相关的角色名,并且有 `user_id` 和 `role_id` 字段。 + +我们可以使用 `belongsToMany` 函数定义一个多对多关系: + + class User extends Eloquent { + + public function roles() + { + return $this->belongsToMany('Role'); + } + + } + +现在,我们可以通过 `User` 模型获取角色: + + $roles = User::find(1)->roles; + +如果您想使用一个非常规的表名作为关系表,您可以传递它作为第二个参数给 `belongsToMany` 函数: + + return $this->belongsToMany('Role', 'user_roles'); + +您也可以覆盖相关的键: + + return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id'); + +当然,您也可以定义在 `Role` 模型中定义逆向关系: + + class Role extends Eloquent { + + public function users() + { + return $this->belongsToMany('User'); + } + + } + + +### 多态关系 + +多态关系允许在一个关联中一个模型属于多于一个的其他模型。比如,您可能有一个 photo 模型属于一个员工模型或者一个订单模型。我们可以定义像这样定义这个关系: + + class Photo extends Eloquent { + + public function imageable() + { + return $this->morphTo(); + } + + } + + class Staff extends Eloquent { + + public function photos() + { + return $this->morphMany('Photo', 'imageable'); + } + + } + + class Order extends Eloquent { + + public function photos() + { + return $this->morphMany('Photo', 'imageable'); + } + + } + +现在,我们可以为一个员工或者一个订单获取照片: + +**获取一个多态关系** + + $staff = Staff::find(1); + + foreach ($staff->photos as $photo) + { + // + } + +然而,"polymorphic" 的真正魔力是当您从 `Photo` 模型中访问员工或者订单的时候: + +**获取多态关系的属主** + + $photo = Photo::find(1); + + $imageable = $photo->imageable; + +`Photo` 模型的 `imageable` 关系将返回一个 `Staff` 或者 `Order` 实例,依赖于那种类型的模型拥有这个照片。 + +帮助理解这是怎样工作的,让我们探讨一个多态关系的数据库结构: + +**多态关系的数据库结构** + + staff + id - integer + name - string + + orders + id - integer + price - integer + + photos + id - integer + path - string + imageable_id - integer + imageable_type - string + +这里需要注意的键字段是表 `photos` 的 `imageable_id` 和 `imageable_type` 字段。ID 将包含所属员工或订单的 ID,类型将包含所属模型的类名。当访问 `imageable` 关系的时候将允许 ORM 检测所属模型的类型。 + + +## 查询关系 + +当为一个模型获取记录的时候,您可能希望基于一个已存在的关系限制结果集的数目。比如,您希望获取至少有一条评论的文章。为此,您可以使用 `has` 函数: + +**查询时检查关系** + + $posts = Post::has('comments')->get(); + +您也可以指定一个操作符和数量: + + $posts = Post::has('comments', '>=', 3)->get(); + + +### 动态属性 + +Eloquent 允许您通过动态属性访问关系。Eloquent 将为您自动加载关系,并且足够聪明到知道是否调用 `get` (为一对多关系)或者 `first` (为一对一关系)。然后,它将通过动态属性访问作为关系。比如,使用下面的 `Phone` 模型: + + class Phone extends Eloquent { + + public function user() + { + return $this->belongsTo('User'); + } + + } + + $phone = Phone::find(1); + +不需要像这样打印用户的电子邮件: + + echo $phone->user()->first()->email; + +它可以简化为: + + echo $phone->user->email; + +> **注意:** 返回多个结果的关系,其本质是返回了 `Illuminate\Database\Eloquent\Collection` 类的一个实例。 + + +## 预先加载 + +预先加载的存在是为了缓解 N + 1 查询问题。比如,考虑一个 `Author` 相关的 `Book` 模型。关系定义是这样的: + + class Book extends Eloquent { + + public function author() + { + return $this->belongsTo('Author'); + } + + } + +现在,考虑下面的代码: + + foreach (Book::all() as $book) + { + echo $book->author->name; + } + +此循环将执行一个查询获取表中的所有书籍,然后另一个查询为每本书获取作者。所以,如果我们有 25 本书,这个循环将允许 26 个查询。 + +值得庆幸的是,我们可以使用预先加载来减少查询的数量。这个关系应该被预先加载通过 `with` 函数指定: + + foreach (Book::with('author')->get() as $book) + { + echo $book->author->name; + } + +在上面的循环中,只有两个查询被执行: + + select * from books + + select * from authors where id in (1, 2, 3, 4, 5, ...) + +明智的使用预先加载可以大大提高应用程序的性能。 + +当然,您可以一次性预先加载多个关系: + + $books = Book::with('author', 'publisher')->get(); + +您甚至可以预先加载嵌套关系: + + $books = Book::with('author.contacts')->get(); + +在上面的例子中,`author` 关系将被预先加载,而且作者的 `contacts` 关系也将被加载。 + +### 预先加载约束 + +有时您可能希望预先加载一个关系,但也为预先加载指定一个条件。这里是一个例子: + + $users = User::with(array('posts' => function($query) + { + $query->where('title', 'like', '%first%'); + }))->get(); + +在这个例子中,我们预先加载用户的文章,但只限于文章的 title 字段中包含单词 "first" 的文章: + +### 延迟预先加载 + +从一个已存在的模型中直接预先加载相关的模型也是可能的。这在动态的决定是否加载相关模型或与缓存结合时可能有用。 + + $books = Book::all(); + + $books->load('author', 'publisher'); + + +## 插入相关模型 + +您会经常需要插入新的相关模型。比如,你可能希望为一篇文章插入一条新的评论。不需要在模型中手动设置 `post_id` 外键,您可以直接从它的父模型 `Post` 中插入新的评论: + +**附加一个相关的模型** + + $comment = new Comment(array('message' => 'A new comment.')); + + $post = Post::find(1); + + $comment = $post->comments()->save($comment); + +在这个例子中,所插入评论的 `post_id` 字段将自动设置。 + +### 相关模型 (属于) + +当更新一个 `belongsTo` 关系,您可以使用 `associate` 函数,这个函数将为子模型设置外键: + + $account = Account::find(10); + + $user->account()->associate($account); + + $user->save(); + +### 插入相关模型 (多对多) + +当处理多对多关系时,您也可以插入相关模型。让我们继续使用我们的 `User` 和 `Role` 模型作为例子。我们能够轻松地使用 `attach` 函数为一个用户附加新的角色: + +**附加多对多模型** + + $user = User::find(1); + + $user->roles()->attach(1); + +您也可以传递一个属性的数组,存在关系的表中: + + $user->roles()->attach(1, array('expires' => $expires)); + +当然,`attach` 的反义词是 `detach`: + + $user->roles()->detach(1); + +您也可以使用 `sync` 函数附加相关的模型。`sync` 函数接受一个 IDs 数组。当这个操作完成,只有数组中的 IDs 将会为这个模型存在关系表中: + +**使用 Sync 附加多对多关系** + + $user->roles()->sync(array(1, 2, 3)); + +您也可以用给定的 IDs 关联其他关系表中的值: + +**同步时添加关系数据** + + $user->roles()->sync(array(1 => array('expires' => true))); + +有时您可能希望创建一个新的相关的模型,并且在一行中附加它。对于此操作,您可以使用 `save` 函数: + + $role = new Role(array('name' => 'Editor')); + + User::find(1)->roles()->save($role); + +在这个例子中,新的 `Role` 模型将被保存并附加到用户模型。您也可以传递一个属性的数组加入到在这个操作中所连接的表: + + User::find(1)->roles()->save($role, array('expires' => $expires)); + + +## 触发父模型时间戳 + +当一个模型 `belongsTo` 另一个模型,比如一个 `Comment` 属于一个 `Post`,当更新子模型时更新父模型的时间戳通常是有用的。比如,当一个 `Comment` 模型更新,您想自动更新所属 `Post` 的 `updated_at` 时间戳。Eloquent 使之变得容易,只需要在子模型中添加 `touches` 属性包含关系的名字: + + class Comment extends Eloquent { + + protected $touches = array('post'); + + public function post() + { + return $this->belongsTo('Post'); + } + + } + +现在,当您更新一个 `Comment`,所属的 `Post` 的 `updated_at` 字段也将被更新: + + $comment = Comment::find(1); + + $comment->text = 'Edit to this comment!'; + + $comment->save(); + + +## 与数据透视表工作 + +正如您已经了解到,使用多对多关系需要一个中间表的存在。Eloquent 提供了一些非常有用与这个表交互的方式。比如,让我们假设我们的 `User` 对象有很多相关的 `Role` 对象,在访问这个关系时,我们可以在这些模型中访问数据透视表: + + $user = User::find(1); + + foreach ($user->roles as $role) + { + echo $role->pivot->created_at; + } + +注意每一个我们检索的 `Role` 模型将自动赋值给一个 `pivot` 属性。这个属性包含一个代表中间表的模型,并且可以像其他 Eloquent 模型使用。 + +默认情况下,在 `pivot` 对象中只有键,如果您的数据透视表包含其他的属性,您必须在定义关系时指定它们: + + return $this->belongsToMany('Role')->withPivot('foo', 'bar'); + +现在,`foo` 和 `bar` 属性将可以通过 `Role` 模型的 `pivot` 对象中访问。 + +如果您想您的数据透视表有自动维护的 `created_at` 和 `updated_at` 时间戳,在关系定义时使用 `withTimestamps` 方法: + + return $this->belongsToMany('Role')->withTimestamps(); + +为一个模型在数据透视表中删除所有记录,您可以使用 `detach` 函数: + +**在一个数据透视表中删除记录** + + User::find(1)->roles()->detach(); + +注意这个操作并不从 `roles` 删除记录,只从数据透视表中删除。 + + +## 集合 + +所有通过 `get` 方法或一个`relationship`由Eloquent返回的多结果集都是一个集合对象。这个对象实现了 `IteratorAggregate` PHP 接口,所以可以像数组一样进行遍历。然而,这个对象也有很多其他的函数来处理结果集。 + +比如,我们可以使用 `contains` 检测一个结果集是否包含指定的主键: + +**检测一个集合是否包含一个键** + + $roles = User::find(1)->roles; + + if ($roles->contains(2)) + { + // + } + +集合也可以被转换为一个数组或JSON: + + $roles = User::find(1)->roles->toArray(); + + $roles = User::find(1)->roles->toJson(); + +如果一个集合被转换为一个字符转,它将以JSON格式返回: + + $roles = (string) User::find(1)->roles; + +Eloquent 集合也包含一些方法来遍历和过滤所包含的项: + +**遍历集合** + + $roles = $user->roles->each(function($role) + { + // + }); + +**过滤集合** + +过滤集合时,你所提供的回调函数将被作为 [array_filter](http://php.net/manual/en/function.array-filter.php) 的回调函数使用。 + + $users = $user->filter(function($user) + { + if($user->isAdmin()) + { + return $user; + } + }); + +> **注意:** 当过滤集合并转化为JSON格式时,应首先调用 `values` 函数重置数组内所有的键(key)。 + +**对每个集合项应用回调函数** + + $roles = User::find(1)->roles; + + $roles->each(function($role) + { + // + }); + +**根据一个值排序集合** + + $roles = $roles->sortBy(function($role) + { + return $role->created_at; + }); + +有时,您可能希望返回一个自定义的集合以及自己添加的函数。您可以通过在 Eloquent 模型中重写 `newCollection` 函数指定这些: + +**返回一个自定义集合类型** + + class User extends Eloquent { + + public function newCollection(array $models = array()) + { + return new CustomCollection($models); + } + + } + + +## 访问器和调整器 + +Eloquent 为获取和设置模型的属性提供了一种便利的方式。简单的在模型中定义一个 `getFooAttribute` 函数声明一个访问器。记住,这个函数应该遵循 "camel-casing" 拼写格式,即使您的数据库使用 "snake-case": + +**定义一个访问器** + + class User extends Eloquent { + + public function getFirstNameAttribute($value) + { + return ucfirst($value); + } + + } + +在上面的例子中,`first_name` 字段有一个访问器。注意属性的值被传递到访问器。 + +调整器以类似的方式声明: + +**定义一个调整器** + + class User extends Eloquent { + + public function setFirstNameAttribute($value) + { + $this->attributes['first_name'] = strtolower($value); + } + + } + + +## 日期调整器 + +默认情况下,Eloquent 将转换 `created_at`、`updated_at` 以及 `deleted_at` 字段为 [Carbon](https://github.com/briannesbitt/Carbon) 的实例,它提供了各种有用的函数,并且继承自原生 PHP 的 `DateTime` 类。 + +您可以指定哪些字段自动调整,设置禁用调整,通过在模型中重写 `getDates` 函数: + + public function getDates() + { + return array('created_at'); + } + +当一个字段被认为是一个日期,您可以设置它的值为一个 UNIX 时间戳,日期字符串 (`Y-m-d`),日期时间字符串,当然也可以是一个 `DateTime` 或 `Carbon` 实例。 + +完全禁用日期调整,从 `getDates` 函数中返回一个空数组: + + public function getDates() + { + return array(); + } + + +## 模型事件 + +Eloquent 模型触发一些事件,允许您使用以下函数在模型的生命周期内的许多地方插入钩子:`creating`、`created`、`updating`、 `updated`、`saving`、`saved`、`deleting`、`deleted` 、`restoring`、`restored`。 + +每当一个新的项目第一次被保存,`creating` 和 `created` 事件将被触发。如果一个项目不是新的并且 `save` 函数被调用, `updating`/`updated` 事件将被触发。在这两种情况下,`saving`/`saved` 事件都将被触发。 + +如果从 `creating`, `updating` 或者 `saving` 返回 `false` ,该操作将被取消: + +**通过事件取消保存操作** + + User::creating(function($user) + { + if ( ! $user->isValid()) return false; + }); + +Eloquent 模型也包含一个静态的 `boot` 函数,它可以提供一个方便的地方注册事件绑定。 + +**设置一个模型 Boot 函数** + + class User extends Eloquent { + + public static function boot() + { + parent::boot(); + + // Setup event bindings... + } + + } + + +## 模型观察者 + +为加强处理模型事件,您可以注册一个模型观察者。一个观察者类可以包含很多函数对应于很多模型事件。比如,`creating`, `updating`, `saving` 函数可以在一个观察者中,除其他模型事件名之外。 + +因此,比如,一个模型观察者可以像这样: + + class UserObserver { + + public function saving($model) + { + // + } + + public function saved($model) + { + // + } + + } + +您可以使用 `observe` 函数注册一个观察者实例: + + User::observe(new UserObserver); + + +## 转为数组或JSON + +当构建JSON APIs,您可能经常需要转换您的模型和关系为数组或JSON字符串。所以,Eloquent 包含这些方法。转换一个模型和它加载的关系为一个数组,您可以使用 `toArray` 函数: + +**转换一个模型为数组** + + $user = User::with('roles')->first(); + + return $user->toArray(); + +注意模型的全部集合也可以被转为数组: + + return User::all()->toArray(); +转换一个模型为JSON字符串,您可以使用 `toJson` 函数: + +**转换一个模型为JSON字符串** + + return User::find(1)->toJson(); + +注意当一个模型或集合转换为一个字符串,它将转换为JSON,这意味着您可以直接从应用程序的路由中返回 Eloquent 对象! + +**从路由中返回一个模型** + + Route::get('users', function() + { + return User::all(); + }); + +有时您可能希望限制包含在模型数组或JSON中的属性,比如密码,为此,在模型中添加一个隐藏属性: + +**从数组或JSON转换中隐藏属性** + + class User extends Eloquent { + + protected $hidden = array('password'); + + } + +> **注意:** 如果关系被隐藏了,请使用关系的 **method** 名称,而不是动态的访问名称。 + +或者,您可以使用 `visible` 属性定义一个白名单: + + protected $visible = array('first_name', 'last_name'); + + +某些时候,你可能需要向数据库添加一组属性,而这组属性并在数据库中没有对应的字段。为了能够实现这一需求,可以简单的为每个属性定义一个访问器: + + public function getIsAdminAttribute() + { + return $this->attributes['admin'] == 'yes'; + } + +一旦创建了访问器,只需将属性添加到模型的`appends`属性中: + + protected $appends = array('is_admin'); + +一旦将属性添加到`appends`列表中,它就将被包含在模型和JSON表单中。 diff --git a/cn/errors.md b/cn/errors.md new file mode 100644 index 00000000000..a2772b1085c --- /dev/null +++ b/cn/errors.md @@ -0,0 +1,104 @@ +# 错误 & 日志 + +- [错误详情](#error-detail) +- [处理错误](#handling-errors) +- [HTTP异常](#http-exceptions) +- [处理404错误](#handling-404-errors) +- [日志](#logging) + + +## 错误详情 + +默认情况下,应用中会默认输出错误详情。这就是说当有错误发生时,你将看到一个详细的堆栈轨迹(stack trace)和错误信息页面。你可以在`app/config/app.php`文件中将`debug`配置选项设为`false`来关闭它。**强烈建议在生产环境中关闭该选项。** + + +## 处理错误 + +默认情况下,`app/start/global.php`文件中包含了一个用于处理所有异常的处理器: + + App::error(function(Exception $exception) + { + Log::error($exception); + }); + +这是一个最基本错误处理器。当然,你也可以自己定义更多的错误处理器。框架会根据你在定义错误处理器时指定的异常类型(type-hint)来分别调用。例如,你定义一个仅处理`RuntimeException`异常的处理器: + + App::error(function(RuntimeException $exception) + { + // Handle the exception... + }); + +如果异常处理器返回一个response,此response将会发送给浏览器,其他异常处理器将不会再被调用: + + App::error(function(InvalidUserException $exception) + { + Log::error($exception); + + return 'Sorry! Something is wrong with this account!'; + }); + +若要监听PHP中的致命错误(fatal error),可以使用`App::fatal`方法: + + App::fatal(function($exception) + { + // + }); + +如果同时有多个异常处理器,应该先定义最通用的,然后定义最具体的异常处理器。例如,处理所有 `Exception` 类型的异常处理器应当在处理 `Illuminate\Encryption\DecryptException` 类型的异常处理器之前。 + + +## HTTP 异常 + +HTTP异常一般发生在用户请求服务器的过程中。可能是页面未找到错误(404)、未授权错误(401)或者系统产生的500错误。为返回这些response,可以使用如下方法: + + App::abort(404, 'Page not found'); + +第一个参数是HTTP状态码,第二个参数是你自定义的针对此状态吗的错误信息。 + +如果要产生一个401未授权异常,只需如下所示: + + App::abort(401, 'You are not authorized.'); + +这些异常可以在request生命周期内的任何时间产生。 + + +## 处理404错误 + +你可以注册一个错误处理器来处理应用中的所有"404 Not Found"错误,这样就可以返回一个自定义的404错误页面: + + App::missing(function($exception) + { + return Response::view('errors.missing', array(), 404); + }); + + +## 日志 + +Laravel日志工具为强大的[Monolog](http://github.com/seldaek/monolog)提供了一层简单的抽象层。默认情况下,Laravel被配置为每天为应用创建单独的日志文件,这些文件存放在`app/storage/logs`目录下。你可以通过如下方法输出log: + + Log::info('This is some useful information.'); + + Log::warning('Something could be going wrong.'); + + Log::error('Something is really going wrong.'); + +日志工具提供了7个日志级别,参见[RFC 5424](http://tools.ietf.org/html/rfc5424):**debug**、**info**、**notice**、**warning**、**error**、**critical** 和 **alert**。 + +还可以像Log方法传递一个包含额外数据的数组: + + Log::info('Log message', array('context' => 'Other helpful information')); + +Monolog提供了多种处理日志的功能。如果需要,你可以通过如下方法获取Laravel内部所使用的Monolog实例: + + $monolog = Log::getMonolog(); + +你还可以注册一个事件,用以获取所有传输到日志的信息: + +**注册一个日志监听器** + + Log::listen(function($level, $message, $context) + { + // + }); + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) diff --git a/cn/events.md b/cn/events.md new file mode 100644 index 00000000000..00166365149 --- /dev/null +++ b/cn/events.md @@ -0,0 +1,157 @@ +# 事件 + +- [基本用法](#basic-usage) +- [利用通配符匹配多个事件](#wildcard-listeners) +- [使用类作为监听器](#using-classes-as-listeners) +- [事件队列](#queued-events) +- [事件订阅者](#event-subscribers) + + +## 基本用法 + +Laravel中的`Event`类实现了简单的观察者模式,允许你在应用程序中订阅和监听事件。 + +**订阅事件** + + Event::listen('user.login', function($user) + { + $user->last_login = new DateTime; + + $user->save(); + }); + +**触发事件** + + $event = Event::fire('user.login', array($user)); + +你也可以在订阅事件时指定优先级。优先级高的监听器将会被优先执行,相同优先级的将被按照订阅顺序执行。 + +**带有优先级的事件订阅** + + Event::listen('user.login', 'LoginHandler', 10); + + Event::listen('user.login', 'OtherHandler', 5); + +如果希望阻止事件传播到其他监听器,只需让监听器返回`false`即可实现: + +**阻止事件传播** + + Event::listen('user.login', function($event) + { + // Handle the event... + + return false; + }); + + +## 利用通配符匹配多个事件 + +注册事件监听器时,可以使用星号匹配多个事件进行监听: + +**注册多个事件的监听器** + + Event::listen('foo.*', function($param, $event) + { + // Handle the event... + }); + +该监听器将会处理所有以`foo.`开始的事件。注意:完整的事件名将以事件处理器的最后一个参数传入。 + + +## 使用类作为监听器 + +在某些情况下,你可能希望使用类来代替闭包处理事件。类事件监听器(Class event listener)通过 [Laravel IoC 容器](/docs/ioc)实现,为监听器提供了强大的依赖注入的能力。 + +**注册一个类监事件听器** + + Event::listen('user.login', 'LoginHandler'); + +默认情况下,`LoginHandler`类的`handle`方法将被调用: + +**定义一个事件监听器类** + + class LoginHandler { + + public function handle($data) + { + // + } + + } + +如果你不想使用默认的`handle`方法,你可以在订阅时指定方法: + +**指定实现订阅的方法** + + Event::listen('user.login', 'LoginHandler@onLogin'); + + +## 事件队列 + +使用`queue`和`flush`方法,可以将事件放入"队列"中,而不是立刻执行: + + +**向事件队列中添加一个事件** + + Event::queue('foo', array($user)); + +**注册一个事件清除器** + + Event::flusher('foo', function($user) + { + // + }); + +最后,你可以使用`flush`方法清除掉队列中的所有事件: + + Event::flush('foo'); + + +## 事件订阅者 + +事件订阅者类可以在其自身内部订阅多个事件。并且事件订阅者应该定义`subscribe`方法,并接收一个事件调度器实例作为参数: + +**定义一个事件订阅者** + + class UserEventHandler { + + /** + * Handle user login events. + */ + public function onUserLogin($event) + { + // + } + + /** + * Handle user logout events. + */ + public function onUserLogout($event) + { + // + } + + /** + * Register the listeners for the subscriber. + * + * @param Illuminate\Events\Dispatcher $events + * @return array + */ + public function subscribe($events) + { + $events->listen('user.login', 'UserEventHandler@onUserLogin'); + + $events->listen('user.logout', 'UserEventHandler@onUserLogout'); + } + + } + +一旦定义了事件订阅者,就可以使用`Event`类进行注册。 + +**注册一个事件订阅者** + + $subscriber = new UserEventHandler; + + Event::subscribe($subscriber); + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) \ No newline at end of file diff --git a/cn/extending.md b/cn/extending.md new file mode 100644 index 00000000000..5e497932fef --- /dev/null +++ b/cn/extending.md @@ -0,0 +1,234 @@ +# 对框架进行扩展 + +- [介绍](#introduction) +- [管理者 & 工厂](#managers-and-factories) +- [缓存](#cache) +- [Session会话](#session) +- [用户验证](#authentication) +- [基于IoC的扩展](#ioc-based-extension) +- [请求(Request)相关扩展](#request-extension) + + +## 介绍 +Laravel 为你提供了很多可扩展的地方, 通过它们你可以定制框架中一些核心组件的行为,甚至对这些核心组件进行全部替换。 例如, 哈希组件是在 "HaserInterface" 接口中被定义的,你可以根据应用程序的需求来选择是否实现它。 你也可以扩展 "Request" 对象来添加专属你的"帮助"方法。你甚至可以添加全新的 用户验证,缓存以及会话(Session) 驱动! + +Laravel 的组件通常以两种方式进行扩展:在IoC容器中绑定新的实现,或者为一个扩展注册一个 "Manager" 类,第二种方式运用了设计模式中"工厂"的理念。在这一章中,我们会探索扩展框架的各种方法以及查看一些具体的实现代码。 + +> **注意:** +请记住,Laravel 组件的扩展通常是以一下两种方法的其中一种实现的:IoC绑定和 "Manager" 类。 Manager类充当实现工厂模式的作用,它负责缓存、会话(Session)等基本驱动的实例化工作。 + + +## 管理者 & 工厂 + +Laravel 有几个 "Manager" 类 ,用来管理一些基本驱动组件的创建工作。 包括 缓存、会话(Session)、用户验证以及队列组件。"Manager"类负责根据应用程序中相关配置来创建一个驱动实现。例如,"CacheManager"类可以创建 APC、Memcached、Native 以及其他各种缓存驱动的实现。 +每一个Manager类都包含有一个"extend"方法,通过它可以轻松的将新驱动中的解决方案和功能注入到Manager中。下面,我们会通过列举一些向Manager中注入定制驱动的例子来依次对它们进行说明。 + + +> **注意:** +花一些时间来探索Laravel中各种不同的"Manager"类,例如,"CacheManager"以及"SessionManager"。通过阅读这些类,可以让你对Laravel的底层实现有一个更加彻底的了解。 所有的"Manager"类都继承了"Illuminate\Support\Manager"基类, 这个基类为每一个"Manager"类提供了一些有用的,常见的功能。 + + + +## 缓存 + +要扩展Laravel的缓存机制,我们需要使用"CacheManager"的"extend"方法来为"manager"绑定一个定制的驱动解析器,这个方法在所有的"manager"中都是通用的。例如,注册一个新的名叫"mongo"的缓存驱动,我们需要做一下操作: + + + Cache::extend('mongo', function($app) + { + // Return Illuminate\Cache\Repository instance... + }); + +传入"extend"方法中的第一个参数是这个驱动的名字。这个会与你在"app/config/cache.php"配置文件中的"driver"选项相对应。第二个参数是一个返回"Illuminate\Cache\Repository"实例的闭包。 这闭包需要传入一个"$app"实例, 它是"Illuminate\Foundation\Application" 的一个实例,并且是一个IoC容器。 + +为了创建我们定制的缓存驱动,我们首先应该实现"Illuminate\Cache\StoreInterface"接口。 因此,我们的 MongDB 缓存的实现应该是这样的: + + class MongoStore implements Illuminate\Cache\StoreInterface { + + public function get($key) {} + public function put($key, $value, $minutes) {} + public function increment($key, $value = 1) {} + public function decrement($key, $value = 1) {} + public function forever($key, $value) {} + public function forget($key) {} + public function flush() {} + + } + +我们要用一个MongoDB连接来实现所有这些方法。一旦完成了这些实现,我们就完成了定制驱动的注册。 + + use Illuminate\Cache\Repository; + + Cache::extend('mongo', function($app) + { + return new Repository(new MongoStore); + }); + +正如你在上面的例子中所看到的,你会在创建定制缓存驱动时使用到"Illuminate\Cache\Repository"基类。通常情况下,不需要自己创建"Repository"类。 + +如果你不知道将定制的缓存驱动代码放在哪里,那么可以考虑将它们放在Packagist中,或者你可以在应用程序的主目录下创建一个"Extension"子目录。例如,如果你的应用程序叫"Snappy",你可以将你的缓存扩展放在"app/Snappy/Extensions/MongoStore.php"中。 请记住,Laravel对于应用程序的结构没有严格的限制,你可以自由地根据自己的选择来组织你的应用程序结构。 + + +> **注意:** +当你不知道将代码放在哪里时,请回想一下"service provider" 。 我们已经讨论过,利用"service provider"来组织你的框架扩展是组织代码的最好方式。 + + + +## 会话 Session + +为Laravel扩展一个定制的Session驱动跟扩展一个缓存系统一样简单。同样,我们需要用 "extend" 方法来注册定制的驱动: + + Session::extend('mongo', function($app) + { + // Return implementation of SessionHandlerInterface + }); + +请注意,定制的缓存驱动需要实现"SessionHandlerInterface"接口。这个接口包含在在PHP5.4+core中。如果你正在使用PHP5.3,Laravel会帮你定义这个接口,这使得你的系统可以向前兼容。我们只需要实现这个接口中的一些简单的方法。 下面是一个简化的MongoDB实现: + + class MongoHandler implements SessionHandlerInterface { + + public function open($savePath, $sessionName) {} + public function close() {} + public function read($sessionId) {} + public function write($sessionId, $data) {} + public function destroy($sessionId) {} + public function gc($lifetime) {} + + } + +上面这些方法不像在"StoreInterface"缓存接口中的方法一样让人容易理解。下面让我们快速的了解一下每一个方法的功能: + +- "open"方法通常在基于文件的Session存储系统中使用。因为Laravel自带了PHP原生文件存储session的驱动。因此,你不需要在这个方法中添加任何实现。事实上 PHP强制要求我们实现这个不需要添加任何代码的方法,这实在是一个糟糕的接口设计(在后面的内容中会讨论这一点)。 +-"close"方法也像"open"方法一样,通常是可以被忽略的。大多数驱动不需要这个方法。 +-"read"方法会返回一个与传入的"$sessionId"相关联的字符串形式的Session数据。将驱动中的Session数据取出时,我们不需要做任何的序列化和转码的工作。因为Laravel会帮你处理这些。 +-"write"方法是将与"$sessionId"相关联的"$data"字符串写入到一些持久化存储系统中。例如: MongoDB,Dynamo 等等。 +-"destroy"方法将与"$sessionId"相关的数据从持久化系统中移除。 +-"gc"方法会删除超过传入"$lifetime"(一个UNIX 时间戳)的所有Session数据。对于像Memcached和Redis这一类(self-expired)自动管理超期的系统,"gc"方法内不应该有任何代码。 + +实现"SessionHandlerInterface"后,我们可以将它注册到Session管理中。 + + Session::extend('mongo', function($app) + { + return new MongoHandler; + }); + +session驱动成功注册后,我们就可以使用"app/config/session.php"配置文件中的"mongo"驱动了。 + + +> **注意:** 如果你是在编写一个定制的session处理程序,请在Packagist中分享! + + +## 用户验证 + +用户验证的扩展方法和之前讨论过的缓存和session组件的扩展方法很相似。 +同样,我们会使用这个我们已经渐渐熟悉的"extend"方法。 + + Auth::extend('riak', function($app) + { + // Return implementation of Illuminate\Auth\UserProviderInterface + }); + +"UserProviderInterface"的实现仅仅是负责从如 MySQL、Riak等 持久化存储系统中将"UserInterface"实现获取出来。这两个接口使得Laravel的用户验证机制在不同的用户数据存储方法以及使用不同的类来展示的情况下都可以保持正常的功能。 + +让我们看一下"UserProviderInterface"接口: + + interface UserProviderInterface { + + public function retrieveById($identifier); + public function retrieveByCredentials(array $credentials); + public function validateCredentials(UserInterface $user, array $credentials); + + } + +"retrieveById"方法通常接收一个代表用户的数字形式的键,例如在MySQL数据库中自增的ID字段值。"UserInterface"实现会通过这个方法来比对接收和返回的ID。 + +"retrieveByCredentials"方法接收在试图登录应用程序时传入"Auth:attempt"方法的一个认证信息数组。然后,"retrieveByCredentials"方法会从底层的持久化存储系统中查询与传入身份信息相符合的用户。通常情况下,"retrieveByCredentials"方法会根据"$credentails['username']"来设定"where"条件来进行查询。 + **"retrieveByCredentials"方法不应该试图做任何密码的确认和验证操作。** + +"validateCredentials" 方法会将传入的"$user"用户和"$credentials"身份信息进行对比并验证这个用户。例如,这个方法会将"$user->getAuthPassword()"返回的字符串和经过"Hash::make"方法哈希处理的"$credentials['password']"进行对比。 + +现在,我们已经讨论过"UserProviderInterface"中的所有方法,让我们来看一看"UserInterface"接口。请记住,"provider"必须在"retrieveById"和"retrieveByCredentials"方法中返回对于这个接口的实现: + + interface UserInterface { + + public function getAuthIdentifier(); + public function getAuthPassword(); + + } + +这个接口非常简单。"getAuthIdentifier"返回这个用户的"主键",在MySQL后台中,这个主键就是自增的primary key。"getAuthPassword"方法返回这个用户进过hash加密后的密码。这个接口可以让用户验证系统在任何User类的基础上正常工作,不管你是在使用哪种ORM或存储抽象层。默认情况下,Laravel在"app/models"目录下会有一个实现了这个接口的"User"类,因此,你可以参照这个作一些实现。 + +最后,完成了"UserProviderInterface"实现后,我们可以将"Auth" 扩展注册到用户认证管理中: + + Auth::extend('riak', function($app) + { + return new RiakUserProvider($app['riak.connection']); + }); + +当你通过extend方法注册了这个驱动后,你可以将当前的用户验证驱动切换为"app/config/auth.php"配置文件中的新驱动。 + + + +## 基于IoC的扩展 + +几乎所有的service提供者包括Laravel框架都会将对象绑定在IoC容器中,你可以在"app/config/app.php"配置文件中找到关于你应用程序中的所有service提供者。如果有时间,可以浏览这些service提供者的源代码。通过浏览这些细节,你可以更深刻的了解这些service提供者在框架中添加了什么内容,以及IoC容器利用了哪些键来绑定这些service提供者。 + +例如,"PaginationServiceProvider"在IoC容器中绑定了一个"paginator"键,这个键对应一个"Illuminate\Pagination\Environment"实例。通过重写这个IoC绑定,你可以轻松的在你的应用程序中扩展以及重写这个类。例如,你可以创建一个扩展"Environment"基类的类: + + namespace Snappy\Extensions\Pagination; + + class Environment extends \Illuminate\Pagination\Environment { + + // + + } + +创建了扩展类之后,你可以创建一个在"boot"方法中重写了"paginator"的全新的"SnappyPaginationProvider" service提供者类: + + class SnappyPaginationProvider extends PaginationServiceProvider { + + public function boot() + { + App::bind('paginator', function() + { + return new Snappy\Extensions\Pagination\Environment; + }); + + parent::boot(); + } + + } + +请注意,这个类继承的是 "PaginationServiceProvider" 类,而不是默认的"ServiceProvider"基类。完成service提供者的扩展之后,将在配置文件"app/config/app.php"中的"PaginationServiceProvider"替换成经过扩展的service提供者。 + +这是为扩展绑定在容器中的核心类而通常使用的方法。基本上每一个核心类都是通过这样的方式绑定在容器中,并且可以对它们进行重写。再一次提醒,详细阅读框架中所包含的service提供者会使你更加清楚这些类绑定在容器中的什么地方,以及它们与什么Key(键值)所绑定着。这是学习Laravel究竟是如何组织的最好的方法。 + + + +## Request(请求)扩展 + +由于"Request"是框架中非常基础性的组件,而且它在Request请求周期中很早就被实例化了,因此扩展"Request"类与前面所讲的例子有一些不同。 + +首先,用上述的方法来扩展这个类: + + + +## 简介 + +Facades 提供了一个“静态”接口到 [IoC 容器](/docs/ioc) 类 。Laravel 含有很多 facades,你可能不知道你在某些地方已经使用过它们了。 + +有时候, 你可能想为你的应用程序或包创建自己的 facades, 所以,让我们来讨论一下如何开发和使用这些类。 + + +> **注意** 在深入 facades 之前,我们强烈建议你多了解一下 Laravel [Ioc 容器](/docs/ioc)。 + + + +## 说明 + +在 Laravel 应用程序中, facade 是提供从容器中访问对象方法的类。`Facade` 类实现了该机制。 + +你的 facade 类只需要实现一个方法: `getFacadeAccesor` 。 `getFacadeAccessor` 方法的工作是定义如何从容器中取得对象。 `Facades` 基类构建了 `__callStatic()` 魔术方法来从 facade 延迟访问取得对象。 + + + +## 实际用例 + +在以下示例中,执行 Laravel 缓存系统, 查看该代码,我们可能会假定 `get` 静态方法是执行在 `Cache` 类。 + +In the example below, a call is made to the Laravel cache system. By glancing at this code, one might assume that the static method `get` is being called on the `Cache` class. + + $value = Cache::get('key'); + +然后,如果我们查看 `Illuminate\Support\Facades\Cache` 类, 你会发现该类没有任何 `get` 静态方法: + + class Cache extends Facade { + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() { return 'cache'; } + + } + +Cache 类继承基本 `Facade` 类,并且定义了个 `getFacadeAccessor()` 方法。注意,该方法的工作是返回 IoC 绑定的名字。 + +当用户引用任何在 `Cache` facade 静态方法, Laravel 从 IoC 容器绑定中取得 `cache`,并且执行请求该对象方法(在该例子中为`get`)。 + +所以,我们 `Cache::get` 执行可以重写为: + + $value = $app->make('cache')->get('key'); + + +## 创建 Facades + +要为自己的应用程序或者包创建一个 facade 是非常简单的。你需要做三件事情: + +- 一个 IoC 绑定。 +- 一个 facade 类。 +- 一个 facade 别名配置。 + +让我们来看个例子。这里,我们定义一个 `PaymentGateway\Payment` 类。 + + namespace PaymentGateway; + + class Payment { + + public function process() + { + // + } + + } + +我们需要能够在 IoC 容器中取得该类。所以,让我们增加一个绑定: + + App::bind('payment', function() + { + return new \PaymentGateway\Payment; + }); + +最好注册该绑定的位置是创建一个新的名为 `PaymentServiceProvider` [服务提供器](/docs/ioc#service-providers),并且将该绑定加入到 `register` 方法。接下来你就可以配置 Laravel `app/config/app.php` 配置文件来加载该服务提供器。 + +接下来,我们就可以创建我们的 facade 类: + + use Illuminate\Support\Facades\Facade; + + class Payment extends Facade { + + protected static function getFacadeAccessor() { return 'payment'; } + + } + +最后,如果你想,我们可以为我们 facade 设置别名到 `app/config/app.php` 配置文件里的 `aliases` 数组。现在,我们能够调用 `Payment` 类实例的 `process` 方法。 + Payment::process(); + + +## 模拟 Facades + +单元测试是 facades 工作的重要体现。事实上,可测试性是 facedes 存在的主要原因。要了解更多信息,查看文档[模拟 facades](/docs/testing#mocking-facades)部分。 diff --git a/cn/helpers.md b/cn/helpers.md new file mode 100644 index 00000000000..291d0bcc15d --- /dev/null +++ b/cn/helpers.md @@ -0,0 +1,366 @@ +# 助手函数 + +- [数组助手](#arrays) +- [应用路径](#paths) +- [字符串助手](#strings) +- [URLs路径](#urls) +- [杂项](#miscellaneous) + + +## 数组助手 + +### array_add + +`array_add` 函数将一个指定键的元素添加进数组,如果数组中已有该键,则不添加。 + + $array = array('foo' => 'bar'); + + $array = array_add($array, 'key', 'value'); + +### array_divide + +`array_divide` 返回两个数组,第一个包含数组里的所有键,第二个包含数组里的所有值。 + + $array = array('foo' => 'bar'); + + list($keys, $values) = array_divide($array); + +### array_dot + +`array_dot` 函数将多维数组转为一维数组,该数组不需要规则的结构。所有的键用'.'分割。 + + $array = array('foo' => array('bar' => 'baz')); + + $array = array_dot($array); + + // array('foo.bar' => 'baz'); + +### array_except + +`array_except` 函数移除指定键的元素(数组的第一维),第二个参数包含所有要移除键的数组,并返回新数组。 + + $array = array_except($array, array('keys', 'to', 'remove')); + +### array_fetch + +`array_fetch` 获取多维数组的最终值,参数为 第一维的键.第二维的键.第三维的键.... 的形式,指定的维数 数组的形式需一致,否则Laravel将抛出'Undefined index'。 + + $array = array( + array('developer' => array('name' => 'Taylor')), + array('developer' => array('name' => 'Dayle')), + ); + + $array = array_fetch($array, 'developer.name'); + +### array_first + +`array_first` 方法返回第一个 满足匿名函数(该匿名函数作为参数传入) 返回true的元素的值。 + + $array = array(100, 200, 300); + + $value = array_first($array, function($key, $value) + { + return $value >= 150; + }); + +array_first的第三个参数为该操作指定默认返回值,若匿名函数永远不可能返回true将返回默认值。 + + $value = array_first($array, $callback, $default); + +### array_flatten + +`array_flatten` 获取多维数组的最终值,该数组不需要规则的结构。并丢掉键。 + + $array = array('name' => 'Joe', 'languages' => array('PHP', 'Ruby')); + + $array = array_flatten($array); + + // array('Joe', 'PHP', 'Ruby'); + +### array_forget + +`array_forget` 方法移除数组内指定的元素,通过 键.键.键 的形式来寻找指定要移除的元素 + + $array = array('names' => array('joe' => array('programmer'))); + + $array = array_forget($array, 'names.joe'); + +### array_get + +`array_get` 方法获取数组内指定的元素,通过 键.键.键 的形式来寻找指定的元素 + + $array = array('names' => array('joe' => array('programmer'))); + + $value = array_get($array, 'names.joe'); + +### array_only + +`array_only` 方法返回数组内指定键(仅限第一维的键)的的元素,参数为将获取的数组元素键的数组。 + + $array = array('name' => 'Joe', 'age' => 27, 'votes' => 1); + + $array = array_only($array, array('name', 'votes')); + +### array_pluck + +`array_pluck` 返回数组内指定键的值,并丢掉键,只能指定一个键。 + + $array = array(array('name' => 'Taylor'), array('name' => 'Dayle')); + + $array = array_pluck($array, 'name'); + + // array('Taylor', 'Dayle'); + +### array_pull + +`array_pull` 从原数组中删除指定键的元素,并返回被删除的元素的值。 + + $array = array('name' => 'Taylor', 'age' => 27); + + $name = array_pull($array, 'name'); + +### array_set + +`array_set` 为一个指定键的元素设置值,通过 键.键.键 的形式来寻找将被设置或重新赋值的元素。 + + $array = array('names' => array('programmer' => 'Joe')); + + array_set($array, 'names.editor', 'Taylor'); + +### array_sort + +`array_sort` 函数对数组里的第一维元素进行自定义排序,并保持第一维数组键。 + + $array = array( + array('name' => 'Jill'), + array('name' => 'Barry'), + ); + + $array = array_values(array_sort($array, function($value) + { + return $value['name']; + })); + +### head + +返回数组的第一个元素,内部实际调用reset方法。经常在链式调用时使用。 + + $first = head($this->returnsArray('foo')); + +### last + +返回数组的最后一个元素,内部实际调用end方法。经常在链式调用时使用。 + + $last = last($this->returnsArray('foo')); + + +## 应用路径 + +### app_path + +获取 `app` (/app[laravel4]) 目录的绝对路径。 + +### base_path + +获取laravel应用所在的绝对路径。 + +### public_path + +获取 `public` 目录的绝对路径。 + +### storage_path + +获取 `app/storage` 目录的绝对路径. + + +## 字符串助手 + +### camel_case + +将字符串转为 `驼峰命名法` 的格式. + + $camel = camel_case('foo_bar'); + + // fooBar + +### class_basename + +获取不带命名空间的类名。 + + $class = class_basename('Foo\Bar\Baz'); + + // Baz + +### e + +`htmlentites` 的简写,以utf8格式转换字符串。 + + $entities = e('foo'); + +### ends_with + +判断字符串是否以另一个指定的字符串结束。 + + $value = ends_with('This is my name', 'name'); + +### snake_case + +将字符串转换为下划线命名方式。 + + $snake = snake_case('fooBar'); + + // foo_bar + +### starts_with + +判断字符串是否以另一个指定的字符串开始。 + + $value = starts_with('This is my name', 'This'); + +### str_contains + +判断字符串是否包含另一个指定的字符串。 + + $value = str_contains('This is my name', 'my'); + +### str_finish + +以指定字符结束字符串,且保证字符串结尾有且只有一个指定的字符。 + + $string = str_finish('this/string', '/'); + + // this/string/ + +### str_is + +判断字符串是否符合指定的格式。 下面的星号是作为通配符使用。 + + $value = str_is('foo*', 'foobar'); + +### str_plural + +将字符串转为复数形式(只适合英语)。 + + $plural = str_plural('car'); + +### str_random + +生成一个指定长度的字符串。 + + $string = str_random(40); + +### str_singular + +将字符串转为单数形式(只适合英语)。 + + $singular = str_singular('cars'); + +### studly_case + +将字符串转为 `StudlyCase` 格式。 + + $value = studly_case('foo_bar'); + + // FooBar + +### trans + +Translate a given language line. Alias of `Lang::get`. + + $value = trans('validation.required'): + +### trans_choice + +Tranlate a given language line with inflection. Alias of `Lang::choice`. + + $value = trans_choice('foo.bar', $count); + + +## URLs + +### action + +从指定的控制器的方法生成url。 + + $url = action('HomeController@getIndex', $params); + +### 路由 + +为命名路由生成URL。 + + $url = route('routeName', $params); + +### asset + +生成一个指向样式文件的url。 + + $url = asset('img/photo.jpg'); + +### link_to + +生成一个a标签的html代码,并能为该a标签指定详细信息。 + + echo link_to('foo/bar', $title, $attributes = array(), $secure = null); + +### link_to_asset + +生成一个指向样式文件的a标签。 + + echo link_to_asset('foo/bar.zip', $title, $attributes = array(), $secure = null); + +### link_to_route + +生成一个指向特定路由的a标签。 + + echo link_to_route('route.name', $title, $parameters = array(), $attributes = array()); + +### link_to_action + +生成一个指向特定控制器方法的a标签。 + + echo link_to_action('HomeController@getIndex', $title, $parameters = array(), $attributes = array()); + +### secure_asset + +生成一个指向样式文件的a标签并使用使用 HTTPS 安全链接。 + + echo secure_asset('foo/bar.zip', $title, $attributes = array()); + +### secure_url + +生成一个完全自定义的a标签并使用 HTTPS 安全链接。 + + echo secure_url('http://github.com/foo/bar',%20$parameters%20=%20array()); + +### url + +生成一个完全自定义的a标签。 + + echo url('http://github.com/foo/bar',%20$parameters%20=%20array(), $secure = null); + + +## 杂项 + +### csrf_token + +获取当前 CSRF 标记。 + + $token = csrf_token(); + +### dd + +格式化输出指定的变量,并结束脚本执行。 + + dd($value); + +### value + +如果参数为 `Closure`,value函数将返回 `Closure` 的返回值。 否则,返回参数本。 + + $value = value(function() { return 'bar'; }); + +### with + +参数为对象,直接返回该对象,在PHP 5.3.x中进行链式调用很有用。 + + $value = with(new Foo)->doWork(); diff --git a/cn/html.md b/cn/html.md new file mode 100644 index 00000000000..5cbd10acfe0 --- /dev/null +++ b/cn/html.md @@ -0,0 +1,197 @@ +# 表单 & HTML + +- [创建表单](#opening-a-form) +- [CSRF防御机制](#csrf-protection) +- [表单与模型绑定](#form-model-binding) +- [Label 标签](#labels) +- [单行文本、多行文本、密码和隐藏表单域](#text) +- [多选框和单选框](#checkboxes-and-radio-buttons) +- [文件上传](#file-input) +- [下拉菜单](#drop-down-lists) +- [按钮](#buttons) +- [自定义宏](#custom-macros) +- [生成URL](#generating-urls) + + +## 创建表单 + +**创建表单** + + {{ Form::open(array('url' => 'foo/bar')) }} + // + {{ Form::close() }} + +默认使用 `POST` 方法;当然,你也可以指定使用其他方法提交表单: + + echo Form::open(array('url' => 'foo/bar', 'method' => 'put')) + +> **注意:** 因为 HTML 表单只支持 `POST` 方法,在使用 `PUT` 和 `DELETE` 方法时框架会自动通过一个 `_method` 隐藏域来发送伪装的方法信息。 + +你也可以创建一个表单指向已定义的路由或者控制器操作: + + echo Form::open(array('route' => 'route.name')) + + echo Form::open(array('action' => 'Controller@method')) + +如果你需要在表单中上传文件,请添加一个 `files` 参数到数组中: + + echo Form::open(array('url' => 'foo/bar', 'files' => true)) + + +## CSRF防御机制 + +Laravel框架提供了一种简单的方法帮助你的应用抵御CSRF(跨站请求伪造)式攻击。Laravel框架会自动在用户 session 中存放一个随机令牌(token),并且将这个令牌作为一个隐藏字段包含在表单信息中。当然,你也可以使用表单的 `token` 方法直接调用令牌字段的HTML代码: + +**添加CSRF令牌到表单中** + + echo Form::token(); + //译者注,调用结果类似: + +**在路由中附加CSRF过滤器** + + Route::post('profile', array('before' => 'csrf', function() + { + // + })); + + +## 表单与模型绑定 + +你可以使用 `Form::model` 方法将模型中的内容填充到表单中去: + +**创建一个模型表单** + + echo Form::model($user, array('route' => array('user.update', $user->id))) + +当你创建一个表单元素(如文本输入字段)时,表单域的值会被自动设置成与之匹配的模型属性的值。例如表单中name属性为 `email` 的文本域的值会被设置为用户模型中 `email` 属性的值。不仅仅这样,当Session数据中有项目与表单域名称相匹配,Session值的填充优先级会高于模型的值。具体的优先级如下: + +1. Session 数据 (旧的输入数据) +2. 已传输的数据 +3. 模型属性数据 + +这可以帮助你快速建立与模型绑定的表单,而且当服务器端验证出表单错误时可以轻松回填数据! + +> **注意:** 当你使用 `Form::model` 时,别忘了在表单结尾处加上 `Form::close` ! + + +## Label 标签 + +**获取一个 label 标签元素** + + echo Form::label('email', 'E-Mail Address'); + //译者注,输出结果为: + +**设定其他 HTML 标签属性** + + echo Form::label('email', 'E-Mail Address', array('class' => 'awesome')); + //译者注,输出结果为: + +> **注意:** 当你创建了一个label标签后,name值与之相匹配的表单元素会被自动加上一个同名的ID属性。 + + +## 单行文本、多行文本、密码和隐藏表单域 + +**获取一个单行文本域** + + echo Form::text('username'); + //译者注,输出结果为: + +**设置一个默认的值** + + echo Form::text('email', 'example@gmail.com'); + //译者注,输出结果为: + +> **注意:** 调用 *hidden* 隐藏域和 *textarea* 多行文本表单域的方法和调用 *text* 单行文本域的方法是一样的。 + +**获取一个密码域** + + echo Form::password('password'); + //译者注,输出结果为: + + +## 多选框和单选框 + +**获取一个多选框和单选框** + + echo Form::checkbox('name', 'value'); + //译者注,输出结果为: + echo Form::radio('name', 'value'); + //译者注,输出结果为: + +**获取一个已选中的多选框和单选框** + + echo Form::checkbox('name', 'value', true); + //译者注,输出结果为: + echo Form::radio('name', 'value', true); + //译者注,输出结果为: + + +## 文件上传 + +**获取一个文件上传域** + + echo Form::file('image'); + //译者注,输出结果为: + + +## 下拉菜单 + +**获取一个下拉菜单** + + echo Form::select('size', array('L' => 'Large', 'S' => 'Small')); + //译者注,输出结果为: + +**获取一个已选中默认值的下拉菜单** + + echo Form::select('size', array('L' => 'Large', 'S' => 'Small'), 'S'); + //译者注,输出结果为: + +**获取一个分组下拉菜单** + + echo Form::select('animal', array( + 'Cats' => array('leopard' => 'Leopard'), + 'Dogs' => array('spaniel' => 'Spaniel'), + )); + //译者注,输出结果为: + +**生成一个包含可取值范围的下拉菜单** + + echo Form::selectRange('number', 10, 20); + +**生成月份名称列表** + + echo Form::selectMonth('month'); + + +## 按钮 + +**获取一个提交按钮** + + echo Form::submit('Click Me!'); + //译者注,输出结果为: + +> **注意:** 如果需要创建一个单独的表单元素,可以使用 *button* 方法。它的调用方式和 *submit* 方法一样。 + + +## 自定义宏 + +你可以轻松定义一个表单宏来帮助你创建表单。下面是使用表单宏的方法,注册一个宏名称,然后在闭包函数中定义对应的操作: + +**注册一个表单宏** + + Form::macro('myField', function() + { + return ''; + }); + +然后你就可以使用刚刚定义的宏名称调用它: + +**调用一个自定义表单宏** + + echo Form::myField(); + + + +##生成URL + +请参考 [helpers](/docs/helpers#urls) 文档了解更多关于生成URL的信息。 diff --git a/cn/installation.md b/cn/installation.md new file mode 100644 index 00000000000..3d3c5764196 --- /dev/null +++ b/cn/installation.md @@ -0,0 +1,68 @@ +# 安装 + +- [安装Composer](#install-composer) +- [安装Laravel](#install-laravel) +- [服务器环境要求](#server-requirements) +- [配置](#configuration) +- [优雅链接](#pretty-urls) + + +## 安装Composer + +Laravel框架使用[Composer](http://getcomposer.org)(PHP包管理工具,参考 [Composer 中文文档](http://www.phpcomposer.com/))来管理代码依赖性。 +首先,你需要下载Composer的PHAR打包文件( `composer.phar` ),下载完成后把它放在项目目录下或者放到 `usr/local/bin` 目录下以便在系统中全局调用。在Windows操作系统中,你可以使用Composer的[Windows安装工具](https://getcomposer.org/Composer-Setup.exe)。 + + +## 安装Laravel + +### 通过 Composer create-project 命令安装Laravel + +通过在命令行执行 Composer `create-project` 命令来安装Laravel: + + composer create-project laravel/laravel --prefer-dist + +### 通过下载Laravel包安装 + +Composer安装完成后,下载[最新版Laravel框架](https://github.com/laravel/laravel/archive/master.zip),把它解压缩到你服务器上的一个目录中。然后在Laravel应用的根目录下运行命令行命令 `php composer.phar install` (或者 `composer install` )来安装所有的框架依赖包。在此过程中,为了成功完成安装,你需要在服务器上安装好Git。 + +当Laravel框架安装好后,你可以使用命令行命令 `php composer.phar update` 来更新框架。 + + +## 服务器环境要求 + +Laravel框架有一些系统要求: + +- PHP最低版本: 5.3.7 +- MCrypt PHP扩展 + +从PHP 5.5版本开始,针对某些操作系统的安装包需要你自己手工安装PHP的JSON扩展模块。如果你使用的是Ubuntu,可以通过, `apt-get install php5-json` 命令直接安装。(译者注:还是Ubuntu傻瓜化啊!!!) + + +## 配置 + +Laravel框架几乎无需配置就可立即使用。你可以自由地快速开始开发。然而,你也许希望先查看下 `app/config/app.php` 配置文件和相关的文档说明。它包含了一些你也许要修改的配置选项,如 `时区` 和 `地区` 等。 + + +### 权限设置 +Laravel框架有一处需要设置权限 —— app/storage 目录下的文件需要服务器上的写权限。 + + +### 路径设置 + +一些框架目录路径是可以设置的。如果需要改变这些目录的位置,可以查看 `bootstrap/paths.php` 文件中的设置。 + + +## 优雅链接 + +Laravel框架通过设置 `public/.htaccess` 文件去除链接中的`index.php`。 如果你你的服务器使用的是Apache,请开启`mod_rewrite` 模块。 + +如果框架附带的 `.htaccess` 文件在你的Apache环境中不起作用,请尝试下面这个版本: + + Options +FollowSymLinks + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^(.+)/$ http://%{HTTP_HOST}/$1 [R=301,L] + + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] diff --git a/cn/introduction.md b/cn/introduction.md new file mode 100644 index 00000000000..a9371858c0d --- /dev/null +++ b/cn/introduction.md @@ -0,0 +1,36 @@ +# 简介 + +- [Laravel哲学](#laravel-philosophy) +- [学习Laravel](#learning-laravel) +- [研发小组](#development-team) + + +## Laravel哲学 + +Laravel是一套web应用开发框架,它具有富于表达性且简洁的语法。我们相信,开发过程应该是愉悦、创造性的体验。Laravel努力剔除开发过程中的痛苦,因此我们提供了验证(authentication)、路由(routing)、session和缓存(caching)等开发过程中经常用到的工具或功能。 + +Laravel的目标是给开发者创造一个愉快的开发过程,并且不牺牲应用的功能性。快乐的开发者才能创造最棒的代码!为了这个目的,我们博取众框架之长处集中到Laravel中,这些框架甚至是基于Ruby on Rails、ASP.NET MVC、和Sinatra等开发语言或工具的。 + +Laravel是易于理解并且强大的,它提供了强大的工具用以开发大型、健壮的应用。杰出的IoC、数据库迁移工具和紧密集成的单元测试支持,这些工具赋予你构建任何应用的能力。 + + +## 学习Laravel + +一个最好的学习Laravel的途径就是通读整个文档。你现在所看到的这份指南详述了框架的所有方面,还告诉你如何在你的应用中使用Laravel。 + +除了这份指南,也许你可以看一看其它的[Laravel书籍](http://wiki.laravel.io/Books)。下面列出了一些社区贡献的书籍,对于学习Laravel框架也是非常好的补充: + +- [Code Bright](https://leanpub.com/codebright) by Dayle Rees +- [Laravel Testing Decoded](https://leanpub.com/laravel-testing-decoded) by Jeffrey Way +- [Laravel: From Apprentice To Artisan](https://leanpub.com/laravel) by Taylor Otwell +- [Implementing Laravel](https://leanpub.com/implementinglaravel) by Chris Fidao +- [Getting Stuff Done With Laravel 4](https://leanpub.com/gettingstuffdonelaravel) by Chuck Heintzelman +- [Laravel 4 Cookbook](https://leanpub.com/laravel4cookbook) by Christopher Pitt +- [Laravel in Action](http://www.manning.com/surguy/) by Maks Surguy + + +## 研发小组 + +Laravel was created by [Taylor Otwell](https://github.com/taylorotwell), who continues to lead development of the framework. Other prominent community members and contributors include [Dayle Rees](https://github.com/daylerees), [Shawn McCool](https://github.com/ShawnMcCool), [Jeffrey Way](https://github.com/JeffreyWay), [Jason Lewis](https://github.com/jasonlewis), [Ben Corlett](https://github.com/bencorlett), [Franz Liedke](https://github.com/franzliedke), [Dries Vints](https://github.com/driesvints), [Mior Muhammed Zaki](https://github.com/crynobone), and [Phil Sturgeon](https://github.com/philsturgeon). + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) diff --git a/cn/ioc.md b/cn/ioc.md new file mode 100644 index 00000000000..8b7722867a3 --- /dev/null +++ b/cn/ioc.md @@ -0,0 +1,178 @@ +# IoC 容器 + +- [简介](#introduction) +- [基本用例](#basic-usage) +- [自动解析](#automatic-resolution) +- [实际用例](#practical-usage) +- [服务提供器](#service-providers) +- [容器事件](#container-events) + + +## 简介 + +Laravel 控制器反转容器是一个强大的工具来处理类依赖关系。依赖注入是一个不用硬代码处理类依赖关系的方法。依赖关系是在运行时注入的,允许处理依赖时具有更大的灵活性。 + +理解 Laravel IoC 容器是构建强大应用程序所必要的,也有助于 Laravel 核心本身。 + + +## 基本用例 + +IoC 容器有两种方法来解决依赖关系:通过闭包回调或者自动解析。首先,我们来探究一下闭包回调。首先,需要绑定一个“类型”到容器中: + +**帮定一个类型到容器** + + App::bind('foo', function($app) + { + return new FooBar; + }); + +**从容器中取得一个类型** + + $value = App::make('foo'); + +当执行 `App::make` 方法,闭包函数被执行并返回结果。 + +有时,你只想将绑定到容器的类型只应该取得一次,然后接下来从容器中取得的都应该是相同实例: + +**绑定一个”共享“类型到容器** + + App::singleton('foo', function() + { + return new FooBar; + }); + +你也可以使用 `instance`方法 将一个已经存在的类型绑定到容器中: + +**绑定一个已经存在的类型实例到容器** + + $foo = new Foo; + + App::instance('foo', $foo); + + +## 自动解析 + +IoC 容器在许多场景下足够强大来取得类而不需要任何配置。例如: + +**取得一个类** + + class FooBar { + + public function __construct(Baz $baz) + { + $this->baz = $baz; + } + + } + + $fooBar = App::make('FooBar'); + +注意我们虽然没有在容器中注册FooBar类,容器仍然可以取得该类,甚至自动注入 `Baz` 依赖! + +当某个类型没有绑定到容器,将使用 PHP 的反射工具来检查类和读取构造器的类型提示。使用这些信息,容器可以自动构建类实例。 + +然而,在某些情况下,一个类可能依赖某个接口实现,而不是一个 “具体的类”。当在这种情况下,`App::bind` 方法必须通知容器注入该实现哪个接口: + +**绑定一个接口给实现类** + + App::bind('UserRepositoryInterface', 'DbUserRepository'); + +现在考虑以下控制器: + + class UserController extends BaseController { + + public function __construct(UserRepositoryInterface $users) + { + $this->users = $users; + } + + } + +由于我们将 `UserRepositoryInterface` 绑定了具体类,`DbUserRepository` 在该控制器创建时将自动注入到该控制器。 + + +## 实际用例 + +Laravel 提供了几个方法使用 IoC 容器增强应用程序可扩展性和可测试性。一个主要的例子是取得控制器。所有控制器都通过 IoC 容器取得,意味着可以解决控制器构造方法类型提示的依赖关系,它们将自动被注入。 + +**控制器类型提示依赖关系** + + class OrderController extends BaseController { + + public function __construct(OrderRepository $orders) + { + $this->orders = $orders; + } + + public function getIndex() + { + $all = $this->orders->all(); + + return View::make('orders', compact('all')); + } + + } + +在这个例子中,`OrderRepository` 将会自动注入到控制器。意味着当 [单元测试](/docs/testing) 模拟请求时,`OrderRepository` 将会绑定到容器以及注入到控制器中,允许无痛与数据库层交互。 + +[过滤器](/docs/routing#route-filters), [composers](/docs/responses#view-composers), 和 [事件处理器](/docs/events#using-classes-as-listeners) 也通过 IoC 容器来取得。当注册了它们,仅简单提供要使用的类名: + +**IoC 使用的其他例子** + + Route::filter('foo', 'FooFilter'); + + View::composer('foo', 'FooComposer'); + + Event::listen('foo', 'FooHandler'); + + +## 服务提供器 + +服务器提供器是将一组相关 IoC 注册到单一路径的有效方法。将它们看做是一种引导组件的方法。在服务器提供器里,你可以注册自定义的验证驱动器,使用 IoC 容器注册应用程序仓库类,甚至是自定义 Artisan 命令。 + +事实上,大多数核心 Laravel 组件 包含服务提供器。应用程序所有注册在服务提供器的均列在 `app/config/app.php` 配置文件的 `providers` 数组中。 + +要创建服务提供器,简单的继承 `Illuminate\Support\ServiceProvider` 类并且定义一个 `register` 方法: + +**定义一个服务提供器** + + use Illuminate\Support\ServiceProvider; + + class FooServiceProvider extends ServiceProvider { + + public function register() + { + $this->app->bind('foo', function() + { + return new Foo; + }); + } + + } + +注意在 `register` 方法,应用程序通过 `$this->app` 属性访问 IoC 容器。一旦你已经创建了提供器并且想将它注册到应用程序中, 只需简单的放入 `app` 配置文件里 `providers` 数组中。 + +也可以使用 `App::register` 方法在运行时注册服务提供器: + +**运行时注册服务提供器** + + App::register('FooServiceProvider'); + + +## 容器事件 + +容器在每次获取对象时都触发一个时间。你可以通过使用 `resolving` 方法来监听该事件: + +**注册获取事件监听者** + + App::resolvingAny(function($object) + { + // + }); + + App::resolving('foo', function($foo) + { + // + }); + +注意获取的对象将会传入回调函数中。 \ No newline at end of file diff --git a/cn/lifecycle.md b/cn/lifecycle.md new file mode 100644 index 00000000000..14c20f5a6f2 --- /dev/null +++ b/cn/lifecycle.md @@ -0,0 +1,41 @@ +# Request的生命周期 + +- [概述](#overview) +- [启动文件](#start-files) +- [应用程序事件](#application-events) + + +## 概述 + +Laravel中的request的生命周期相当简单。当一个request进入到你应用程序时,它被调度到适当的路由或控制器。从该路由输出的response将返回到浏览器并显示在屏幕上。有时你想在路由执行之前或后做一些其他处理。有几种办法可以达到该目的,其中两种办法是"start"文件和应用程序事件。 + + +## 启动文件 + +应用程序的启动文件被存放在`app/start`目录中。默认情况下,该目录下包含三个文件:`global.php`、`local.php` 和 `artisan.php`文件。需要获取更多关于`artisan.php`的信息,可以参考文档[Artisan 命令行](/docs/commands#registering-commands)。 + +`global.php`启动文件默认包含一些基本项目,例如[Logger](/docs/errors)的注册以及载入`app/filters.php` 文件。然而,你可以在该文件里做任何你想做的事情。无论在什么环境下,它都将会被自动包含进_每一个_request中。而`local.php` 文件仅在`local`环境下被执行。获取更多关于环境的信息,请查看文档[配置](/docs/configuration)。 + +当然,如果除了`local`环境你还有其他环境的话,你也可以为针对这些环境创建启动文件。这些文件将在应用程序运行在该环境中时被自动包含。 + + + +## 应用程序事件 + +你还可以通过注册 `before`、`after`、`close`、`finish` 和 `shutdown`应用程序事件以便在处理request之前或后做一些操作: + +**注册应用程序事件** + + App::before(function($request) + { + // + }); + + App::after(function($request, $response) + { + // + }); + +上述事件的监听器将会在每个request `之前(before)` 和 `之后(after)`运行 。 + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) \ No newline at end of file diff --git a/cn/localization.md b/cn/localization.md new file mode 100644 index 00000000000..e9160f8c9a2 --- /dev/null +++ b/cn/localization.md @@ -0,0 +1,89 @@ +# 本地化 + +- [简介](#introduction) +- [语言文件](#language-files) +- [基本用例](#basic-usage) +- [复数形式](#pluralization) +- [验证消息的本地化](#validation) + + +## 简介 + +Laravel `Lang` 类提供非常方便的方法来从不同语言文件中取得字符串,允许在应用程序中支持多语言。 + + +## 语言文件 + +语言文字存放在 `app/lang` 目录。应用程序所要支持的语言都需要在此目录建立为子目录。 + + /app + /lang + /en + messages.php + /es + messages.php + +语言文件只是返回键值(字符串)对数组。例如: + +**语言文件示例** + + 'Welcome to our application' + ); + +应用程序默认语言配置在 `app/config/app.php`配置文件中 `locale` 配置项.你可以在任何时候使用 `App::setLocale` 方法来改变当前激活语言。 + +**在运行时改变默认语言** + + App::setLocale('es'); + + +## 基本用例 + +**从语言文件中获取文本** + + echo Lang::get('messages.welcome'); + +传给 `get` 方法字符串第一部分是语言文件名称,第二个部分是要取得文本的名字。 + +> **注意**: 如果语言不存在该文本,那么 `get` 方法会将键返回。 + +**文本中替换** + +可以在语言文件中定义占位符: + + 'welcome' => 'Welcome, :name', + +然后,将要替换的值传递给 `Lang::get` 方法的第二个参数: + + echo Lang::get('messages.welcome', array('name' => 'Dayle')); + +**判断语言文件是否存在该文本** + + if (Lang::has('messages.welcome')) + { + // + } + + +## 复数形式 + +复数形式是一个复杂的问题,因为不同的语言有着不同的复数形式规则。你可以通过简单的在语言文件中使用”管道“符来分开单数和复数文本形式: + + 'apples' => 'There is one apple|There are many apples', + +然后你就可以使用 `Lang::choice` 方法来取得文本: + + echo Lang::choice('messages.apples', 10); + +由于Laravel翻译机制是用Symfony的翻译组件,你也可以非常简单的创建更加复杂的复数形式规则: + + 'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many', + + + +## 验证 + +对于验证功能中需要本地化的错误信息和提示信息,请参阅 相关文档。 \ No newline at end of file diff --git a/cn/mail.md b/cn/mail.md new file mode 100644 index 00000000000..50b5f69eb4b --- /dev/null +++ b/cn/mail.md @@ -0,0 +1,114 @@ +# 邮件 + +- [配置](#configuration) +- [基本用法](#basic-usage) +- [嵌入内联附件](#embedding-inline-attachments) +- [队列邮件](#queueing-mail) +- [邮件 & 本地开发环境](#mail-and-local-development) + + +## 配置 + +Laravel的邮件功能构建于流行的[SwiftMailer](http://swiftmailer.org)库之上,并提供了简介、高效的API。邮件配置信息在`app/config/mail.php`文件中,并提供了包含SMTP主机、端口和证书的配置选项,也可以为发送的邮件配置一个全局`from`(来自)地址。你可以使用任何的SMTP服务器。如果你希望使用PHP的`mail`函数来发送邮件,可以通过改变配置文件中的 `driver`为`mail`。另外还支持`sendmail` 。 + + + +## 基本用例 + +使用`Mail::send` 方法来发送一封邮件: + + Mail::send('emails.welcome', $data, function($message) + { + $message->to('foo@example.com', 'John Smith')->subject('Welcome!'); + }); + +传入`send`方法的第一个参数为生成邮件体所用的视图名。第二个参数`$data`是要传入视图的数据,第三个参数为闭包,允许你为邮件配置各种选项。 + +> **注意:** `$message`变量总是会传递到邮件视图中,它允许你给该邮件内容添加内联附件。因此应该避免向视图中传递命名为`message`的变量。 + +> **注解:** 内联附件(Inline Attachment):“内联附件”是指可以在邮件体中直接看到的附件,一般是文本或图片;“内联附件”与一般附件的区别在于:一般附件必须在点击之后才能查看。详细信息可以看[内联附件与一般附件的区别](http://www.mkyong.com/computer-tips/different-between-inline-and-attachment-in-email/)。 + +除了HTML视图,你还可以指定一个纯文本视图: + + Mail::send(array('html.view', 'text.view'), $data, $callback); + +或者,你可以通过`html`或`text`关键字指定唯一一个视图类型: + + Mail::send(array('text' => 'view'), $data, $callback); + +你还可以为邮件指定其他选项,例如邮件抄送者或者附件: + + Mail::send('emails.welcome', $data, function($m) + { + $m->from('us@example.com', 'Laravel'); + + $m->to('foo@example.com')->cc('bar@example.com'); + + $m->attach($pathToFile); + }); + +当你为邮件添加附件时,可以指定MIME类型和/或展示名: + + $m->attach($pathToFile, array('as' => $display, 'mime' => $mime)); + +> **注意:** 传递给`Mail::send`闭包的消息对象实例继承自SwiftMailer类,因此,你可以调用任何该类的方法来构建邮件内容。 + + +## 嵌入内联附件 + +邮件中嵌入图片通常都很麻烦;幸好Laravel提供了很简便的方法来为你的邮件添加图片,并取得相应的CID。 + +**在邮件视图中嵌入内联图像** + + + Here is an image: + + + + +**在邮件视图中嵌入原始数据** + + + Here is an image from raw data: + + + + +注意`$message`变量总会通过`Mail`类传递给邮件视图。 + + +## 队列邮件 + +由于发送邮件有可能会使应用程序需要花费较长的响应时间,许多开发者选择将邮件放入队列并在后台发送。Laravel内建了[统一队列 API](/docs/queue)来简化此功能。只需调用`Mail`类的`queue`方法就可以将邮件放入队列中: + +**将一封邮件放入队列中** + + Mail::queue('emails.welcome', $data, function($m) + { + $m->to('foo@example.com', 'John Smith')->subject('Welcome!'); + }); + +你还可以使用`later`方法指定延迟多少秒再发送邮件: + + Mail::later(5, 'emails.welcome', $data, function($m) + { + $m->to('foo@example.com', 'John Smith')->subject('Welcome!'); + }); + +如果你想将邮件放到一个指定的队列或"管道" ,可以使用`queueOn`和`laterOn`方法: + + Mail::queueOn('queue-name', 'emails.welcome', $data, function($m) + { + $m->to('foo@example.com', 'John Smith')->subject('Welcome!'); + }); + + +## 邮件 & 本地开发环境 + +当你开发需要发送邮件的应用时,在你本地或开发环境中通常需要禁用邮件发送功能。你可以通过调用`Mail::pretend` 方法或在 `app/config/mail.php` 配置文件中设置 `pretend` 选项为 `true` 达到这一目的。当邮件发送功能被置为 `pretend` 模式时,所有邮件都会被写入当前应用的log文件中,而不会发送给收件人。 + +**启用Pretend模式** + + Mail::pretend(); + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) \ No newline at end of file diff --git a/cn/migrations.md b/cn/migrations.md new file mode 100644 index 00000000000..10535189b48 --- /dev/null +++ b/cn/migrations.md @@ -0,0 +1,106 @@ +# 迁移 & 数据填充 + +- [简介](#introduction) +- [创建迁移](#creating-migrations) +- [运行迁移](#running-migrations) +- [回滚迁移](#rolling-back-migrations) +- [数据库填充](#database-seeding) + + +## 简介 + +迁移是一种数据库的版本控制。它允许一个团队修改数据库结构,并在当前模式下保持最新。迁移通常和 [结构生成器](/docs/schema) 配对使用来管理您应用程序的结构。 + + +## 创建迁移 + +使用 Artisan 命令行的 `migrate:make` 命令创建一个迁移: + +**创建一个迁移** + + php artisan migrate:make create_users_table + +所有的迁移都被存放在 `app/database/migrations` 目录下,并且包含一个时间戳允许框架检测迁移的顺序。 + +您可以在创建迁移的时候指定 `--path` 选项,指定的目录应该相对于安装目录的主目录: + + php artisan migrate:make foo --path=app/migrations + +`--table` 和 `--create` 选项可以被用来指定表名以及是否创建一个新表: + + php artisan migrate:make create_users_table --table=users --create + + +## 运行迁移 + +**运行所有迁移** + + php artisan migrate + +**运行某个路径下的所有迁移** + + php artisan migrate --path=app/foo/migrations + +**运行某个包下的所有迁移** + + php artisan migrate --package=vendor/package + +> **注意:** 如果在运行迁移的时候收到一个 "class not found" 的错误,请尝试运行 `composer dump-autoload` 命令。 + + +## 回滚迁移 + +**回滚最后一次迁移** + + php artisan migrate:rollback + +**回滚所有迁移** + + php artisan migrate:reset + +**回滚所有迁移并重新运行所有迁移** + + php artisan migrate:refresh + + php artisan migrate:refresh --seed + + +## 数据库填充 + +Laravel 还包含一个简单的方式通过填充类使用测试数据填充您的数据库。所有的填充类都存放在 `app/database/seeds` 目录下。填充类可以以形式命名,但最好遵循一些合理的约束,比如 `UserTableSeeder` 等。默认情况下,一个 `DatabaseSeeder` 类以为您定义。在这个类中,您可以使用 `call` 函数运行其他填充类,允许您控制填充顺序。 + +**数据库填充类的例子** + + class DatabaseSeeder extends Seeder { + + public function run() + { + $this->call('UserTableSeeder'); + + $this->command->info('User table seeded!'); + } + + } + + class UserTableSeeder extends Seeder { + + public function run() + { + DB::table('users')->delete(); + + User::create(array('email' => 'foo@bar.com')); + } + + } + +使用 Artisan 命令行的 `db:seed` 命令填充数据库: + + php artisan db:seed + +默认情况下,`db:seed` 命令运行的是 `DatabaseSeeder` 类,这个类还会调用其他seed类。然而,你可以是使用 `--class` 参数来指定一个seed类: + + php artisan db:seed --class=UserTableSeeder + +您也可以使用 `migrate:refresh` 命令填充数据库,将会回滚并重新运行所有迁移: + + php artisan migrate:refresh --seed diff --git a/cn/packages.md b/cn/packages.md new file mode 100644 index 00000000000..dff03503d43 --- /dev/null +++ b/cn/packages.md @@ -0,0 +1,198 @@ +# 包开发 + +- [简介](#introduction) +- [创建包](#creating-a-package) +- [包结构](#package-structure) +- [服务提供器](#service-providers) +- [包规定](#package-conventions) +- [开发流程](#development-workflow) +- [包路由](#package-routing) +- [包配置](#package-configuration) +- [包迁移](#package-migrations) +- [包Assets](#package-assets) +- [发布包](#publishing-packages) + + +## 简介 + +包是主要作用是为Laravel添加功能。包可以是任何东西,例如用于日期处理的[Carbon](https://github.com/briannesbitt/Carbon),或者是一个完整的BDD测试框架[Behat](https://github.com/Behat/Behat)。 + +当然,还有很多不同类型的包。有些包是独立的,这意味着它们可以在任何框架中工作,而不仅仅是Laravel。上面提到的Carbon和Behat就是独立的包。要在Laravel中使用这些包只需要在`composer.json`文件中指明。 + +另一方面,有些包仅支持Laravel。在上一个Laravel版本中,这些类型的包我们称为"bundles"。这些包可以包含专为增强Laravel应用的路由、控制器、视图、配置和迁移。由于开发独立的包不需要专门的过程,因此,本手册主要涵盖针对Laravel开发独立的包。 + +所有Laravel包都是通过[Packagist](http://packagist.org)和[Composer](http://getcomposer.org)发布的,因此很有必要学习这些PHP包发布工具。 + + +## 创建包 + +为Laravel创建一个包的最简单方式是使用Artisan的`workbench`命令。首先,你需要在`app/confg/workbench.php`文件中配置一些参数。在该文件中,你会看到`name`和`email`两个参数,这些值是用来为新创建的包生成`composer.json`文件的。一旦你提供了这些值,就可以开始构建一个新包了! + +**执行Artisan中的Workbench命令** + + php artisan workbench vendor/package --resources + +厂商名称(vendor name)是用来区分不同作者构建的相同名字的包。例如,如果我(Taylor Otwell)创建了个名为"Zapper"的包,厂商名就可以叫做`Taylor`,包名可以叫做`Zapper`。默认情况下,workbench命令建的包是不依赖任何框架的;然而,`resources`命令将会告诉workbench创建特定于Laravel的一些目录,例如`migrations`、`views`、`config`等。 + +一旦执行了`workbench`命令,新创建的包就会出现在Laravel安装目录下的`workbench`目录中。接下来就应该为你创建的包注册`ServiceProvider`了。你可以通过在`app/config/app.php`文件里的`provides`数组中添加该包。这将通知Laravel在应用程序开始启动时加载该包。服务提供者(Service providers)使用`[Package]ServiceProvider`样式的命名方式。所以,以上案例中,你需要将`Taylor\Zapper\ZapperServiceProvider`添加到`providers`数组。 + +一旦注册了provider,你就可以开始写代码了!然而,在此之前,建议你查看以下部分来了解更多关于包结构和开发流程的知识。 + + +## 包结构 + +执行`workbench`命令之后,你的包将被初始化规定的结构,并能够与laravel框架融合: + +**基本包目录结构** + + /src + /Vendor + /Package + PackageServiceProvider.php + /config + /lang + /migrations + /views + /tests + /public + +让我们来深入了解该结构。`src/Vendor/Package`目录是所有class的主目录,包括`ServiceProvider`。`config`、`lang`、`migrations`和`views`目录,就如你所猜测,包含了相应的资源。包可以包含这些资源中的任意几个,就像一个"regular"的应用。 + + +## 服务提供器 + +服务提供器只是包的引导类。默认情况下,他们包含两个方法:`boot`和`register`。你可以在这些方法内做任何事情,例如:包含路由文件、注册IoC容器的绑定、监听事件或者任何想做的事情。 + +`register`方法在服务提供器注册时被立即调用,而`boot`方法仅在请求被路由前调用。因此,如果服务提供器中的动作(action)依赖另一个已经注册的服务提供器,或者你正在覆盖另一个服务提供其绑定的服务,就应该使用`boot`方法。 + +当使用`workbench`命令创建包时,`boot`方法已经包含了如下的动作: + + $this->package('vendor/package'); + +该方法告诉Laravel如何为应用程序加载视图、配置或其他资源。通常情况下,你没有必要改变这行代码,因为它会根据workbench的默认约定将包设置好的。 + +By default, after registering a package, its resources will be available using the "package" half of `vendor/package`. However, you may pass a second argument into the `package` method to override this behavior. For example: + + // Passing custom namespace to package method + $this->package('vendor/package', 'custom-namespace'); + + // Package resources now accessed via custom-namespace + $view = View::make('custom-namespace::foo'); + +There is not a "default location" for service provider classes. You may put them anywhere you like, perhaps organizing them in a `Providers` namespace within your `app` directory. The file may be placed anywhere, as long as Composer's [auto-loading facilities](http://getcomposer.org/doc/01-basic-usage.md#autoloading) know how to load the class. + + +## 包约定 + +要使用包中的资源,例如配置或视图,需要用双冒号语法: + +**加载包中的视图** + + return View::make('package::view.name'); + +**获取包的某个配置项** + + return Config::get('package::group.option'); + +> **注意:** 如果你包中包含迁移,请为迁移名(migration name)添加包名作为前缀,以避免与其他包中的类名冲突。 + + +## 开发流程 + +当开发一个包时,能够使用应用程序上文是相当有用的,这样将允许你很容易的解决视图模板的等问题。所以,我们开始,安装一个全新的Laravel框架,使用`workbench`命令创建包结构。 + +在使用`workbench`命令创建包后。`workbench/[vendor]/[package]`目录使用`git init`,`git push`!这将允许你在应用程序中方便开发而不用为`composer update`命令苦扰。 + +当包存放在`workbench`目录时,你可能担心Composer如何知道自动加载包文件。当`workbench`目录存在,Laravel将智能扫描该目录,在应用程序开始时加载它们的Composer自动加载文件! + + +## 包路由 + +在之前的Laravel版本中,`handlers`用来指定那个URI包会响应。然而,在Laravel4中,一个包可以相应任意URI。要在包中加载路由文件,只需在服务提供器的`boot`方法`include`它。 + +> **注意:** 如果你包中包含控制器,你需要确认你的`composer.json`文件中的auto-load部分已经正确配置。 + +**在服务提供器中包含路由文件** + + public function boot() + { + $this->package('vendor/package'); + + include __DIR__.'/../../routes.php'; + } + + +## 包配置 + +有时创建的包可能会需要配置文件。这些配置文件应该和应用程序配置文件相同方法定义。并且,当使用 `$this->package`方法来注册服务提供器时,那么就可以使用“双冒号”语法来访问: + +**访问包配置文件** + + Config::get('package::file.option'); + +然而,如果你包仅有一个配置文件,你可以简单命名为`config.php`。当你这么做时,你可以直接访问该配置项,而不需要特别指明文件名: + +**访问包单一配置文件** + + Config::get('package::option'); + +### 级联配置文件 + +但其他开发者安装你的包时,他们也许需要覆盖一些配置项。然而,如果从包源代码中改变值,他们将会在下次使用Composer更新包时又被覆盖。替代方法是使用artisan命令 `config:publish`: + +**执行配置公布命令** + + php artisan config:publish vendor/package + +当执行该命令,配置文件就会拷贝到`app/config/packages/vendor/package`,开发者就可以安全的更改配置项了。 + +> **注意:** 开发者也可以为该包创建指定环境的配置文件通过替换配置项并放置在`app/config/packages/vendor/package/environment. + + +## 包迁移 + +你可以很容易在包中创建和运行迁移。要为工作台里的包创建迁移,使用`--bench`选项: + +**为工作台的包创建迁移** + + php artisan migrate:make create_users_table --bench="vendor/package" + +**为工作台包运行迁移** + + php artisan migrate --bench="vendor/package" + +要为已经通过Composer安装在`vendor`目录下的包执行迁移,你可以直接使用`--package`: + +**为已安装的包执行迁移** + + php artisan migrate --package="vendor/package" + + +## 包Assets + +有些包可能含有assets,例如JavaScript,CSS,和图片。然而,我们无法链接到`vendor`或`workbench`目录里的assets,所以我们需要可以将这些assets移入应用程序的`public`目录。`asset:publish`命令可以实现: + +**将包Assets移动到Public** + + php artisan asset:publish + + php artisan asset:publish vendor/package + +If the package is still in the `workbench`, use the `--bench` directive: + + php artisan asset:publish --bench="vendor/package" + +This command will move the assets into the `public/packages` directory according to the vendor and package name. So, a package named `userscape/kudos` would have its assets moved to `public/packages/userscape/kudos`. Using this asset publishing convention allows you to safely code asset paths in your package's views. + + +## 发布包 + +当你创建的包准备发布时,你应该将包提交到[Packagist](http://packagist.org)仓库。如果你的包只针对Laravel,最好在包的`composer.json`文件中添加`laravel`标签。 + +还有,在发布的版本中添加tag,以便开发者能当请求你的包在他们`composer.json`文件中依赖稳定版本。如果稳定版本还没有好,考虑直接在Composer中使用`branch-alias`。 + +一旦你的包发布,舒心的继续开发通过`workbench`创建的应用程序上下文。这是很方便的方法来在发布包后继续开发包。 + +一些组织使用他们私有分支包为他们自己开发者。如果你对这感兴趣,查看Composer团队构建的[Satis](http://github.com/composer/satis)文档。 + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) diff --git a/cn/pagination.md b/cn/pagination.md new file mode 100644 index 00000000000..9bb5d136f52 --- /dev/null +++ b/cn/pagination.md @@ -0,0 +1,68 @@ +# 分页 + +- [配置](#configuration) +- [基本用法](#usage) +- [给分页链接添加自定义信息](#appending-to-pagination-links) + + +## 配置 + +在其它的框架中,分页有时很痛苦. 但是Laravel让分页简单到不可思议. 默认Laravel包含了两个分页视图, 在`app/config/view.php` 文件中的pagination选项中指定分页链接具体使用哪一个视图. + +`pagination::slider`视图 基于当前所在页数给出一个浮动的页数范围,`pagination::simple` 视图只是简单的给出 '上一页' '下一页' 两个链接. **两个视图都能完美的和bootstrap框架结合** + + +## 基本用法 + +Laravel有多种方式实现分页. 最简单的是在普通查询或Eloquent模型查询中使用 `paginate` 方法. + +**从数据库查询中分页** + + $users = DB::table('users')->paginate(15); + +也可以从 [Eloquent](/docs/eloquent) 模型中分页: + +**从一个 Eloquent 模型中分页** + + $allUsers = User::paginate(15); + + $someUsers = User::where('votes', '>', 100)->paginate(15); + +你只需要吧每页查询的记录数传给`paginate`方法即可. 在获得包含分页的查询结果集之后, 就可以在视图中展示了, 在视图中输出分页链接使用 `links` 方法: + +
+ + name; ?> + +
+ + links(); ?> + +以上就是创建一个分页链接的所需的所有信息了! 我们还没有提到Laravel做了什么. Laravel会自己把其它的事情做完. + +你也可以通过下面的方法获取关于分页更加详尽的信息: + +- `getCurrentPage` +- `getLastPage` +- `getPerPage` +- `getTotal` +- `getFrom` +- `getTo` +- `count` + +有时你可能希望自定义分页, 只需使用 `Paginator::make` 方法,并把 记录集合(当前页需要展示的数据集), 总记录数(int), 每页记录数(int) 作为参数: + +**自定义分页** + + $paginator = Paginator::make($items, $totalItems, $perPage); + + +## 给分页链接添加自定义信息 + +可以通过分页器的 `appends` 方法为分页链接添加上自定查询字符串: + + appends(array('sort' => 'votes'))->links(); ?> + +最后产生的url如下: + + http://example.com/something?page=2&sort=votes diff --git a/cn/queries.md b/cn/queries.md new file mode 100644 index 00000000000..e8fce110838 --- /dev/null +++ b/cn/queries.md @@ -0,0 +1,282 @@ +# 查询生成器 + +- [简介](#introduction) +- [获取数据](#selects) +- [连接](#joins) +- [高级查询条件](#advanced-wheres) +- [聚合查询结果](#aggregates) +- [原始查询表达式](#raw-expressions) +- [创建数据](#inserts) +- [更新数据](#updates) +- [删除数据](#deletes) +- [union合并查询结果](#unions) +- [缓存查询结果](#caching-queries) + + +## 简介 + +查询生成器 为操作数据库提供了一个方便,顺畅的的接口,它支持所有Laravel支持的数据库系统,并能够完成绝大部分查询任务。 + +> **注意:** 查询生成器 使用了PDO参数绑定传递的方式,从而避免sql注入攻击,也就是在使用参数时不需要进行保证安全性的过滤操作。 + + +## 获取数据 + +**获取一张表里的所有数据** + + $users = DB::table('users')->get(); + + foreach ($users as $user) + { + var_dump($user->name); + } + +**获取一张表里的一条数据** + + $user = DB::table('users')->where('name', 'John')->first(); + + var_dump($user->name); + +**获取一张表里的满足where条件的第一行数据的指定字段的值** + + $name = DB::table('users')->where('name', 'John')->pluck('name'); + +**以列表形势获取一张表里一个字段的值** + + $roles = DB::table('roles')->lists('title'); + +lists方法返回一个包含所有roles表的title字段的值的数组. 可以通过lists的第二个参数为返回的数组自定义键名: + + $roles = DB::table('roles')->lists('title', 'name'); + +**筛选查询结果** + + $users = DB::table('users')->select('name', 'email')->get(); + + $users = DB::table('users')->distinct()->get(); + + $users = DB::table('users')->select('name as user_name')->get(); + +**为已经建立的查询添加筛选** + + $query = DB::table('users')->select('name'); + + $users = $query->addSelect('age')->get(); + +**使用where条件语句** + + $users = DB::table('users')->where('votes', '>', 100)->get(); + +**使用or语句** + + $users = DB::table('users') + ->where('votes', '>', 100) + ->orWhere('name', 'John') + ->get(); + +**在Where语句中使用Between子句** + + $users = DB::table('users') + ->whereBetween('votes', array(1, 100))->get(); + +**在Where语句中使用In子句,In的内容通过数组传递** + + $users = DB::table('users') + ->whereIn('id', array(1, 2, 3))->get(); + + $users = DB::table('users') + ->whereNotIn('id', array(1, 2, 3))->get(); + +**使用whereNull方法获取未被清除或未被初始化的记录(字段如果没有指定默认值将会是null)** + + $users = DB::table('users') + ->whereNull('updated_at')->get(); + +**Order By语句, Group By语句, 和 Having 语句筛选** + + $users = DB::table('users') + ->orderBy('name', 'desc') + ->groupBy('count') + ->having('count', '>', 100) + ->get(); + +**Offset 和 Limit语句** + + $users = DB::table('users')->skip(10)->take(5)->get(); + + +## 连接 + +查询生成器 也可以用来建立数据连接操作,我们看看下面的例子: + +**简单连接语句** + + DB::table('users') + ->join('contacts', 'users.id', '=', 'contacts.user_id') + ->join('orders', 'users.id', '=', 'orders.user_id') + ->select('users.id', 'contacts.phone', 'orders.price'); + +**左连接(Left Join)语句** + + DB::table('users') + ->leftJoin('posts', 'users.id', '=', 'posts.user_id') + ->get(); + +指定更多的连接条件: + + DB::table('users') + ->join('contacts', function($join) + { + $join->on('users.id', '=', 'contacts.user_id')->orOn(...); + }) + ->get(); + + +## 高级查询条件 + +有时你可能需要创建更高级的where查询,比如 "where exists"筛选 或者给where条件分组. 查询生成器 都能够很好的处理: + +**where条件分组** + + DB::table('users') + ->where('name', '=', 'John') + ->orWhere(function($query) + { + $query->where('votes', '>', 100) + ->where('title', '<>', 'Admin'); + }) + ->get(); + +上面的查询将产生如下的 SQL: + + select * from users where name = 'John' or (votes > 100 and title <> 'Admin') + +**where中的Exists语句** + + DB::table('users') + ->whereExists(function($query) + { + $query->select(DB::raw(1)) + ->from('orders') + ->whereRaw('orders.user_id = users.id'); + }) + ->get(); + +上面的查询将产生如下的 SQL: + + select * from users + where exists ( + select 1 from orders where orders.user_id = users.id + ) + + +## 聚合查询结果 + +查询生成器 提供了多个聚合方法, 比如 `count`, `max`, `min`, `avg`,和 `sum`. + +**使用聚合方法** + + $users = DB::table('users')->count(); + + $price = DB::table('orders')->max('price'); + + $price = DB::table('orders')->min('price'); + + $price = DB::table('orders')->avg('price'); + + $total = DB::table('users')->sum('votes'); + + +## 原始查询表达式 + +有时,你可能需要使用原始的查询表达式, 这种表达式需要直接插入最终的sql语句中, 所以,请特别注意防范sql注入! 使用 `DB::raw` 方法实现原始查询表达式: + +**使用原始查询表达式** + + $users = DB::table('users') + ->select(DB::raw('count(*) as user_count, status')) + ->where('status', '<>', 1) + ->groupBy('status') + ->get(); + +**将数据表中的某个字段+1或-1** + + DB::table('users')->increment('votes'); + + DB::table('users')->decrement('votes'); + + +## 创建数据 + +**插入一条数据** + + DB::table('users')->insert( + array('email' => 'john@example.com', 'votes' => 0) + ); + +如果数据表已经有主键了, 使用 `insertGetId` 方法插入数据,不需要主键字段信息: + +**同时插入多条数据,同时让主键自增** + + $id = DB::table('users')->insertGetId( + array('email' => 'john@example.com', 'votes' => 0) + ); + +> **注意:** 当使用 PostgreSQL 数据库系统时, insertGetId 方法要求主键字段名为 id + +**一次插入多条数据** + + DB::table('users')->insert(array( + array('email' => 'taylor@example.com', 'votes' => 0), + array('email' => 'dayle@example.com', 'votes' => 0), + )); + + +## 更新数据 + +**更新数据** + + DB::table('users') + ->where('id', 1) + ->update(array('votes' => 1)); + + +## 删除数据 + +**普通删除方式** + + DB::table('users')->where('votes', '<', 100)->delete(); + +**删除一张表的所有数据** + + DB::table('users')->delete(); + +**清空一张表** + + DB::table('users')->truncate(); + + +## union合并查询结果 + +查询生成器提供了一个快速的方式来 "union" 两个查询: + +**使用Union合并两次查询** + + $first = DB::table('users')->whereNull('first_name'); + + $users = DB::table('users')->whereNull('last_name')->union($first)->get(); + +`unionAll` 也是可用的, 它和 `union` 方法一样. + + +## 缓存查询结果 + +使用 `remember` 方法可以很容易的缓存查询结果: + +**缓存一次查询的查询结果** + + $users = DB::table('users')->remember(10)->get(); + +在上面的例子中, 查询结果将被缓存10分钟, 当某个查询的结果正在被缓存时, 该查询实际不会执行, 查询结果将直接从缓存系统中读取。 + +

译者:苏小林 github

diff --git a/cn/queues.md b/cn/queues.md new file mode 100644 index 00000000000..c7ba9e21644 --- /dev/null +++ b/cn/queues.md @@ -0,0 +1,160 @@ +# 队列 + +- [配置](#configuration) +- [基础用法](#basic-usage) +- [队列闭包](#queueing-closures) +- [运行队列监听器](#running-the-queue-listener) +- [推送队列](#push-queues) + + +## 配置 + +Laravel的队列组件为许多队列服务提供了统一的API接口。队列服务让你可以异步处理一个耗时任务,比如延迟发送一封邮件,从而大大加快了应用的Web请求处理速度。 + +队列的设置信息储存在 `app/config/queue.php` 文件中。在这个文件中你可以找到所有目前支持的队列驱动的连接设置,包括[Beanstalkd](http://kr.github.com/beanstalkd)、[IronMQ](http://iron.io)、[Amazon SQS](http://aws.amazon.com/sqs)和同步处理(本地环境使用)驱动。 + +下面是相应队列驱动所需的依赖性包: + +- Beanstalkd: `pda/pheanstalk` +- Amazon SQS: `aws/aws-sdk-php` +- IronMQ: `iron-io/iron_mq` + + +## 基础用法 + +使用 `Queue::push` 方法推送一个新任务到队列中: + +**推送一个任务到队列中** + + Queue::push('SendEmail', array('message' => $message)); + +`push` 方法的第一个参数是用来处理任务的类的名称。第二个参数是一个数组,包含了需要传递给处理器的数据。一个任务处理器应该像这样定义: + +**定义一个任务处理器** + + class SendEmail { + + public function fire($job, $data) + { + // + } + + } + +注意,类唯一需要的方法是 `fire` 方法,它接受一个 `Job` 实例就像 `data` 数组一样发送到队列。 + +如果你希望任务调用 `fire` 以外的方法,你可以在推送任务时指定相应方法: + +**指定一个自定义处理器方法** + + Queue::push('SendEmail@send', array('message' => $message)); + +一旦你处理完了一个任务,必须从队列中将它删除,可以通过 `Job` 实例中的 `delete` 方法完成这项工作: + +**删除一个处理完的任务** + + public function fire($job, $data) + { + // Process the job... + + $job->delete(); + } + +如果你想要将一个任务放回队列,你可以使用 `release` 方法: + +**将一个任务放回队列** + + public function fire($job, $data) + { + // Process the job... + + $job->release(); + } + +你也可以指定几秒后将任务放回队列: + + $job->release(5); + +如果任务在处理时发生异常,它会被自动放回队列。你可以使用 `attempts` 方法获取尝试处理任务的次数: + +**获取尝试处理任务的次数** + + if ($job->attempts() > 3) + { + // + } + +你也可以获取任务的ID: + +**获取任务ID** + + $job->getJobId(); + + +## 队列闭包 + +你可以将一个闭包函数推送到队列中。这非常便于快速、简单地处理队列任务: + +**推送一个闭包函数到队列中** + + Queue::push(function($job) use ($id) + { + Account::delete($id); + + $job->delete(); + }); + +> **注意:** 当推送闭包到队列时,不能使用 `__DIR__` 和 `__FILE__` 常量。 + +当使用 Iron.io [推送队列](#push-queues) 时,你需要特别谨慎地处理队列闭包。接受队列信息的结尾应该带有Iron.io的验证令牌。例如,推送队列的结尾应该类似: `https://yourapp.com/queue/receive?token=SecretToken`。你也许需要在封装队列请求前检查一下应用的秘密令牌的值。 + + +## 运行队列监听器 + +Laravel包含了一个用于运行已推送到队列的任务的Artisan服务。可以使用 `queue:listen` 命令来运行这个功能: + +**开启队列监听器** + + php artisan queue:listen + +你也可以指定队列监听器需要使用的连接: + + php artisan queue:listen connection + +注意一旦任务启动,它会一直运行除非你手动停止它。可以使用进程监视工具(例如 [Supervisor](http://supervisord.org/))来确保队列监听器处于运行状态。 + +你也可以设置单个任务可以执行的最长时间(单位秒): + +**设置任务的超时参数** + + php artisan queue:listen --timeout=60 + +另外,你还可以指定新任务轮询之前所需要等待的秒数: + + php artisan queue:listen --sleep=5 + +如果只想处理队列的第一个任务,你可以使用 `queue:work` 命令: + +**处理队列的第一个任务** + + php artisan queue:work + + +## 推送队列 + +推送队列可以让你在没有守护进程和后台监听器的情况下使用 Laravel 4 强大的队列工具。当前,推送队列仅支持[Iron.io](http://iron.io)驱动。在开始前,创建一个 Iron.io 账户,然后将Iron的认证信息填入到 `app/config/queue.php` 配置文件中。 + +接下来,你可以使用 `queue:subscribe` Artisan命令注册一个用来接收新的推送队列任务的URL结尾。 + +**注册一个推送队列订阅** + + php artisan queue:subscribe queue_name http://foo.com/queue/receive + +现在,当你登录到Iron后台,你可以看见新的推送队列和订阅的URL。你可以为一个指定的队列订阅多个URL。接下来,为 `queue/receive` 结尾的URL创建一个路由,并且返回来自 `Queue::marshal` 方法的响应: + + Route::post('queue/receive', function() + { + return Queue::marshal(); + }); + +`marshal` 方法会自动执行相应的任务处理器类。想要处理推送队列上的任务,可以像处理一般的队列一样使用 `Queue::push` 方法。 diff --git a/cn/quick.md b/cn/quick.md new file mode 100644 index 00000000000..38d0b02c093 --- /dev/null +++ b/cn/quick.md @@ -0,0 +1,161 @@ +# Laravel 快速入门 + +- [安装](#installation) +- [路由](#routing) +- [创建视图](#creating-a-view) +- [创建迁移](#creating-a-migration) +- [Eloquent ORM](#eloquent-orm) +- [显示数据](#displaying-data) + + +## 安装 + +Laravel框架使用 [Composer](http://getcomposer.org) 执行安装和依赖管理。如果还没有安装的话,现在就开始 [安装 Composer](http://getcomposer.org/doc/00-intro.md) 吧。 + +安装Composer之后,你就可以通过命令行使用如下命令安装Laravel了: + + composer create-project laravel/laravel your-project-name + +或者,你可以从 [Github仓库](https://github.com/laravel/laravel/archive/master.zip) 下载。接下来,在 [安装Composer](http://getcomposer.org) 之后,在项目根目录下执行 `composer install` 命令。该命令将会下载以及安装框架的依赖组件。 + + +### 写入权限 + +安装完 Laravel ,你还需要为web服务器设置 `app/storage` 目录的写入权限。请参考 [安装](/docs/installation) 一节以获取更多关于配置方面的信息。 + + +### 目录结构 + +安装完框架后,你需要熟悉一下该项目的目录结构。`app` 文件夹包含了一些例如 `views` ,`controllers` 和 `models` 目录。 程序中大部分代码将要存放这些目录下。你也可以查看一下 `app/config` 文件夹里一些配置项目。 + + +## 路由 + +我们开始创建我们第一个路由。在 Laravel,简单路由的方法是闭包。打开 `app/routes.php` 文件加入如下代码: + + Route::get('users', function() + { + return 'Users!'; + }); + +现在,你在 web 浏览器输入 `/users`,你应该会看到 `Users!` 输出。真棒!已经创建了你第一个路由。 + +路由也可以赋予控制器类。例如: + + Route::get('users', 'UserController@getIndex'); + +该路由告知框架 `/users` 路由请求应该调用 `UserController` 类的 `getIndex` 方法。要查看更多关于路由控制器信息,查看 [控制器文档](/docs/controllers) 。 + + + +## 创建视图 + +接下来,我们要创建视图来显示我们用户数据。视图以HTML代码存放在 `app/views` 文件夹。我们将存放两个视图文件到该文件夹:`layout.blade.php` 和 `users.blade.php`。首先,让我们先创建 `layout.blade.php` 文件: + + + +

Laravel Quickstart

+ + @yield('content') + + + +接着, 我们创建 `users.blade.php` 视图: + + @extends('layout') + + @section('content') + Users! + @stop + +这里的语法可能让你感到陌生。因为我们使用的是 Laravel 模板系统:Blade。Blade 非常快,因为仅使用了少量的正则表达式来为你的模板编译成原始PHP代码。Blade提供强大的功能,例如模板继承,还有一些常用的PHP控制结构语法糖,例如 `if` 和 `for`。 查看 [Blade 文档](/docs/templates) 了解更多。 + +现在我们有了我们视图,让我们返回 `/users` 路由。我们用视图来替代返回 `Users!`: + + Route::get('users', function() + { + return View::make('users'); + }); + +漂亮!现在你成功创建了继承至layout的视图。接下来,让我们开始数据库层。 + + + +## 创建迁移 + +要创建表来保存我们数据,我们将使用 Laravel 迁移系统。迁移描述数据库的改变,这让分享给他们团队成员非常简单。 + +首先,我们配置数据库连接。你可以在 `app/config/database.php` 文件配置所有数据库连接信息。默认,Laravel 被配置为使用 SQLite,并且一个 SQLite 数据库存放在 `app/database` 目录。你可以将数据库配置文件的 `driver` 选项修改为 `mysql` 并且配置 `mysql` 连接信息。 + +接下来,要创建迁移,我们将使用 [Artisan CLI](/docs/artisan)。在项目根目录中,在终端中执行以下命令: + + php artisan migrate:make create_users_table + +然后,找到生成的迁移文件 `app/database/migrations` 目录。该文件包含了一个包含两个方法: `up` 和 `down` 的类。在 `up` 方法,你要指名数据库表的修改,在 `down` 方法中你只需要移除它。 + +让我们定义如下迁移: + + public function up() + { + Schema::create('users', function($table) + { + $table->increments('id'); + $table->string('email')->unique(); + $table->string('name'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::drop('users'); + } + +然后,我们在项目根目录中使用终端运行 `migrate` 命令来执行迁移: + + php artisan migrate + +如果你想回滚迁移,你可以执行 `migrate:rollback` 命令。现在我们已经有了数据库表,让我们让添加一些数据! + + +## Eloquent ORM + +Laravel 提供非常棒的 ORM:Eloquent。如果你使用过 Ruby on Rails 框架,你会发现 Eloquent 很相似,因为它遵循数据库交互的 ActiveRecord ORM 风格。 + +首先,让我们来定义个模型。ELoquent 模型可以用来查询相关数据表,以及表内的某一行。别着急,我们很快会谈及!模型通常存放在 `app/models` 目录。让我们在该目录定义个 `User.php` 模型,如: + + class User extends Eloquent {} + +注意我们并没有告诉 Eloquent 使用哪个表。Eloquent 有多种约定, 一个是使用模型的复数形式作为模型的数据库表。非常方便! + +使用你喜欢的数据库管理工具,插入几行数据到 `users` 表,我们将使用 Eloquent 取得它们并传递到视图中。 + +现在我们修改我们 `/users` 路由如下: + + Route::get('users', function() + { + $users = User::all(); + + return View::make('users')->with('users', $users); + }); + +让我们来看看该路由。首先,`User` 模型的 `all` 方法将会从 `users` 表中取得所有记录。接下来,我们通过 `with` 方法将这些记录传递到视图。`with` 方法接受一个键和一个值,那么该值就可以在视图中使用了。 + +激动啊。现在我们准备将用户显示在我们视图! + + +## 显示数据 + +现在我们视图中已经可以访问 `users` 类,我们可以如下显示它们: + + @extends('layout') + + @section('content') + @foreach($users as $user) +

{{ $user->name }}

+ @endforeach + @stop + +你可以发现没有找到 `echo` 语句。当使用 Blade 时,你可以使用两个花括号来输出数据。非常简单,你现在应该可以通过 `/users` 路由来查看到用户姓名作为响应输出。 + +这仅仅是开始。在本系列教程中,你已经了解了 Laravel 基础部分,但是还有更让人兴奋的东西要学。继续阅读该文档并且深入[Eloquent](/docs/eloquent)和[Blade](/docs/templates)这些强大的特性。或者你对[队列](/docs/queues) 和 [单元测试](/docs/testing) 感兴趣。或许是你想了解[IoC Container](/docs/ioc), 选择权在于你! \ No newline at end of file diff --git a/cn/redis.md b/cn/redis.md new file mode 100644 index 00000000000..370be91304a --- /dev/null +++ b/cn/redis.md @@ -0,0 +1,81 @@ +# Redis + +- [简介](#introduction) +- [配置](#configuration) +- [用法](#usage) +- [批量输送](#pipelining) + + +## 简介 + +[Redis](http://redis.io) 是一个开源、先进的键值对(key-value)存储容器。由于它允许使用[strings](http://redis.io/topics/data-types#strings)、[hashes](http://redis.io/topics/data-types#hashes)、[lists](http://redis.io/topics/data-types#lists)、[sets](http://redis.io/topics/data-types#sets)和[sorted sets](http://redis.io/topics/data-types#sorted-sets)作为键(key),因此它经常被称为数据结构服务器。 + +> **注意:** 如果你是通过PECL为PHP安装的Redis扩展模块,那么,你必须在 `app/config/app.php` 文件中对其别名进行重新命名。 + + +## 配置 + +当前应用的Redis配置信息存储在 **app/config/database.php** 文件中。在此文件中,你可以看到一个 **redis** 数组,次数组中存储了当前使用Redis服务器信息。 + + 'redis' => array( + + 'cluster' => true, + + 'default' => array('host' => '127.0.0.1', 'port' => 6379), + + ), + +默认的服务器配置应该可以满足开发过程中的需求了。当然,你可以根据你的开发环境任意修改此配置数组。只需简单的为每个Redis服务器命名并制定此服务器所占用的host和port。 + +`cluster` 参数是告诉Laravel中的Redis客户端对所有的Redis节点执行客户端侧的分片(sharding),这就赋予你将创建一个节点池,并使用大量的RAM的能力。然而,客户端的分片机制不能够处理失效切换,因此,这种方式主要用来访问其它主数据容器中存放的缓存数据。 + + +## 用法 + +调用 `Redis::connection` 方法可以获取一个Redis类的实例: + + $redis = Redis::connection(); + +上面的代码可以获取一个到默认Redis服务器的连接。如果你没有使用服务器集群的话,你可以将服务器名称作为参数传递给 `connection` 方法,这样就可以获取Redis配置信息中的某个指定的服务器连接了: + + $redis = Redis::connection('other'); + +一旦获取到Redis类的实例,我们就可以向其发送任何[Redis命令](http://redis.io/commands) 了。Laravel使用一些魔术方法向Redis服务器传送命令: + + $redis->set('name', 'Taylor'); + + $name = $redis->get('name'); + + $values = $redis->lrange('names', 5, 10); + +注意,向Redis命令传递的参数以同样类似的方式传递给这些魔术方法。当然,如果你不用这些魔术方法,还可以使用 `command` 方法向服务器传送Redis命令: + + $values = $redis->command('lrange', array(5, 10)); + +当命令只是在默认连接上执行时,仅需使用 `Redis` 类中定义的静态方法即可: + + Redis::set('name', 'Taylor'); + + $name = Redis::get('name'); + + $values = Redis::lrange('names', 5, 10); + +> **注意:** Redis [cache](/docs/cache) 和 [session](/docs/session) 驱动都已经包含在Laravel中了。 + + +## 批量输送 + +批量输送(Pipelining)应当用于需要在一个操作中发送许多命令的场景。立即使用 `pipeline` 命令动手试试吧: + +**批量输送命令到服务器** + + Redis::pipeline(function($pipe) + { + for ($i = 0; $i < 1000; $i++) + { + $pipe->set("key:$i", $i); + } + }); + + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) \ No newline at end of file diff --git a/cn/requests.md b/cn/requests.md new file mode 100644 index 00000000000..9bf7d146d22 --- /dev/null +++ b/cn/requests.md @@ -0,0 +1,182 @@ +# 请求与输入 + +- [基本输入](#basic-input) +- [Cookies](#cookies) +- [用户提交信息持久化](#old-input) +- [文件上传](#files) +- [用户请求的详细信息](#request-information) + + +## 基本输入 + +Laravel使用一种简单的方式来访问用户提交的信息。 你可以用统一的方式来访问用户提交的信息,而不用为用户提交信息的方式操心。 + +**获取一个用户提交的值** + + $name = Input::get('name'); + +**为用户提交信息指定一个的默认返回值(如果用户未提交)** + + $name = Input::get('name', 'Sally'); + +**判断指定的提交信息是否存在** + + if (Input::has('name')) + { + // + } + +**获取所有用户提交的信息** + + $input = Input::all(); + +**获取指定的信息,或者获取排除指定几个提交项之外的所有提交信息** + + $input = Input::only('username', 'password'); + + $input = Input::except('credit_card'); + +如果提交的表单含有 "数组" 形式的输入,可以使用点符号访问数组: + + $input = Input::get('products.0.name'); + +> **注意:** 有一些javascript库,比如 Backbone 会以json格式提交信息。 通过 `Input::get` 来获取信息,使用上无差别。 + + +## Cookies + +Laravel会加密所有已创建的cookie信息,并附加上授权码,当客户端擅自修改cookie信息时,该cookie将被废弃,从而保证安全性。 + +**获取一个指定的cookie值** + + $value = Cookie::get('name'); + +**添加一个新的cookie键值对** + + $response = Response::make('Hello World'); + + $response->withCookie(Cookie::make('name', 'value', $minutes)); + +**Queueing A Cookie For The Next Response** + +If you would like to set a cookie before a response has been created, use the `Cookie::queue()` method. The cookie will automatically be attached to the final response from your application. + + Cookie::queue($name, $value, $minutes); + +**创建一个永不过期的cookie键值对** + + $cookie = Cookie::forever('name', 'value'); + + +## 用户提交信息持久化 + +有时可能需要在多个用户请求之间持久化用户提交的信息。 比如,当用户提交的信息验证失败重新返回提交信息页面时还原用户的输入。 + +**将用户提交的信息存入Session** + + Input::flash(); + +**把指定的用户提交的信息存入Session** + + Input::flashOnly('username', 'email'); + + Input::flashExcept('password'); + +如果你需要关联持久用户提交的信息的操作和重定向操作,可以使用如下的链式调用的方法: + + return Redirect::to('form')->withInput(); + + return Redirect::to('form')->withInput(Input::except('password')); + +> **注意:** 如果你想持久化其它的信息,请参考 [Session](/docs/session) 类. + +**获取已持久化的用户提交的信息** + + Input::old('username'); + + +## 文件上传 + +**获取用户上传的文件** + + $file = Input::file('photo'); + +**判断指定文件是否已经被上传** + + if (Input::hasFile('photo')) + { + // + } + +`file` 方法返回了一个 `Symfony\Component\HttpFoundation\File\UploadedFile` 类的实例, 该类继承自PHP的 `SplFileInfo` 类,并提供了大量操作该用户上传的文件的方法。 + +**移动一个已上传的文件** + + Input::file('photo')->move($destinationPath); + + Input::file('photo')->move($destinationPath, $fileName); + +**获取一个已上传的文件在服务器的真实路径** + + $path = Input::file('photo')->getRealPath(); + +**获取一个已上传的文件的大小** + + $size = Input::file('photo')->getSize(); + +**获取一个已上传的文件的 MIME 类型** + + $mime = Input::file('photo')->getMimeType(); + + +## 用户请求的详细信息 + +`Request` 类提供了许多 方法 用于获取关于请求的详细信息,该类继承自 `Symfony\Component\HttpFoundation\Request` 类。 下面提供了几个具有代表性的方法: + +**获取请求URI** + + $uri = Request::path(); + +**判断请求路径是否符合指定模式** + + if (Request::is('admin/*')) + { + // + } + +**获取请求URL** + + $url = Request::url(); + +**获取请求URI信息** + + $segment = Request::segment(1); + +**获取请求头里的Content-Type信息** + + $value = Request::header('Content-Type'); + +**获取 $_SERVER 数组里指定的值** + + $value = Request::server('PATH_INFO'); + +**判断请求是否是通过 HTTPS 连接发送过来的** + + if (Request::secure()) + { + // + } + +**判断是否是使用ajax请求** + + if (Request::ajax()) + { + // + } + +**检测是否是任何可能的 JSON 类型的请求** + + if (Request::ajax() or Request::isJson() or Request::wantsJson()) + { + // + } \ No newline at end of file diff --git a/cn/responses.md b/cn/responses.md new file mode 100644 index 00000000000..5cbabc2748f --- /dev/null +++ b/cn/responses.md @@ -0,0 +1,183 @@ +# 视图 & Response + +- [基本Response](#basic-responses) +- [重定向](#redirects) +- [视图](#views) +- [视图合成](#view-composers) +- [特殊Response](#special-responses) + + +## 基本Response + +**从路由中返回字符串** + + Route::get('/', function() + { + return 'Hello World'; + }); + +**创建自定义Response** + +`Response`类继承自`Symfony\Component\HttpFoundation\Response`类,提供了多种方法用于构建HTTP Response。 + + $response = Response::make($contents, $statusCode); + + $response->header('Content-Type', $value); + + return $response; + +**在Response中添加Cookie** + + $cookie = Cookie::make('name', 'value'); + + return Response::make($content)->withCookie($cookie); + + +## 重定向 + +**返回一个重定向** + + return Redirect::to('user/login'); + +**返回一个重定向至命名路由** + + return Redirect::route('login'); + +**返回一个重定向至带有参数的命名路由** + + return Redirect::route('profile', array(1)); + +**返回一个重定向至带有命名参数的命名路由** + + return Redirect::route('profile', array('user' => 1)); + +**返回一个重定向至控制器Action** + + return Redirect::action('HomeController@index'); + +**返回一个重定向至控制器Action并带有参数** + + return Redirect::action('UserController@profile', array(1)); + +**返回一个重定向至控制器Action并带有命名参数** + + return Redirect::action('UserController@profile', array('user' => 1)); + + +## 视图 + +视图通常包含应用中的HTML代码,为分离表现层与控制器和业务逻辑提供了便利。视图存放于`app/views`目录。 + +一个简单视图案例: + + + + + +

Hello,

+ + + +通过如下方法来返回该视图到浏览器: + + Route::get('/', function() + { + return View::make('greeting', array('name' => 'Taylor')); + }); + +传递给`View::make`方法的第二个参数是一个数组,它将被传递给视图。 + +**传递数据给视图** + + $view = View::make('greeting', $data); + + $view = View::make('greeting')->with('name', 'Steve'); + +在上面的案例中,`$name`变量在视图内是可以访问的,其值为`Steve`。 + +你还可以在所有视图同共享同一数据: + + View::share('name', 'Steve'); + +**向视图传递子视图** + +或许你可能想将一个视图放入到另一个视图中。例如,将存放在`app/views/child/view.php`文件中的子视图传递给另一视图,如下: + + $view = View::make('greeting')->nest('child', 'child.view'); + + $view = View::make('greeting')->nest('child', 'child.view', $data); + +在父视图就可以输出该子视图了: + + + +

Hello!

+ + + + + +## 视图合成器 + +视图合成器可以是回调函数或者类方法,它们在创建视图时被调用。如果你想在应用程序中,每次创建视图时都为其绑定一些数据,使用视图合成器可以将代码组织到一个地方。因此,视图合成器就好像是 “视图模型”或者是“主持人”。 + +**定义一个视图合成器** + + View::composer('profile', function($view) + { + $view->with('count', User::count()); + }); + +现在,每次创建`profile`视图时,`count`都会被绑定到视图中。 + +你也可以为多个视图同时绑定一个视图合成器: + + View::composer(array('profile','dashboard'), function($view) + { + $view->with('count', User::count()); + }); + +如果你更喜欢使用基于类的视图合成器,[IoC container](/docs/ioc)可以提供更多便利,如下所示: + + View::composer('profile', 'ProfileComposer'); + +视图合成器类定义如下: + + class ProfileComposer { + + public function compose($view) + { + $view->with('count', User::count()); + } + + } + +注意,没有规定视图合成器类存放在哪里。因此,你可以任意存放,只要能在`composer.json`文件中指定位置并自动加载即可。 + +### View Creators + +View **creators** work almost exactly like view composers; however, they are fired immediately when the view is instantiated. To register a view creator, simple use the `creator` method: + + View::creator('profile', function($view) + { + $view->with('count', User::count()); + }); + + +## 特殊Response + +**创建一个JSON Response** + + return Response::json(array('name' => 'Steve', 'state' => 'CA')); + +**创建一个JSONP Response** + + return Response::json(array('name' => 'Steve', 'state' => 'CA'))->setCallback(Input::get('callback')); + +**创建一个文件下载Response** + + return Response::download($pathToFile); + + return Response::download($pathToFile, $status, $headers); + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) diff --git a/cn/routing.md b/cn/routing.md new file mode 100644 index 00000000000..5fde70d9855 --- /dev/null +++ b/cn/routing.md @@ -0,0 +1,322 @@ +# 路由 + +- [基本路由](#basic-routing) +- [路由参数](#route-parameters) +- [路由过滤器](#route-filters) +- [命名路由](#named-routes) +- [路由组](#route-groups) +- [子域名路由](#sub-domain-routing) +- [路由前缀](#route-prefixing) +- [路由与模型绑定](#route-model-binding) +- [抛出 404 错误](#throwing-404-errors) +- [控制器路由](#routing-to-controllers) + + +## 基本路由 + +应用中的大多数路都会定义在 `app/routes.php` 文件中。最简单的Laravel路由由URI和闭包回调函数组成。 + +**基本 GET 路由** + + Route::get('/', function() + { + return 'Hello World'; + }); + +**基本 POST 路由** + + Route::post('foo/bar', function() + { + return 'Hello World'; + }); + +**注册一个可以响应任何HTTP动作的路由** + + Route::any('foo', function() + { + return 'Hello World'; + }); + +**仅支持HTTPS的路由** + + Route::get('foo', array('https', function() + { + return 'Must be over HTTPS'; + })); + +实际开发中经常需要根据路由生成 URL,`URL::to`方法就可以满足此需求: + + $url = URL::to('foo'); + + +## 路由参数 + + Route::get('user/{id}', function($id) + { + return 'User '.$id; + }); + +**可选路由参数** + + Route::get('user/{name?}', function($name = null) + { + return $name; + }); + +**带有默认值的可选路由参数** + + Route::get('user/{name?}', function($name = 'John') + { + return $name; + }); + +**用正则表达式限定的路由参数** + + Route::get('user/{name}', function($name) + { + // + }) + ->where('name', '[A-Za-z]+'); + + Route::get('user/{id}', function($id) + { + // + }) + ->where('id', '[0-9]+'); + +当然,必要的时候你还可以传递一个包含参数限定的数组作为参数: + + Route::get('user/{id}/{name}', function($id, $name) + { + // + }) + ->where(array('id' => '[0-9]+', 'name' => '[a-z]+')) + +If you would like a route parameter to always be constrained by a given regular expression, you may use the `pattern` method: + + Route::pattern('id', '[0-9]+'); + + Route::get('user/{id}', function($id) + { + // Only called if {id} is numeric. + }); + + +## 路由过滤器 + +路由过滤器提供了非常方便的方法来限制对应用程序中某些功能访问,例如对于需要验证才能访问的功能就非常有用。Laravel框架自身已经提供了一些过滤器,包括 `auth`过滤器、`auth.basic`过滤器、`guest`过滤器以及`csrf`过滤器。这些过滤器都定义在`app/filter.php`文件中。 + + +**定义一个路由过滤器** + + Route::filter('old', function() + { + if (Input::get('age') < 200) + { + return Redirect::to('home'); + } + }); + +如果从路由过滤器中返回了一个response,那么该response将被认为对应的是此次request,路由将不会被执行,并且,此路由中所有定义在此过滤器之后的代码也都不会被执行。 + +**为路由绑定过滤器** + + Route::get('user', array('before' => 'old', function() + { + return 'You are over 200 years old!'; + })); + +**Attaching A Filter To A Controller Action** + + Route::get('user', array('before' => 'old', 'uses' => 'UserController@showProfile')); + +**为路由绑定多个过滤器** + + Route::get('user', array('before' => 'auth|old', function() + { + return 'You are authenticated and over 200 years old!'; + })); + +**指定过滤器参数** + + Route::filter('age', function($route, $request, $value) + { + // + }); + + Route::get('user', array('before' => 'age:200', function() + { + return 'Hello World'; + })); + +所有其后的过滤器将接收到 `$response`作为第三个参数: + + Route::filter('log', function($route, $request, $response, $value) + { + // + }); + +**基于模式的过滤器** + +你也可以指针对URI为一组路由指定过滤器。 + + Route::filter('admin', function() + { + // + }); + + Route::when('admin/*', 'admin'); + +上述案例中,`admin`过滤器将会应用到所有以`admin/`开头的路由中。星号是通配符,将会匹配任意多个字符的组合。 + +还可以针对HTTP动作限定模式过滤器: + + Route::when('admin/*', 'admin', array('post')); + +**过滤器类** + +过滤器的高级用法中,还可以使用类来替代闭包函数。由于过滤器类是通过[IoC container](/docs/ioc)实现解析的,所有,你可以在这些过滤器中利用依赖注入(dependency injection)的方法实现更好的测试能力。 + +**定义一个过滤器类** + + class FooFilter { + + public function filter() + { + // Filter logic... + } + + } + +**注册过滤器类** + + Route::filter('foo', 'FooFilter'); + + +## 命名路由 + +重定向和生成URL时,使用命名路由会更方便。你可以为路由指定一个名字,如下所示: + + Route::get('user/profile', array('as' => 'profile', function() + { + // + })); + +还可以为 controller action指定路由名称: + + Route::get('user/profile', array('as' => 'profile', 'uses' => 'UserController@showProfile')); + +现在,你可以使用路由名称来创建URL和重定向: + + $url = URL::route('profile'); + + $redirect = Redirect::route('profile'); + +可以使用`currentRouteName`方法来获取当前运行的路由名称: + + $name = Route::currentRouteName(); + + +## 路由组 + +有时你可能需要为一组路由应用过滤器。使用路由组就可以避免单独为每个路由指定过滤器了: + + Route::group(array('before' => 'auth'), function() + { + Route::get('/', function() + { + // Has Auth Filter + }); + + Route::get('user/profile', function() + { + // Has Auth Filter + }); + }); + + +## 子域名路由 + +Laravel中的路由功能还支持通配符子域名,你可以在域名中指定通配符参数: + +**注册子域名路由** + + Route::group(array('domain' => '{account}.myapp.com'), function() + { + + Route::get('user/{id}', function($account, $id) + { + // + }); + + }); + +## 路由前缀 + +可以通过`prefix`属性为组路由设置前缀: + +**为路由组设置前缀** + + Route::group(array('prefix' => 'admin'), function() + { + + Route::get('user', function() + { + // + }); + + }); + + +## 路由与模型绑定 + +模型绑定,为在路由中注入模型实例提供了便捷的途径。例如,你可以向路由中注入匹配用户ID的整个模型实例,而不是仅仅注入用户ID。首先,使用 `Route::model` 方法指定要被注入的模型: + +**将参一个模型** + + Route::model('user', 'User'); + +然后,定义一个包含`{user}`参数的路由: + + Route::get('profile/{user}', function(User $user) + { + // + }); + +由于我们已将`{user}`参数绑定到了`User`模型,因此可以向路由中注入一个`User`实例。例如,对`profile/1`的访问将会把ID为1的`User`实例注入到路由中。 + +> **注意:** 如果在数据库中无法匹配到对应的模型实例,404错误将被抛出。 + +如果你希望自定义"not found"行为,可以通过传递一个闭包函数作为 `model` 方法的第三个参数: + + Route::model('user', 'User', function() + { + throw new NotFoundException; + }); + +如果你想自己实现路由参数的解析,只需使用`Route::bind`方法即可: + + Route::bind('user', function($value, $route) + { + return User::where('name', $value)->first(); + }); + + +## 抛出 404 错误 + +有两种从路由中手动触发404错误的方法。首先,你可以使用`App::abort`方法: + + App::abort(404); + +其次,你可以抛出`Symfony\Component\HttpKernel\Exception\NotFoundHttpException`异常。 + +更多关于处理404异常以及错误发生时自定义response的信息可以查看[错误](/docs/errors#handling-404-errors)文档。 + + +## 控制器路由 + +Laravel不光提供了利用闭包函数处理路由的功能,还可以路由到控制器,甚至支持创建 [resource controllers](/docs/controllers#resource-controllers)。 + +参见文档 [Controllers](/docs/controllers) 以获取更多信息。 + +译者:王赛 [(Bootstrap中文网)](http://www.bootcss.com) diff --git a/cn/schema.md b/cn/schema.md new file mode 100644 index 00000000000..5055df9347f --- /dev/null +++ b/cn/schema.md @@ -0,0 +1,206 @@ +# 结构生成器 + +- [简介](#introduction) +- [创建和删除表](#creating-and-dropping-tables) +- [添加字段](#adding-columns) +- [重命名字段](#renaming-columns) +- [删除字段](#dropping-columns) +- [检查存在性](#checking-existence) +- [添加索引](#adding-indexes) +- [外键](#foreign-keys) +- [删除索引](#dropping-indexes) +- [存储引擎](#storage-engines) + + +## 简介 + +Laravel 的 `Schema` 类提供了一种与数据库无关的方式维护表。它和 Laravel 所支持的所有数据库都能很好的工作,并且提供了统一的接口。 + + +## 创建和删除表 + +使用 `Schema::create` 创建一个数据库的表: + + Schema::create('users', function($table) + { + $table->increments('id'); + }); + +传递给 `create` 函数的第一个参数是表的名字,第二个参数是一个闭包,将接受一个 `Blueprint` 对象用于定义新的表。 + +使用 `rename` 函数重命名一个已存在的表: + + Schema::rename($from, $to); + +使用 `Schema::connection` 函数指定结构操作所使用的数据库连接: + + Schema::connection('foo')->create('users', function($table) + { + $table->increments('id'): + }); + +使用 `Schema::drop` 函数删除一个表: + + Schema::drop('users'); + + Schema::dropIfExists('users'); + + +## 添加字段 + +使用 `Schema::table` 函数更新一个已存在的表: + + Schema::table('users', function($table) + { + $table->string('email'); + }); + + +表生成器包含一系列的字段类型用于构建表: + +命令 | 描述 +------------- | ------------- +`$table->increments('id');` | 自动增长的 ID (主键). +`$table->bigIncrements('id');` | 类似"big integer"类型的自动增长的 ID. +`$table->string('email');` | VARCHAR 类型 +`$table->string('name', 100);` | 带长度的 VARCHAR 类型 +`$table->integer('votes');` | INTEGER 类型 +`$table->bigInteger('votes');` | BIGINT 类型 +`$table->smallInteger('votes');` | SMALLINT 类型 +`$table->float('amount');` | FLOAT 类型 +`$table->double('column', 15, 8);` | 设置了精度的DOUBLE类型 +`$table->decimal('amount', 5, 2);` | 带精度和小数的 DECIMAL +`$table->boolean('confirmed');` | BOOLEAN 类型 +`$table->date('created_at');` | DATE 类型 +`$table->dateTime('created_at');` | DATETIME 类型 +`$table->time('sunrise');` | TIME 类型 +`$table->timestamp('added_on');` | TIMESTAMP 类型 +`$table->timestamps();` | 添加 **created\_at** 和 **updated\_at** 列 +`$table->softDeletes();` | 添加 **deleted\_at** 列用于软删除 +`$table->text('description');` | TEXT 类型 +`$table->longtext('description');` | LONGTEXT 类型 +`$table->binary('data');` | BLOB 类型 +`$table->enum('choices', array('foo', 'bar'));` | ENUM 类型 +`->nullable()` | 指明该字段允许 NULL 值 +`->default($value)` | 为字段声明一个默认值 +`->unsigned()` | 设置 INTEGER 为无符号 + +如果你使用 MySQL 数据库,您可以使用 `after` 函数指明字段的顺序: + +**在 MySQL 中使用 After** + + $table->string('name')->after('email'); + + +## 重命名字段 + +使用 `renameColumn` 函数重命名一个字段: + +**重命名一个字段** + + Schema::table('users', function($table) + { + $table->renameColumn('from', 'to'); + }); + +> **注意:** 不支持重命名 `enum` 字段类型. + + +## 删除字段 + +**从表中删除一个字段** + + Schema::table('users', function($table) + { + $table->dropColumn('votes'); + }); + +**从表中删除多个字段** + + Schema::table('users', function($table) + { + $table->dropColumn('votes', 'avatar', 'location'); + }); + + +## 检查存在性 + +您可以使用 `hasTable` 和 `hasColumn` 检查一个表或一个字段是否存在: + +**检查表是否存在** + + if (Schema::hasTable('users')) + { + // + } + +**检查字段是否存在** + + if (Schema::hasColumn('users', 'email')) + { + // + } + + +## 添加索引 + +结构生成器支持多种类型的索引,有两种方法可以添加它们。首先,您可以在字段定义后链式的定义它们,或者独立的添加它们: + +**链式创建一个字段和索引** + + $table->string('email')->unique(); + +或者,您可以选择在不同的行添加索引。下面是全部支持的索引类型: + +命令 | 描述 +------------- | ------------- +`$table->primary('id');` | 添加一个主键 +`$table->primary(array('first', 'last'));` | 添加组合键 +`$table->unique('email');` | 添加唯一键 +`$table->index('state');` | 添加一个索引 + + +## 外键 + +Laravel 也支持向表中添加外键约束: + +**向表中添加外键** + + $table->foreign('user_id')->references('id')->on('users'); + +在这个例子中,我们指明 `user_id` 字段参照 `users` 表中的 `id` 字段。 + +您也可以指明 "on delete" 以及 "on update" 行为选项: + + $table->foreign('user_id') + ->references('id')->on('users') + ->onDelete('cascade'); + +可以使用 `dropForeign` 函数删除一个外键。像其他索引一样,一个相似的命名惯例被使用于外键的命名: + + $table->dropForeign('posts_user_id_foreign'); + +> **注意:** 当创建一个参照递增整数类型的外键的时候,记得把外键字段的类型定义为无符号。 + + +## 删除索引 + +为了删除索引,必须指明索引的名字。Laravel 默认为索引分配了一个合理的名字。通过连接表明、索引的字段名以及索引类型的形式。这里是一些例子: + +命令 | 描述 +------------- | ------------- +`$table->dropPrimary('users_id_primary');` | 从 "users" 表中删除一个主键 +`$table->dropUnique('users_email_unique');` | 从 "users" 表中删除一个唯一键 +`$table->dropIndex('geo_state_index');` | 从 "geo" 表中删除一个索引 + + +## 存储引擎 + +通过在结构生成器设置 `engine` 属性为表设置存储引擎: + + Schema::create('users', function($table) + { + $table->engine = 'InnoDB'; + + $table->string('email'); + }); diff --git a/cn/security.md b/cn/security.md new file mode 100644 index 00000000000..154577c5ee6 --- /dev/null +++ b/cn/security.md @@ -0,0 +1,313 @@ +# 安全 + +- [配置](#configuration) +- [保存密码](#storing-passwords) +- [验证用户](#authenticating-users) +- [手动登陆用户](#manually) +- [保护路由](#protecting-routes) +- [HTTP基本验证](#http-basic-authentication) +- [密码提示和重置](#password-reminders-and-reset) +- [加密](#encryption) + + +## 配置 + +Laravel 旨在让验证实现简单。事实上,大部分都已经配置好了。验证配置文件位于 `app/config/auth.php`,该文件包含了一些文档说明非常齐全的配置选项,通过它们可以调整用户验证的具体形式。 + +一般情况下,Laravel在`app/models`目录下包含有一个`User` 模型,通常被作为默认的Eloquent验证驱动所使用。请注意在构建该数据库结构模型时确保密码字段至少能容纳60个字符。 + +如果你的应用中不使用 Eloquent,你可以使用 Laravel 查询构造器来使用 `数据库` 验证驱动。 + + +## 保存密码 + +Laravel `Hash` 类提供了可靠的Bcrypt散列算法: + +**使用Bcrypt散列密码** + + $password = Hash::make('secret'); + +**验证散列密码** + + if (Hash::check('secret', $hashedPassword)) + { + // 密码匹配... + } + +**检查密码是否需要重新散列** + + if (Hash::needsRehash($hashed)) + { + $hashed = Hash::make('secret'); + } + + +## 用户验证 + +要在应用程序中登陆用户,使用 `Auth::attempt` 方法。 + + if (Auth::attempt(array('email' => $email, 'password' => $password))) + { + return Redirect::intended('dashboard'); + } + +注意 `email` 不是必需选项,这里仅作为示例。你应该使用任意数据库字段名来作为"用户名"。 `Redirect::intended` 方法会将用户请求重定向至被用户验证过滤器拦截之前用户试图访问URL中去。可以给该方法提供个回退URI,用于在访问目的无效时,重定向到该URI。 + +在调用 `attempt` 方法时,`auth.attempt` [事件](/docs/events) 将会触发。如果验证成功以及用户登陆了,`auth.login` 事件也会被触发。 + +要在应用程序中判断用户是否已经登陆,可以使用 `check` 方法: + +**判断用户是否已经验证** + + if (Auth::check()) + { + // 用户已经登陆... + } + +如果你想在应用中提供“记住我”功能,你可以传递true作为第二个参数传递给attempt方法,应用程序将会无期限地保持用户验证状态(除非手动退出): + +**验证用户并且“记住”她们** + + if (Auth::attempt(array('email' => $email, 'password' => $password), true)) + { + // 用户状态永久保存... + } + +**注意:** 如果 `attempt` 方法返回 `true`, 用户就已经成功登陆应用程序了。 + +你也可以在用户验证查询中加入其它条件: + +**指定其它条件验证用户** + + if (Auth::attempt(array('email' => $email, 'password' => $password, 'active' => 1))) + { + // 用户激动状态,没有暂停,并且存在。 + } + +一旦用户验证通过了,就可以查看 User 模型/记录: + +**查看登陆用户** + + $email = Auth::user()->email; + +要简单使用用户ID来登陆应用程序,可以使用 `loginUsingId` 方法: + + Auth::loginUsingId(1); +`validate` 方法可以在不登录应用程序的情况下来验证用户的信息: + +**验证用户但不登陆** + + if (Auth::validate($credentials)) + { + // + } + +也可以使用 `once` 方法将一个用户登录到系统中做一个单次请求。这样的方式不会使用Session或Cookie来进行状态保持。 + +**登陆用户做单次请求** + + if (Auth::once($credentials)) + { + // + } + +**应用程序中注销用户登陆状态** + + Auth::logout(); + + +## 手动登陆用户 + +如果想登陆一个存在的用户实例,只需要简单使用`login`方法并传入该实例即可: + + $user = User::find(1); + + Auth::login($user); + +这与使用 `attempt` 方法验证用户登陆是一样的。 + + + +## 保护路由 + +路由过滤器可以保证只有通过了验证的用户才能访问指定路由。Laravel 默认提供了 `auth` 过滤器,它定义在 `app/filters.php`文件。 + +**保护路由** + + Route::get('profile', array('before' => 'auth', function() + { + // 只有验证用户可以访问…… + })); + +### 跨站请求伪造(CSRF)保护 + +Laravel 提供便捷的方法来避免应用程序受到跨站伪造请求的攻击。 + +**表单中插入 CSRF Token** + + + +**提供表单时验证 CSRF Token** + + Route::post('register', array('before' => 'csrf', function() + { + return 'You gave a valid CSRF token!'; + })); + + +## HTTP 基本验证 +HTTP基本验证可以在不设定专门的登录页面的情况下便捷的对应用程序中的用户进行验证。要使用该功能,在路由中附加 `auth.filter` 过滤器: + +**HTTP 基本验证来保护路由** + + Route::get('profile', array('before' => 'auth.basic', function() + { + // 只有验证用户可以访问…… + })); + +默认情况下,`basic` 过滤器验证时会使用用户记录里的 `email` 字段。如果你想使用其他字段,你可以通过传递该字段名字给 `basic` 方法作为第一个参数: + + return Auth::basic('username'); + +你也可以使用 HTTP 基本验证时不将用户cookie标识写入 session,对 API 验证极其有用。要实现该功能,请定义一个返回 `onceBasic` 方法的过滤器: + +**设置无状态的 HTTP 基本过滤器** + + Route::filter('basic.once', function() + { + return Auth::onceBasic(); + }); + +如果你正在使用PHP FastCGI,那么HTTP 基本验证在默认情况下是不会正常工作的。应该将下面的代码加入到`.htaccess`文件中: + + RewriteCond %{HTTP:Authorization} ^(.+)$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + +## 密码提示和重置 + +### 发送密码提示 + +大多web应用程序提供了让用户重置密码功能。与其让你在每个应用程序中重复去实现该功能,Laravel 提供非常方便的方法来发送密码提示以及执行密码重置。在开始前,请确认你的 `User` 模型实现了 `Illuminate\Auth\Reminders\RemindableInterface` 接口。 当然,框架中的默认的 `User` 模型已经实现了该接口。 + +**实现 RemindableInterface 接口** + + class User extends Eloquent implements RemindableInterface { + + public function getReminderEmail() + { + return $this->email; + } + + } + +接下来,要创建一个表来存储密码重置 token。要为该表生成迁移,简单执行 Artisan 命令 `auth::reminders`: + +**为提示表建立迁移** + + php artisan auth:reminders + + php artisan migrate + +要发送密码提示,我们可以使用 `Password::remind` 方法: + +**发送密码提示** + + Route::post('password/remind', function() + { + $credentials = array('email' => Input::get('email')); + + return Password::remind($credentials); + }); + +请注意,传递给 `remind` 方法的参数和 `Auth::attempt` 方法中的是相似的。该方法将会找到这个用户并且通过邮箱发送密码重置链接。e-mail视图中会传入一个用来构造重置密码表单链接的 `token` 变量。`user` 对象也会传入该视图中。 + +> **注意:** 你也可以通过改变 `auth.reminder.email` 配置项来改变邮件消息视图。当然,Laravel已经提供了个默认视图。. + +你可以通过传递一个 Closure 作为 `remind` 方法的第二个参数来改变发送给用户的邮件实例: + + return Password::remind($credentials, function($message, $user) + { + $message->subject('Your Password Reminder'); + }); + +你也可能已经注意到我们我们直接从路由中返回 `remind` 方法的结果。默认情况下,`remind` 方法将会返回一个指向当前URI的`Redirect` 重定向. 当在尝试重置密码而出现错误时, 一个 `error` 变量将会 flash 到session中,还有个 `reason` 变量,可以从 `reminders` 语言文件中取得文本。如果密码重置成功,`success` 变量会 flash 到session。所以,你的密码重置表单视图类似如下: + + @if (Session::has('error')) + {{ trans(Session::get('reason')) }} + @elseif (Session::has('success')) + An e-mail with the password reset has been sent. + @endif + + + + +### 重置密码 + +一旦用户从提示邮件中点击了重置链接,她们将被重定向到一个包含 `token` 隐藏域、一个 `password` 以及 `password_confirmation` 域的表单。以下是一个重置表单路由示例: + + Route::get('password/reset/{token}', function($token) + { + return View::make('auth.reset')->with('token', $token); + }); + +重置表单类似这样: + + @if (Session::has('error')) + {{ trans(Session::get('reason')) }} + @endif + + + + + + +再次强调,我们使用 `Session` 来显示任何重置密码时框架检测出的错误。接下来,我们可以定义一个 `POST` 路由来处理重置: + + Route::post('password/reset/{token}', function() + { + $credentials = array( + 'email' => Input::get('email'), + 'password' => Input::get('password'), + 'password_confirmation' => Input::get('password_confirmation') + ); + + return Password::reset($credentials, function($user, $password) + { + $user->password = Hash::make($password); + + $user->save(); + + return Redirect::to('home'); + }); + }); + +如果密码重置成功,`User` 实例以及密码将会传递到 Closure,允许你实际执行保存操作。然后,你可以在Closure闭包中返回一个`Redirect` 重定向或者任何其他类型的应答。这些应答将会由这个闭包外面的reset方法返回。请注意, `reset` 方法会自动检查请求中 `token` 和 用户信息的有效性,并且匹配密码。 + +默认情况下,密码重置tokens的有效期是一个小时。你可以通过修改`app/config/auth.php` 文件中的`reminder.expire`选项来更改有效期。 + +同 `remind` 方法一样, 如果在重置密码时发生错误, `reset` 方法将会 `Redirect` 到当前URI,并带有 `error` 和 `reason` 变量。 + + +## 加密 + +Laravel通过 mcrypt PHP 的扩展提供了强大的AES-256 加密组件: + +**加密** + + $encrypted = Crypt::encrypt('secret'); + +> **注意:** 确认在 `app/config/app.php` 文件设置了一个32随机字符给 `key` 项。否则,加密的值是不安全的。 + +**解密** + + $decrypted = Crypt::decrypt($encryptedValue); + +你也可以设置在encrypter中使用的 cipher 和 mode: + + +**设置 Cipher 和 Mode** + + Crypt::setMode('crt'); + + Crypt::setCipher($cipher); diff --git a/cn/session.md b/cn/session.md new file mode 100644 index 00000000000..d349a7c92b4 --- /dev/null +++ b/cn/session.md @@ -0,0 +1,84 @@ +# Session + +- [配置](#configuration) +- [Session 使用指南](#session-usage) +- [闪存数据](#flash-data) +- [数据库 Sessions](#database-sessions) + + +## 配置 + +因为HTTP协议本身是无状态的,session提供了一种保存用户请求信息的途径。Laravel框架可以使用多种session后端驱动,并且提供了清晰、统一的API支持。框架内置支持一些比较流行的后端驱动如[Memcached](http://memcached.org)、 [Redis](http://redis.io)和数据库。 + +session的配置被存放在 `app/config/session.php` 文件中。请务必查看一下这个文件中那些带有注释的配置选项。Laravel默认使用`原生`的session驱动,它可以在大多数应用中良好地工作。 + + +## Session 使用指南 + +**储存一个Session变量** + + Session::put('key', 'value'); + +**读取一个Session变量** + + $value = Session::get('key'); + +**读取一个Session变量或者返回默认值** + + $value = Session::get('key', 'default'); + + $value = Session::get('key', function() { return 'default'; }); + +**判断一个Session变量是否存在** + + if (Session::has('users')) + { + // + } + +**删除一个Session变量** + + Session::forget('key'); + +**删除所有Session变量** + + Session::flush(); + +**重置 Session ID** + + Session::regenerate(); + + +## 闪存数据 + +有时,你也许希望将信息暂存在session中到下一次请求为止,你可以使用 `Session::flash` 方法达到这个目的: + + Session::flash('key', 'value'); + +**刷新当前闪存数据,延长其过期时间到下一个请求** + + Session::reflash(); + +**刷新一部分闪存数据** + + Session::keep(array('username', 'email')); + + +## 数据库 Sessions + +当使用 `数据库` session驱动时,你需要设置一张表去存储session变量。下面是一个例子,使用 `Schema` 声明新建一张表: + + Schema::create('sessions', function($table) + { + $table->string('id')->unique(); + $table->text('payload'); + $table->integer('last_activity'); + }); + +当然,你也可以使用Artisan命令 `session:table` 来创建这张表: + + php artisan session:table + + composer dump-autoload + + php artisan migrate \ No newline at end of file diff --git a/cn/templates.md b/cn/templates.md new file mode 100644 index 00000000000..7ec1d5ca531 --- /dev/null +++ b/cn/templates.md @@ -0,0 +1,129 @@ +# 模板 + +- [控制器布局](#controller-layouts) +- [Blade模板](#blade-templating) +- [其他 Blade模板 控制结构](#other-blade-control-structures) + + +## 控制器布局 + +在Laravel框架中使用模板的一种方法就是通过控制器布局。通过在控制器中指定 `layout` 属性,对应的视图会被创建并且作为请求的默认返回数据。 + +**在控制器中定义一个布局** + + class UserController extends BaseController { + + /** + * The layout that should be used for responses. + */ + protected $layout = 'layouts.master'; + + /** + * Show the user profile. + */ + public function showProfile() + { + $this->layout->content = View::make('user.profile'); + } + + } + + +## Blade模板 + +Blade是Laravel框架下的一种简单又强大的模板引擎。 +不同于控制器布局,Blade模板引擎由模板继承和模板片段驱动。所有的Blade模板文件必须使用Blade `.blade.php` 文件扩展名。 + + +**定义一个Blade布局** + + + + + + @section('sidebar') + This is the master sidebar. + @show + +
+ @yield('content') +
+ + + +**使用一个Blade布局** + + @extends('layouts.master') + + @section('sidebar') + @parent + +

This is appended to the master sidebar.

+ @stop + + @section('content') +

This is my body content.

+ @stop + +注意一个Blade布局的扩展视图简单地在布局中替换了模板片段。通过在模板片段中使用 `@parent` 指令,布局的内容可以被包含在一个子视图中,这样你就可以在布局片段中添加诸如侧边栏、底部信息的内容。 + + +## 其他 Blade模板 控制结构 + +**输出数据** + + Hello, {{ $name }}. + + The current UNIX timestamp is {{ time() }}. + +If you need to display a string that is wrapped in curly braces, you may escape the Blade behavior by prefixing your text with an `@` symbol: + +**Displaying Raw Text With Curly Braces** + + @{{ This will not be processed by Blade }} + +你可以使用三联大括号语法来避免输出: + + Hello, {{{ $name }}}. + +**If 声明** + + @if (count($records) === 1) + I have one record! + @elseif (count($records) > 1) + I have multiple records! + @else + I don't have any records! + @endif + + @unless (Auth::check()) + You are not signed in. + @endunless + +**循环** + + @for ($i = 0; $i < 10; $i++) + The current value is {{ $i }} + @endfor + + @foreach ($users as $user) +

This is user {{ $user->id }}

+ @endforeach + + @while (true) +

I'm looping forever.

+ @endwhile + +**包含子视图** + + @include('view.name') + +**输出多语言(Language Lines)** + + @lang('language.line') + + @choice('language.line', 1); + +**注释** + + {{-- This comment will not be in the rendered HTML --}} diff --git a/cn/testing.md b/cn/testing.md new file mode 100644 index 00000000000..46a4c4270ac --- /dev/null +++ b/cn/testing.md @@ -0,0 +1,185 @@ +# 单元测试 + +- [简介](#introduction) +- [定义 & 运行测试](#defining-and-running-tests) +- [测试环境](#test-environment) +- [测试中执行路由](#calling-routes-from-tests) +- [模拟Facades](#mocking-facades) +- [框架断言](#framework-assertions) +- [辅助函数](#helper-methods) + + +## 简介 + +Laravel 自建了单元测试。事实上,我们支持使用自带的 PHPUnit 进行测试,并且已经为你的应用程序配置好了 `phpunit.xml` 文件。除了 PHPUnit,Laravel 也使用了 Symfony HttpKernel,DomCrawler 和 BrowserKit 组件,允许你在测试中检查和操作视图以及模拟浏览器行为。 + +在 `app/tests` 目录下已经提供了一个测试示例文件。在安装了一个全新的 Laravel 应用程序之后,只需要简单的在命令行中执行 `phpunit`, 即可运行你的测试。 + + + +## 定义 & 运行测试 + +要创建一个测试用例,只需要简单的在 `app/tests` 目录创建一个新的测试文件。测试类需要继承 `TestCase` 。然后你可以像使用 PHPUnit 一样来定义这些测试函数。 + +**一个测试类示例** + + class FooTest extends TestCase { + + public function testSomethingIsTrue() + { + $this->assertTrue(true); + } + + } + +你可以在终端执行 `phpunit` 命令来运行应用程序中的所有测试。 + +> **注意:** 如果你定义了自己的 `setUp` 函数,请确保调用了 `parent::setUp `。 + + +## 测试环境 + +在运行单元测试时,Laravel 将自动设置环境配置为 `testing` 。并且,Laravel 在测试环境中包含了 `session` 和 `cache` 配置文件。测试环境中,这两个驱动都被设置为空 `array`,意味着在测试过程中,不会将 session 或者 cache 持久化。必要时,你也可以创建其他的测试环境。 + + +## 测试中执行路由 + +在测试时可以很简单的使用 `call` 函数来执行一个路由: + +**测试中执行路由** + + $response = $this->call('GET', 'user/profile'); + + $response = $this->call($method, $uri, $parameters, $files, $server, $content); + +你可以检查 `Illuminate\Http\Response` 对象: + + $this->assertEquals('Hello World', $response->getContent()); + +你也可以在测试中执行控制器: + +**测试中执行控制器** + + $response = $this->action('GET', 'HomeController@index'); + + $response = $this->action('GET', 'UserController@profile', array('user' => 1)); + +`getContent` 函数将会返回和响应相同的字符串内容。如果路由返回 `视图(View)` , 你可以使用 `original` 属性来访问: + + $view = $response->original; + + $this->assertEquals('John', $view['name']); + +要执行一个 HTTPS 路由,可以使用 `callSecure` 函数: + + $response = $this->callSecure('GET', 'foo/bar'); + +> **注意:** 在测试环境中,路由过滤器将被禁用。如果需要启用它们, 请在你的测试中添加 `Route::enableFilters()` 。 + +### DOM Crawler + +你也可以执行一个路由并且接受一个 DOM Crawler 实例来检查内容: + + $crawler = $this->client->request('GET', '/'); + + $this->assertTrue($this->client->getResponse()->isOk()); + + $this->assertCount(1, $crawler->filter('h1:contains("Hello World!")')); + +如需获取更多关于 DOM Crawler 的信息,请参见文档 [official documentation](http://symfony.com/doc/master/components/dom_crawler.html)。 + + +## 模拟Facades + +在测试时,你可能经常需要模拟执行一个 Laravel 静态 facade。例如,参考如下控制器的动作: + + public function getIndex() + { + Event::fire('foo', array('name' => 'Dayle')); + + return 'All done!'; + } + +我们可以通过在 facade 使用 `shouldReceive` 函数来模拟执行 `Event` 类, 它将返回一个 [Mockery](https://github.com/padraic/mockery) 模拟的实例。 + + + +**模拟一个Facade** + + public function testGetIndex() + { + Event::shouldReceive('fire')->once()->with('foo', array('name' => 'Dayle')); + + $this->call('GET', '/'); + } + +> **注意:** 你不能模拟 `Request` 的 facade。替代的方式为,在运行你的测试时,将你预期的输入传给 `call` 函数。 + + +## 框架断言 + +Laravel 提供了一些 `断言(assert)` 函数来让测试更加容易: + +**断言响应为OK** + + public function testMethod() + { + $this->call('GET', '/'); + + $this->assertResponseOk(); + } + +**断言响应状态码** + + $this->assertResponseStatus(403); + +**断言响应为重定向** + + $this->assertRedirectedTo('foo'); + + $this->assertRedirectedToRoute('route.name'); + + $this->assertRedirectedToAction('Controller@method'); + +**断言视图含有的一些数据** + + public function testMethod() + { + $this->call('GET', '/'); + + $this->assertViewHas('name'); + $this->assertViewHas('age', $value); + } + +**断言 Session 中含有的一些数据** + + public function testMethod() + { + $this->call('GET', '/'); + + $this->assertSessionHas('name'); + $this->assertSessionHas('age', $value); + } + + +## 辅助函数 + +`TestCase` 类包含一些辅助函数来让应用程序测试更加容易。 + +你可以使用 `be` 函数来设置当前通过验证的用户: + +**设置当前通过验证的用户** + + $user = new User(array('name' => 'John')); + + $this->be($user); + +你可以在测试中使用 `seed` 函数来重新填充你的数据库: + +**测试中重新填充数据库** + + $this->seed(); + + $this->seed($connection); + +如需获取更多关于创建数据填充的信息,请参见文档 [迁移 & 数据填充](/docs/migrations#database-seeding)。 diff --git a/cn/validation.md b/cn/validation.md new file mode 100644 index 00000000000..76969d3baa7 --- /dev/null +++ b/cn/validation.md @@ -0,0 +1,508 @@ +# 验证 + +- [基本使用](#basic-usage) +- [使用错误消息](#working-with-error-messages) +- [错误消息 & 视图](#error-messages-and-views) +- [可用的验证规则](#available-validation-rules) +- [有条件的添加规则](#conditionally-adding-rules) +- [自定义错误消息](#custom-error-messages) +- [自定义验证规则](#custom-validation-rules) + + +## 基本使用 + +Laravel 自带一个简单、方便的 `Validation` 类用于验证数据以及获取错误消息。 + +**基本验证例子** + + $validator = Validator::make( + array('name' => 'Dayle'), + + array('name' => 'required|min:5') + ); + +传递给 `make` 函数的第一个参数是待验证的数据,第二个参数是对该数据需要应用的验证规则。 + +多个验证规则可以通过 "|" 字符进行分隔,或者作为数组的一个单独的元素。 + +**通过数组指定验证规则** + + $validator = Validator::make( + array('name' => 'Dayle'), + array('name' => array('required', 'min:5')) + ); + +**验证多个字段** + + $validator = Validator::make( + array( + 'name' => 'Dayle', + 'password' => 'lamepassword', + 'email' => 'email@example.com' + ), + array( + 'name' => 'required', + 'password' => 'required|min:8', + 'email' => 'required|email|unique:users' + ) + ); + +一旦一个 `Validator` 实例被创建,可以使用 `fails` (或者 `passes`)函数执行这个验证。 + + if ($validator->fails()) + { + // The given data did not pass validation + } + +如果验证失败,你可以从验证器中获取错误消息。 + + $messages = $validator->messages(); + +你也可以使用 `failed` 函数得到不带错误消息的没有通过验证的规则的数组。 + + $failed = $validator->failed(); + +**文件验证** + +`Validator` 类提供了一些验证规则用于验证文件,比如 `size`、`mimes`等。在验证文件的时候,你可以和其他数据一起传递给验证器。 + + +## 使用错误消息 + +在一个 `Validator` 实例上调用 `messages` 函数之后,将会得到一个 `MessageBag` 实例,该实例拥有很多方便使用错误消息的函数。 + +**获取一个字段的第一个错误消息** + + echo $messages->first('email'); + +**获取一个字段的全部错误消息** + + foreach ($messages->get('email') as $message) + { + // + } + +**获取全部字段的全部错误消息** + + foreach ($messages->all() as $message) + { + // + } + +**检查一个字段是否存在消息** + + if ($messages->has('email')) + { + // + } + +**以某种格式获取一条错误消息** + + echo $messages->first('email', '

:message

'); + +> **注意:** 默认情况下,消息将使用与 Bootstrap 兼容的语法进行格式化。 + +**以某种格式获取所有错误消息** + + foreach ($messages->all('
  • :message
  • ') as $message) + { + // + } + + +## 错误消息 & 视图 + +一旦你执行了验证,你需要一种简单的方法向视图反馈错误消息。这在 Lavavel 中能够方便的处理。以下面的路由作为例: + + Route::get('register', function() + { + return View::make('user.register'); + }); + + Route::post('register', function() + { + $rules = array(...); + + $validator = Validator::make(Input::all(), $rules); + + if ($validator->fails()) + { + return Redirect::to('register')->withErrors($validator); + } + }); + +注意当验证失败,我们使用 `withErrors` 函数把 `Validator` 实例传递给 Redirect。这个函数将刷新 Session 中保存的错误消息,使得它们在下次请求中可用。 + +然而,请注意在我们的 GET 路由中并没有明确的绑定错误消息到视图。这是因为 Laravel 总会检查 Session 中的错误,并且如果它们是可用的将自动绑定到视图。**所以,需要特别注意的是,对于每个请求,一个 `$errors` 变量在所有视图中总是可用的**,允许你方便的认为 `$errors` 变量总是被定义并可以安全使用的。`$errors` 变量将是一个 `MessageBag` 类的实例。 + +所以,在跳转之后,你可以在视图中使用自动绑定的 `$errors` 变量: + + first('email'); ?> + + +## 可用的验证规则 + +下面是一个所有可用的验证规则的列表以及它们的功能: + +- [Accepted](#rule-accepted) +- [Active URL](#rule-active-url) +- [After (Date)](#rule-after) +- [Alpha](#rule-alpha) +- [Alpha Dash](#rule-alpha-dash) +- [Alpha Numeric](#rule-alpha-num) +- [Before (Date)](#rule-before) +- [Between](#rule-between) +- [Confirmed](#rule-confirmed) +- [Date](#rule-date) +- [Date Format](#rule-date-format) +- [Different](#rule-different) +- [Digits](#rule-digits) +- [Digits Between](#rule-digitsbetween) +- [E-Mail](#rule-email) +- [Exists (Database)](#rule-exists) +- [Image (File)](#rule-image) +- [In](#rule-in) +- [Integer](#rule-integer) +- [IP Address](#rule-ip) +- [Max](#rule-max) +- [MIME Types](#rule-mimes) +- [Min](#rule-min) +- [Not In](#rule-not-in) +- [Numeric](#rule-numeric) +- [Regular Expression](#rule-regex) +- [Required](#rule-required) +- [Required If](#rule-required-if) +- [Required With](#rule-required-with) +- [Required Without](#rule-required-without) +- [Same](#rule-same) +- [Size](#rule-size) +- [Unique (Database)](#rule-unique) +- [URL](#rule-url) + + +#### accepted + +验证此规则的值必须是 _yes_、 _on_ 或者是 _1_。这在验证是否同意"服务条款"的时候非常有用。 + + +#### active_url + +验证此规则的值必须是一个合法的 URL,根据 PHP 函数 `checkdnsrr`。 + + +#### after:_date_ + +验证此规则的值必须在给定日期之后,该日期将被传递到 PHP 的 `strtotime` 函数。 + + +#### alpha + +验证此规则的值必须全部由字母字符构成。 + + +#### alpha_dash + +验证此规则的值必须全部由字母、数字、中划线或下划线字符构成。 + + +#### alpha_num + +验证此规则的值必须全部由字母和数字构成。 + + +#### before:_date_ + +验证此规则的值必须在给定日期之前,该日期将被传递到 PHP 的 `strtotime` 函数。 + + +#### between:_min_,_max_ + +验证此规则的值必须在给定的 _min_ 和 _max_ 之间。字符串、数字以及文件都将使用大小规则进行比较。 + + +#### confirmed + +验证此规则的值必须和 `foo_confirmation` 的值相同。比如,需要验证此规则的字段是 `password`,那么在输入中必须有一个与之相同的 `password_confirmation` 字段。 + + +#### date + +验证此规则的值必须是一个合法的日期,根据 PHP 函数 `strtotime`。 + + +#### date_format:_format_ + +验证此规则的值必须符合给定的 _format_ 的格式,根据 PHP 函数 `date_parse_from_format`。 + + +#### different:_field_ + +验证此规则的值必须与指定的 _field_ 字段的值不同。 + + +#### digits:_value_ + +The field under validation must be _numeric_ and must have an exact length of _value_. + + +#### digitsbetween:_min_,_max_ + +The field under validation must have a length between the given _min_ and _max_. + + +#### email + +验证此规则的值必须是一个合法的电子邮件地址。 + + +#### exists:_table_,_column_ + +验证此规则的值必须在指定的数据库的表中存在。 + +**Exists 规则的基础使用** + + 'state' => 'exists:states' + +**指定列名** + + 'state' => 'exists:states,abbreviation' + +你也可以指定更多的条件,将以 "where" 的形式添加到查询。 + + 'email' => 'exists:staff,email,account_id,1' + + +#### image + +验证此规则的值必须是一个图片 (jpeg, png, bmp 或者 gif)。 + + +#### in:_foo_,_bar_,... + +验证此规则的值必须在给定的列表中存在。 + + +#### integer + +验证此规则的值必须是一个整数。 + + +#### ip + +验证此规则的值必须是一个合法的 IP 地址。 + + +#### max:_value_ + +验证此规则的值必须小于最大值 _value_。字符串、数字以及文件都将使用大小规则进行比较。 + + +#### mimes:_foo_,_bar_,... + +验证此规则的文件的 MIME 类型必须在给定的列表中。 + +**MIME 规则的基础使用** + + 'photo' => 'mimes:jpeg,bmp,png' + + +#### min:_value_ + +验证此规则的值必须大于最小值 _value_。字符串、数字以及文件都将使用大小规则进行比较。 + + +#### not_in:_foo_,_bar_,... + +验证此规则的值必须在给定的列表中不存在。 + + +#### numeric + +验证此规则的值必须是一个数字。 + + +#### regex:_pattern_ + +验证此规则的值必须符合给定的正则表达式。 + +**注意:** 当使用 `regex` 模式的时候,可能需要在一个数组中指定规则,而不是使用 "|" 分隔符,特别是正则表达式中包含一个 "|" 字符的时候。 + + +#### required + +验证此规则的值必须在输入数据中存在。 + + +#### required_if:_field_,_value_ + +如果指定的 _field_ 字段等于指定的 _value_ ,那么验证此规则的值必须存在。 + + +#### required_with:_foo_,_bar_,... + +_仅当_其它指定的字段存在的时候,验证此规则的值必须存在。 + + +#### required_without:_foo_,_bar_,... + +_仅当_其它指定的字段不存在的时候,验证此规则的值必须存在。 + + +#### same:_field_ + +验证此规则的值必须与给定的 _field_ 字段的值相同。 + + +#### size:_value_ + +验证此规则的值的大小必须与给定的 _value_ 相同。对于字符串,_value_ 代表字符的个数;对于数字,_value_ 代表它的整数值,对于文件,_value_ 代表文件以KB为单位的大小。 + + +#### unique:_table_,_column_,_except_,_idColumn_ + +验证此规则的值必须在给定的数据库的表中唯一。如果 `column` 没有被指定,将使用该字段的名字。 + +**Unique 规则的基础使用** + + 'email' => 'unique:users' + +**指定列名** + + 'email' => 'unique:users,email_address' + +**强制忽略一个给定的 ID** + + 'email' => 'unique:users,email_address,10' + +**添加额外的where语句** + +你还可以指定更多条件,这些条件将被添加到查询的"where"语句中: + + 'email' => 'unique:users,email_address,NULL,id,account_id,1' + +在上面的规则中,只有`account_id` 为 `1` 的行才会被包含到unique检查中。 + + +#### url + +验证此规则的值必须是一个合法的 URL。 + + +## 有条件的添加规则 + +有时你可能希望给定的字段仅当另一个字段的值大于100的时候必须存在。或者你可能需要两个字段均含有一个给定的值,仅当另一个字段存在的时候。添加这些验证规则并没有那么麻烦。首先,创建一个使用你永远不会改变的 _static rules_ 的 `Validator` 实例: + + $v = Validator::make($data, array( + 'email' => 'required|email', + 'games' => 'required|numeric', + )); + +假设我们的WEB应用程序是服务于游戏收藏爱好者们。如果一个游戏收藏爱好者注册了我们的应用程序,并且他们拥有100多款游戏,我们想让他们说明为什么他们会拥有如此多的游戏。例如,或许他们要开一个游戏转售店,或者也许他们只是喜欢收集。为了有条件的添加这个需求,我们可以使用 `Validator` 实例的 `sometimes` 函数。 + + $v->sometimes('reason', 'required|max:500', function($input) + { + return $input->games >= 100; + }); + +`sometimes` 函数的第一个参数是我们有条件的验证的字段名。第二个参数是我们要添加的规则。如果 `Closure` 作为第三个参数且返回了 `true`,规则将被添加。这种方法可以很容易构建复杂的条件验证。你甚至可以一次性为多个字段添加条件验证: + + $v->sometimes(array('reason', 'cost'), 'required', function($input) + { + return $input->games >= 100; + }); + +> **注意:** 传递到你的 `Closure` 中的 `$input` 参数将被作为 `Illuminate\Support\Fluent` 的一个实例,并且可能被用作一个对象来访问你的输入和文件。 + + +## 自定义错误消息 + +如果有需要,你可以使用自定义的错误消息代替默认的消息。这里有好几种自定义错误消息的方法。 + +**传递自定义消息到验证器** + + $messages = array( + 'required' => 'The :attribute field is required.', + ); + + $validator = Validator::make($input, $rules, $messages); + +*注意:* `:attribute` 占位符将被实际要验证的字段名替换,你也可以在错误消息中使用其他占位符。 + +**其他验证占位符** + + $messages = array( + 'same' => 'The :attribute and :other must match.', + 'size' => 'The :attribute must be exactly :size.', + 'between' => 'The :attribute must be between :min - :max.', + 'in' => 'The :attribute must be one of the following types: :values', + ); + +有些时候,你可能希望只对一个指定的字段指定自定义的错误消息: + +**对一个指定的字段指定自定义的错误消息** + + $messages = array( + 'email.required' => 'We need to know your e-mail address!', + ); + +在一些情况下,你可能希望在一个语言文件中指定你的错误消息而不是直接传递给 `Validator`。为了实现这个目的,请在 `app/lang/xx/validation.php` 文件中添加你的自定义消息到 `custom` 数组。 + + +**在语言文件中指定错误消息** + + 'custom' => array( + 'email' => array( + 'required' => 'We need to know your e-mail address!', + ), + ), + + +## 自定义验证规则 + +Laravel 提供了一系列有用的验证规则;但是,你可能希望添加自己的验证规则。其中一种方法是使用 `Validator::extend` 函数注册自定义的验证规则: + +**注册一个自定义的验证规则** + + Validator::extend('foo', function($attribute, $value, $parameters) + { + return $value == 'foo'; + }); + +自定义的验证器接受三个参数:待验证属性的名字、待验证属性的值以及传递给这个规则的参数。 + +你也可以传递一个类的函数到 `extend` 函数,而不是使用闭包: + + Validator::extend('foo', 'FooValidator@validate'); + +注意你需要为你的自定义规则定义错误消息。你既可以使用一个行内的自定义消息数组,也可以在验证语言文件中进行添加。 + +你也可以扩展 `Validator` 类本身,而不是使用闭包回调扩展验证器。为了实现这个目的,添加一个继承自 `Illuminate\Validation\Validator` 的验证器类。你可以在类中添加以 `validate` 开头的验证函数: + +**扩展验证器类** + + ## Environment Configuration @@ -54,6 +56,8 @@ Next, we need to instruct the framework how to determine which environment it is )); +In this example, 'local' is the name of the environment and 'your-machine-name' is the hostname of your server. On Linux and Mac, you may determine your hostname using the `hostname` terminal command. + You may also pass a `Closure` to the `detectEnvironment` method, allowing you to implement your own environment detection: $env = $app->detectEnvironment(function() diff --git a/contributing.md b/contributing.md index 32f12fc1a5a..ca61bb5a9b8 100644 --- a/contributing.md +++ b/contributing.md @@ -7,7 +7,7 @@ ## Introduction -Laravel is free, open-source software, meaning anyone can contribute to its development and progress. Laravel source code is currently hosted on [Github](http://github.com), which provides an easy method for forking the project and merging your contributions. +Laravel is free, open-source software, meaning anyone can contribute to its development and progress. Laravel source code is currently hosted on [Github](https://github.com/laravel), which provides an easy method for forking the project and merging your contributions. ## Pull Requests @@ -16,6 +16,8 @@ The pull request process differs for new features and bugs. Before sending a pul Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed on Github, please leave a comment detailing your proposed fix. +Additions and corrections to the documentation may also be contributed via the [documentation repository](https://github.com/laravel/docs) on Github. + ### Feature Requests If you have an idea for a new feature you would like to see added to Laravel, you may create an issue on Github with `[Request]` in the title. The feature request will then be reviewed by a core contributor. diff --git a/controllers.md b/controllers.md index 29177e495f7..d68f5d92c6c 100644 --- a/controllers.md +++ b/controllers.md @@ -67,7 +67,7 @@ However, you may also specify filters from within your controller: */ public function __construct() { - $this->beforeFilter('auth'); + $this->beforeFilter('auth', array('except' => 'getLogin')); $this->beforeFilter('csrf', array('on' => 'post')); @@ -142,15 +142,15 @@ This single route declaration creates multiple routes to handle a variety of RES **Actions Handled By Resource Controller** -Verb | Path | Action | Route Name -----------|-----------------------|--------------|--------------------- -GET | /resource | index | resource.index -GET | /resource/create | create | resource.create -POST | /resource | store | resource.store -GET | /resource/{id} | show | resource.show -GET | /resource/{id}/edit | edit | resource.edit -PUT/PATCH | /resource/{id} | update | resource.update -DELETE | /resource/{id} | destroy | resource.destroy +Verb | Path | Action | Route Name +----------|-----------------------------|--------------|--------------------- +GET | /resource | index | resource.index +GET | /resource/create | create | resource.create +POST | /resource | store | resource.store +GET | /resource/{resource} | show | resource.show +GET | /resource/{resource}/edit | edit | resource.edit +PUT/PATCH | /resource/{resource} | update | resource.update +DELETE | /resource/{resource} | destroy | resource.destroy Sometimes you may only need to handle a subset of the resource actions: @@ -163,6 +163,9 @@ And, you may also specify a subset of actions to handle on the route: Route::resource('photo', 'PhotoController', array('only' => array('index', 'show'))); + Route::resource('photo', 'PhotoController', + array('except' => array('create', 'store', 'update', 'delete'))); + ## Handling Missing Methods @@ -173,4 +176,4 @@ A catch-all method may be defined which will be called when no other matching me public function missingMethod($parameters) { // - } \ No newline at end of file + } diff --git a/database.md b/database.md index 6898653d339..4e3f818baf5 100644 --- a/database.md +++ b/database.md @@ -83,4 +83,8 @@ Sometimes you may need to reconnect to a given database: By default, Laravel keeps a log in memory of all queries that have been run for the current request. However, in some cases, such as when inserting a large number of rows, this can cause the application to use excess memory. To disable the log, you may use the `disableQueryLog` method: - DB::connection()->disableQueryLog(); \ No newline at end of file + DB::connection()->disableQueryLog(); + +To get an array of the executed queries, you may use the `getQueryLog` method: + + $queries = DB::getQueryLog(); diff --git a/documentation.md b/documentation.md index ea1af9200b2..b37018756aa 100755 --- a/documentation.md +++ b/documentation.md @@ -13,6 +13,7 @@ - [Errors & Logging](/docs/errors) - Learning More - [Cache](/docs/cache) + - [Core Extension](/docs/extending) - [Events](/docs/events) - [Facades](/docs/facades) - [Forms & HTML](/docs/html) diff --git a/eloquent.md b/eloquent.md index 78fc4e00677..d37fa96bcd3 100644 --- a/eloquent.md +++ b/eloquent.md @@ -92,6 +92,16 @@ Of course, you may also use the query builder aggregate functions. $count = User::where('votes', '>', 100)->count(); +If you are unable to generate the query you need via the fluent interface, feel free to use `whereRaw`: + + $users = User::whereRaw('age > ? and votes = 100', array(25))->get(); + +**Specifying The Query Connection** + +You may also specify which database connection should be used when running an Eloquent query. Simply use the `on` method: + + $user = User::on('connection-name')->find(1); + ## Mass Assignment @@ -144,6 +154,10 @@ To create a new record in the database from a model, simply create a new model i You may also use the `create` method to save a new model in a single line. The inserted model instance will be returned to you from the method. However, before doing so, you will need to specify either a `fillable` or `guarded` attribute on the model, as all Eloquent models protect against mass-assignment. +After saving or creating a new model that uses auto-incrementing IDs, you may retrieve the ID by accessing the object's `id` attribute: + + $insertedId = $user->id; + **Setting The Guarded Attributes On The Model** class User extends Eloquent { @@ -188,6 +202,8 @@ To delete a model, simply call the `delete` method on the instance: User::destroy(1); + User::destroy(array(1, 2, 3)); + User::destroy(1, 2, 3); Of course, you may also run a delete query on a set of models: @@ -255,7 +271,7 @@ To determine if a given model instance has been soft deleted, you may use the `t ## Timestamps -By default, Eloquent will maintain the `created_at` and `updated_at` columns on your database table automatically. Simply add these `datetime` columns to your table and Eloquent will take care of the rest. If you do not wish for Eloquent to maintain these columns, add the following property to your model: +By default, Eloquent will maintain the `created_at` and `updated_at` columns on your database table automatically. Simply add these `timestamp` columns to your table and Eloquent will take care of the rest. If you do not wish for Eloquent to maintain these columns, add the following property to your model: **Disabling Auto Timestamps** @@ -267,15 +283,15 @@ By default, Eloquent will maintain the `created_at` and `updated_at` columns on } -If you wish to customize the format of your timestamps, you may override the `freshTimestamp` method in your model: +If you wish to customize the format of your timestamps, you may override the `getDateFormat` method in your model: **Providing A Custom Timestamp Format** class User extends Eloquent { - public function freshTimestamp() + protected function getDateFormat() { - return time(); + return 'U'; } } @@ -294,11 +310,33 @@ Scopes allow you to easily re-use query logic in your models. To define a scope, return $query->where('votes', '>', 100); } + public function scopeWomen($query) + { + return $query->whereGender('W'); + } + } **Utilizing A Query Scope** - $users = User::popular()->orderBy('created_at')->get(); + $users = User::popular()->women()->orderBy('created_at')->get(); + +**Dynamic Scopes** + +Sometimes You may wish to define a scope that accepts parameters. Just add your parameters to your scope function: + + class User extends Eloquent { + + public function scopeOfType($query, $type) + { + return $query->whereType($type); + } + + } + +Then pass the parameter into the scope call: + + $users = User::ofType('member')->get(); ## Relationships @@ -353,6 +391,17 @@ To define the inverse of the relationship on the `Phone` model, we use the `belo } +In the example above, Eloquent will look for a `user_id` column on the `phones` table. If you would like to define a different foreign key column, you may pass it as the second argument to the `belongsTo` method: + + class Phone extends Eloquent { + + public function user() + { + return $this->belongsTo('User', 'custom_key'); + } + + } + ### One To Many @@ -532,7 +581,7 @@ Eloquent allows you to access your relations via dynamic properties. Eloquent wi } $phone = Phone::find(1); - + Instead of echoing the user's email like this: echo $phone->user()->first()->email; @@ -541,6 +590,8 @@ It may be shortened to simply: echo $phone->user->email; +> **Note:** Relationships that return many results will return an instance of the `Illuminate\Database\Eloquent\Collection` class. + ## Eager Loading @@ -732,7 +783,7 @@ Note that this operation does not delete records from the `roles` table, but onl ## Collections -All multi-result sets returned by Eloquent either via the `get` method or a relationship return an Eloquent `Collection` object. This object implements the `IteratorAggregate` PHP interface so it can be iterated over like an array. However, this object also has a variety of other helpful methods for working with result sets. +All multi-result sets returned by Eloquent, either via the `get` method or a `relationship`, will return a collection object. This object implements the `IteratorAggregate` PHP interface so it can be iterated over like an array. However, this object also has a variety of other helpful methods for working with result sets. For example, we may determine if a result set contains a given primary key using the `contains` method: @@ -757,25 +808,34 @@ If a collection is cast to a string, it will be returned as JSON: Eloquent collections also contain a few helpful methods for looping and filtering the items they contain: -**Iterating & Filtering Collections** +**Iterating Collections** $roles = $user->roles->each(function($role) { - + // }); - $roles = $user->roles->filter(function($role) - { +**Filtering Collections** + +When filtering collections, the callback provided will be used as callback for [array_filter](http://php.net/manual/en/function.array-filter.php). + $users = $user->filter(function($user) + { + if($user->isAdmin()) + { + return $user; + } }); +> **Note:** When filtering a collection and converting it to JSON, try calling the `values` function first to reset the array's keys. + **Applying A Callback To Each Collection Object** $roles = User::find(1)->roles; - + $roles->each(function($role) { - // + // }); **Sorting A Collection By A Value** @@ -853,7 +913,11 @@ To totally disable date mutations, simply return an empty array from the `getDat ## Model Events -Eloquent models fire several events, allowing you to hook into various points in the model's lifecycle using the following methods: `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`. If `false` is returned from the `creating`, `updating`, or `saving` events, the action will be cancelled: +Eloquent models fire several events, allowing you to hook into various points in the model's lifecycle using the following methods: `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `restoring`, `restored`. + +Whenever a new item is saved for the first time, the `creating` and `created` events will fire. If an item is not new and the `save` method is called, the `updating` / `updated` events will fire. In both cases, the `saving` / `saved` events will fire. + +If `false` is returned from the `creating`, `updating`, `saving`, or `deleting` events, the action will be cancelled: **Cancelling Save Operations Via Events** @@ -942,6 +1006,22 @@ Sometimes you may wish to limit the attributes that are included in your model's } +> **Note:** When hiding relationships, use the relationship's **method** name, not the dynamic accessor name. + Alternatively, you may use the `visible` property to define a white-list: protected $visible = array('first_name', 'last_name'); + + +Occasionally, you may need to add array attributes that do not have a corresponding column in your database. To do so, simply define an accessor for the value: + + public function getIsAdminAttribute() + { + return $this->attributes['admin'] == 'yes'; + } + +Once you have created the accessor, just add the value to the `appends` property on the model: + + protected $appends = array('is_admin'); + +Once the attribute has been added to the `appends` list, it will be included in both the model's array and JSON forms. diff --git a/errors.md b/errors.md index 439e48bb1c2..0f2c508b80b 100644 --- a/errors.md +++ b/errors.md @@ -44,6 +44,8 @@ To listen for PHP fatal errors, you may use the `App::fatal` method: // }); +If you have several exception handlers, they should be defined from most generic to most specific. So, for example, a handler that handles all exceptions of type `Exception` should be defined before a custom exception type such as `Illuminate\Encryption\DecryptException`. + ## HTTP Exceptions @@ -82,6 +84,10 @@ The Laravel logging facilities provide a simple layer on top of the powerful [Mo The logger provides the seven logging levels defined in [RFC 5424](http://tools.ietf.org/html/rfc5424): **debug**, **info**, **notice**, **warning**, **error**, **critical**, and **alert**. +An array of contextual data may also be passed to the log methods: + + Log::info('Log message', array('context' => 'Other helpful information')); + Monolog has a variety of additional handlers you may use for logging. If needed, you may access the underlying Monolog instance being used by Laravel: $monolog = Log::getMonolog(); diff --git a/extending.md b/extending.md new file mode 100644 index 00000000000..d4a4057ed88 --- /dev/null +++ b/extending.md @@ -0,0 +1,221 @@ +# Extending The Framework + +- [Introduction](#introduction) +- [Managers & Factories](#managers-and-factories) +- [Cache](#cache) +- [Session](#session) +- [Authentication](#authentication) +- [IoC Based Extension](#ioc-based-extension) +- [Request Extension](#request-extension) + + +## Introduction + +Laravel offers many extension points for you to customize the behavior of the framework's core components, or even replace them entirely. For example, the hashing facilities are defined by a `HasherInterface` contract, which you may implement based on your application's requirements. You may also extend the `Request` object, allowing you to add your own convenient "helper" methods. You may even add entirely new authentication, cache, and session drivers! + +Laravel components are generally extended in two ways: binding new implementations in the IoC container, or registering an extension with a `Manager` class, which are implementations of the "Factory" design pattern. In this chapter we'll explore the various methods of extending the framework and examine the necessary code. + +> **Note:** Remember, Laravel components are typically extended in one of two ways: IoC bindings and the `Manager` classes. The manager classes serve as an implementation of the "factory" design pattern, and are responsible for instantiating driver based facilities such as cache and session. + + +## Managers & Factories + +Laravel has several `Manager` classes that manage the creation of driver-based components. These include the cache, session, authentication, and queue components. The manager class is responsible for creating a particular driver implementation based on the application's configuration. For example, the `CacheManager` class can create APC, Memcached, Native, and various other implementations of cache drivers. + +Each of these managers includes an `extend` method which may be used to easily inject new driver resolution functionality into the manager. We'll cover each of these managers below, with examples of how to inject custom driver support into each of them. + +> **Note:** Take a moment to explore the various `Manager` classes that ship with Laravel, such as the `CacheManager` and `SessionManager`. Reading through these classes will give you a more thorough understanding of how Laravel works under the hood. All manager classes extend the `Illuminate\Support\Manager` base class, which provides some helpful, common functionality for each manager. + + +## Cache + +To extend the Laravel cache facility, we will use the `extend` method on the `CacheManager`, which is used to bind a custom driver resolver to the manager, and is common across all manager classes. For example, to register a new cache driver named "mongo", we would do the following: + + Cache::extend('mongo', function($app) + { + // Return Illuminate\Cache\Repository instance... + }); + +The first argument passed to the `extend` method is the name of the driver. This will correspond to your `driver` option in the `app/config/cache.php` configuration file. The second argument is a Closure that should return an `Illuminate\Cache\Repository` instance. The Closure will be passed an `$app` instance, which is an instance of `Illuminate\Foundation\Application` and an IoC container. + +To create our custom cache driver, we first need to implement the `Illuminate\Cache\StoreInterface` contract. So, our MongoDB cache implementation would look something like this: + + class MongoStore implements Illuminate\Cache\StoreInterface { + + public function get($key) {} + public function put($key, $value, $minutes) {} + public function increment($key, $value = 1) {} + public function decrement($key, $value = 1) {} + public function forever($key, $value) {} + public function forget($key) {} + public function flush() {} + + } + +We just need to implement each of these methods using a MongoDB connection. Once our implementation is complete, we can finish our custom driver registration: + + use Illuminate\Cache\Repository; + + Cache::extend('mongo', function($app) + { + return new Repository(new MongoStore); + }); + +As you can see in the example above, you may use the base `Illuminate\Cache\Repository` when creating custom cache drivers. There is typically no need to create your own repository class. + +If you're wondering where to put your custom cache driver code, consider making it available on Packagist! Or, you could create an `Extensions` namespace within your application's primary folder. For example, if the application is named `Snappy`, you could place the cache extension in `app/Snappy/Extensions/MongoStore.php`. However, keep in mind that Laravel does not have a rigid application structure and you are free to organize your application according to your preferences. + +> **Note:** If you're ever wondering where to put a piece of code, always consider a service provider. As we've discussed, using a service provider to organize framework extensions is a great way to organize your code. + + +## Session + +Extending Laravel with a custom session driver is just as easy as extending the cache system. Again, we will use the `extend` method to register our custom code: + + Session::extend('mongo', function($app) + { + // Return implementation of SessionHandlerInterface + }); + +Note that our custom cache driver should implement the `SessionHandlerInterface`. This interface is included in the PHP 5.4+ core. If you are using PHP 5.3, the interface will be defined for you by Laravel so you have forward-compatibility. This interface contains just a few simple methods we need to implement. A stubbed MongoDB implementation would look something like this: + + class MongoHandler implements SessionHandlerInterface { + + public function open($savePath, $sessionName) {} + public function close() {} + public function read($sessionId) {} + public function write($sessionId, $data) {} + public function destroy($sessionId) {} + public function gc($lifetime) {} + + } + +Since these methods are not as readily understandable as the cache `StoreInterface`, let's quickly cover what each of the methods do: + +- The `open` method would typically be used in file based session store systems. Since Laravel ships with a `native` session driver that uses PHP's native file storage for sessions, you will almost never need to put anything in this method. You can leave it as an empty stub. It is simply a fact of poor interface design (which we'll discuss later) that PHP requires us to implement this method. +- The `close` method, like the `open` method, can also usually be disregarded. For most drivers, it is not needed. +- The `read` method should return the string version of the session data associated with the given `$sessionId`. There is no need to do any serialization or other encoding when retrieving or storing session data in your driver, as Laravel will perform the serialization for you. +- The `write` method should write the given `$data` string associated with the `$sessionId` to some persistent storage system, such as MongoDB, Dynamo, etc. +- The `destroy` method should remove the data associated with the `$sessionId` from persistent storage. +- The `gc` method should destroy all session data that is older than the given `$lifetime`, which is a UNIX timestamp. For self-expiring systems like Memcached and Redis, this method may be left empty. + +Once the `SessionHandlerInterface` has been implemented, we are ready to register it with the Session manager: + + Session::extend('mongo', function($app) + { + return new MongoHandler; + }); + +Once the session driver has been registered, we may use the `mongo` driver in our `app/config/session.php` configuration file. + +> **Note:** Remember, if you write a custom session handler, share it on Packagist! + + +## Authentication + +Authentication may be extended the same way as the cache and session facilities. Again, we will use the `extend` method we have become familiar with: + + Auth::extend('riak', function($app) + { + // Return implementation of Illuminate\Auth\UserProviderInterface + }); + +The `UserProviderInterface` implementations are only responsible for fetching a `UserInterface` implementation out of a persistent storage system, such as MySQL, Riak, etc. These two interfaces allow the Laravel authentication mechanisms to continue functioning regardless of how the user data is stored or what type of class is used to represent it. + +Let's take a look at the `UserProviderInterface`: + + interface UserProviderInterface { + + public function retrieveById($identifier); + public function retrieveByCredentials(array $credentials); + public function validateCredentials(UserInterface $user, array $credentials); + + } + +The `retrieveById` function typically receives a numeric key representing the user, such as an auto-incrementing ID from a MySQL database. The `UserInterface` implementation matching the ID should be retrieved and returned by the method. + +The `retrieveByCredentials` method receives the array of credentials passed to the `Auth::attempt` method when attempting to sign into an application. The method should then "query" the underlying persistent storage for the user matching those credentials. Typically, this method will run a query with a "where" condition on `$credentails['username']`. **This method should not attempt to do any password validation or authentication.** + +The `validateCredentials` method should compare the given `$user` with the `$credentials` to authenticate the user. For example, this method might compare the `$user->getAuthPassword()` string to a `Hash::make` of `$credentials['password']`. + +Now that we have explored each of the methods on the `UserProviderInterface`, let's take a look at the `UserInterface`. Remember, the provider should return implementations of this interface from the `retrieveById` and `retrieveByCredentials` methods: + + interface UserInterface { + + public function getAuthIdentifier(); + public function getAuthPassword(); + + } + +This interface is simple. The `getAuthIdentifier` method should return the "primary key" of the user. In a MySQL back-end, again, this would be the auto-incrementing primary key. The `getAuthPassword` should return the user's hashed password. This interface allows the authentication system to work with any User class, regardless of what ORM or storage abstraction layer you are using. By default, Laravel includes a `User` class in the `app/models` directory which implements this interface, so you may consult this class for an implementation example. + +Finally, once we have implemented the `UserProviderInterface`, we are ready to register our extension with the `Auth` facade: + + Auth::extend('riak', function($app) + { + return new RiakUserProvider($app['riak.connection']); + }); + +After you have registered the driver with the `extend` method, you switch to the new driver in your `app/config/auth.php` configuration file. + + +## IoC Based Extension + +Almost every service provider included with the Laravel framework binds objects into the IoC container. You can find a list of your application's service providers in the `app/config/app.php` configuration file. As you have time, you should skim through each of these provider's source code. By doing so, you will gain a much better understanding of what each provider adds to the framework, as well as what keys are used to bind various services into the IoC container. + +For example, the `PaginationServiceProvider` binds a `paginator` key into the IoC container, which resolves into a `Illuminate\Pagination\Environment` instance. You can easily extend and override this class within your own application by overriding this IoC binding. For example, you could create a class that extend the base `Environment`: + + namespace Snappy\Extensions\Pagination; + + class Environment extends \Illuminate\Pagination\Environment { + + // + + } + +Once you have created your class extension, you may create a new `SnappyPaginationProvider` service provider class which overrides the paginator in its `boot` method: + + class SnappyPaginationProvider extends PaginationServiceProvider { + + public function boot() + { + App::bind('paginator', function() + { + return new Snappy\Extensions\Pagination\Environment; + }); + + parent::boot(); + } + + } + +Note that this class extends the `PaginationServiceProvider`, not the default `ServiceProvider` base class. Once you have extended the service provider, swap out the `PaginationServiceProvider` in your `app/config/app.php` configuration file with the name of your extended provider. + +This is the general method of extending any core class that is bound in the container. Essentially every core class is bound in the container in this fashion, and can be overridden. Again, reading through the included framework service providers will familiarize you with where various classes are bound into the container, and what keys they are bound by. This is a great way to learn more about how Laravel is put together. + + +## Request Extension + +Because it is such a foundational piece of the framework and is instantiated very early in the request cycle, extending the `Request` class works a little differently than the previous examples. + +First, extend the class like normal: + + ## Mocking Facades diff --git a/helpers.md b/helpers.md index fc7eafd03f8..9d08ca1f63d 100644 --- a/helpers.md +++ b/helpers.md @@ -45,9 +45,12 @@ The `array_except` method removes the given key / value pairs from the array. The `array_fetch` method returns a flattened array containing the selected nested element. - $array = array(array('name' => 'Taylor'), array('name' => 'Dayle')); + $array = array( + array('developer' => array('name' => 'Taylor')), + array('developer' => array('name' => 'Dayle')), + ); - var_dump(array_fetch($array, 'name')); + $array = array_fetch($array, 'developer.name'); // array('Taylor', 'Dayle'); @@ -157,7 +160,7 @@ Return the last element in the array. Useful for method chaining. ### app_path -Get the fully qualified path to the `application` directory. +Get the fully qualified path to the `app` directory. ### base_path @@ -169,7 +172,7 @@ Get the fully qualified path to the `public` directory. ### storage_path -Get the fully qualified path to the `application/storage` directory. +Get the fully qualified path to the `app/storage` directory. ## Strings @@ -192,7 +195,7 @@ Get the class name of the given class, without any namespace names. ### e -Run `htmlentites` over the given string, with UTF-8 support. +Run `htmlentities` over the given string, with UTF-8 support. $entities = e('foo'); @@ -283,6 +286,12 @@ Generate a URL for a given controller action. $url = action('HomeController@getIndex', $params); +### route + +Generate a URL for a given named route. + + $url = route('routeName', $params); + ### asset Generate a URL for an asset. diff --git a/html.md b/html.md index 732f35ad746..c0ff4f73989 100644 --- a/html.md +++ b/html.md @@ -10,6 +10,7 @@ - [Drop-Down Lists](#drop-down-lists) - [Buttons](#buttons) - [Custom Macros](#custom-macros) +- [Generating URLs](#generating-urls) ## Opening A Form @@ -24,7 +25,7 @@ By default, a `POST` method will be assumed; however, you are free to specify an echo Form::open(array('url' => 'foo/bar', 'method' => 'put')) -> **Note:** Since HTML forms only support `POST`, `PUT` and `DELETE` methods will be spoofed by automatically adding a `_method` hidden field to your form. +> **Note:** Since HTML forms only support `POST` and `GET`, `PUT` and `DELETE` methods will be spoofed by automatically adding a `_method` hidden field to your form. You may also open forms that point to named routes or controller actions: @@ -32,6 +33,12 @@ You may also open forms that point to named routes or controller actions: echo Form::open(array('action' => 'Controller@method')) +You may pass in route parameters as well: + + echo Form::open(array('route' => array('route.name', $user->id))) + + echo Form::open(array('action' => array('Controller@method', $user->id))) + If your form is going to accept file uploads, add a `files` option to your array: echo Form::open(array('url' => 'foo/bar', 'files' => true)) @@ -101,19 +108,24 @@ This allows you to quickly build forms that not only bind to model values, but e echo Form::password('password'); +**Generating Other Inputs** + + echo Form::email($name, $value = null, $attributes = array()); + echo Form::file($name, $attributes = array()); + ## Checkboxes and Radio Buttons **Generating A Checkbox Or Radio Input** echo Form::checkbox('name', 'value'); - + echo Form::radio('name', 'value'); **Generating A Checkbox Or Radio Input That Is Checked** echo Form::checkbox('name', 'value', true); - + echo Form::radio('name', 'value', true); @@ -141,6 +153,14 @@ This allows you to quickly build forms that not only bind to model values, but e 'Dogs' => array('spaniel' => 'Spaniel'), )); +**Generating A Drop-Down List With A Range** + + echo Form::selectRange('number', 10, 20); + +**Generating A List With Month Names** + + echo Form::selectMonth('month'); + ## Buttons @@ -167,3 +187,9 @@ Now you can call your macro using its name: **Calling A Custom Form Macro** echo Form::myField(); + + + +##Generating URLs + +For more information on generating URL's, check out the documentation on [helpers](/docs/helpers#urls). diff --git a/installation.md b/installation.md index 57f36958763..58aefbee033 100644 --- a/installation.md +++ b/installation.md @@ -18,7 +18,7 @@ Laravel utilizes [Composer](http://getcomposer.org) to manage its dependencies. You may install Laravel by issuing the Composer `create-project` command in your terminal: - composer create-project laravel/laravel + composer create-project laravel/laravel --prefer-dist ### Via Download @@ -34,13 +34,13 @@ The Laravel framework has a few system requirements: - PHP >= 5.3.7 - MCrypt PHP Extension +As of PHP 5.5, some OS distributions may require you to manually install the PHP JSON extension. When using Ubuntu, this can be done via `apt-get install php5-json`. + ## Configuration Laravel needs almost no configuration out of the box. You are free to get started developing! However, you may wish to review the `app/config/app.php` file and its documentation. It contains several options such as `timezone` and `locale` that you may wish to change according to your application. -> **Note:** One configuration option you should be sure to set is the `key` option within `app/config/app.php`. This value should be set to a 32 character, random string. This key is used when encrypting values, and encrypted values will not be safe until it is properly set. You can set this value quickly by using the following artisan command `php artisan key:generate`. - ### Permissions Laravel requires one set of permissions to be configured - folders within app/storage require write access by the web server. @@ -50,8 +50,6 @@ Laravel requires one set of permissions to be configured - folders within app/st Several of the framework directory paths are configurable. To change the location of these directories, check out the `bootstrap/paths.php` file. -> **Note:** Laravel is designed to protect your application code, and local storage by placing only files that are necessarily public in the public folder. It is recommended that you either set the public folder as your site's documentRoot (also known as a web root) or to place the contents of public into your site's root directory and place all of Laravel's other files outside the web root. - ## Pretty URLs @@ -62,5 +60,6 @@ If the `.htaccess` file that ships with Laravel does not work with your Apache i Options +FollowSymLinks RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] diff --git a/introduction.md b/introduction.md index 1299b56abf6..62a34ad9dea 100644 --- a/introduction.md +++ b/introduction.md @@ -3,7 +3,6 @@ - [Laravel Philosophy](#laravel-philosophy) - [Learning Laravel](#learning-laravel) - [Development Team](#development-team) -- [Framework Sponsors](#framework-sponsors) ## Laravel Philosophy @@ -23,20 +22,13 @@ In addition to this guide, you may wish to check out some [Laravel books](http:/ - [Code Bright](https://leanpub.com/codebright) by Dayle Rees - [Laravel Testing Decoded](https://leanpub.com/laravel-testing-decoded) by Jeffrey Way +- [Laravel: From Apprentice To Artisan](https://leanpub.com/laravel) by Taylor Otwell +- [Implementing Laravel](https://leanpub.com/implementinglaravel) by Chris Fidao +- [Getting Stuff Done With Laravel 4](https://leanpub.com/gettingstuffdonelaravel) by Chuck Heintzelman +- [Laravel 4 Cookbook](https://leanpub.com/laravel4cookbook) by Christopher Pitt +- [Laravel in Action](http://www.manning.com/surguy/) by Maks Surguy ## Development Team -Laravel was created by [Taylor Otwell](https://github.com/taylorotwell), who continues to lead development of the framework. Other prominent community members and contributors include [Dayle Rees](https://github.com/daylerees), [Shawn McCool](https://github.com/ShawnMcCool), [Jeffrey Way](https://github.com/JeffreyWay), [Jason Lewis](https://github.com/jasonlewis), [Ben Corlett](https://github.com/bencorlett), [Franz Liedke](https://github.com/franzliedke), [Dries Vints](https://github.com/driesvints), [Mior Muhammed Zaki](https://github.com/crynobone), and [Phil Sturgeon](https://github.com/philsturgeon). - - -## Framework Sponsors - -The following organizations have made financial contributions to the development of the Laravel framework: - -- [UserScape](http://userscape.com) -- [Cartalyst](http://cartalyst.com) -- [Elli Davis - Toronto Realtor](http://ellidavis.com) -- [Jay Banks - Vancouver Lofts & Condos](http://jaybanks.ca/vancouver-lofts-condos) -- [Julie Kinnear - Toronto MLS](http://juliekinnear.com/toronto-mls-listings) -- [Jamie Sarner - Toronto Real Estate](http://jamiesarner.com) +Laravel was created by [Taylor Otwell](https://github.com/taylorotwell), who continues to lead development of the framework. Other prominent community members and contributors include [Dayle Rees](https://github.com/daylerees), [Shawn McCool](https://github.com/ShawnMcCool), [Jeffrey Way](https://github.com/JeffreyWay), [Jason Lewis](https://github.com/jasonlewis), [Ben Corlett](https://github.com/bencorlett), [Franz Liedke](https://github.com/franzliedke), [Dries Vints](https://github.com/driesvints), [Mior Muhammad Zaki](https://github.com/crynobone), and [Phil Sturgeon](https://github.com/philsturgeon). diff --git a/ioc.md b/ioc.md index 038337d5462..c61cff3e69a 100644 --- a/ioc.md +++ b/ioc.md @@ -165,7 +165,12 @@ The container fires an event each time it resolves an object. You may listen to **Registering A Resolving Listener** - App::resolving(function($object) + App::resolvingAny(function($object) + { + // + }); + + App::resolving('foo', function($foo) { // }); diff --git a/lifecycle.md b/lifecycle.md index fce8d59dfb9..2d2c09a0daa 100644 --- a/lifecycle.md +++ b/lifecycle.md @@ -25,7 +25,7 @@ You may also do pre and post request processing by registering `before`, `after` **Registering Application Events** - App::before(function() + App::before(function($request) { // }); diff --git a/localization.md b/localization.md index 2bb050b9b43..b96e25d1036 100644 --- a/localization.md +++ b/localization.md @@ -4,6 +4,7 @@ - [Language Files](#language-files) - [Basic Usage](#basic-usage) - [Pluralization](#pluralization) +- [Validation Localization](#validation) ## Introduction @@ -49,6 +50,10 @@ The first segment of the string passed to the `get` method is the name of the la > **Note**: If a language line does not exist, the key will be returned by the `get` method. +You may also use the `trans` helper function, which is an alias for the `Lang::get` method. + + echo trans('messages.welcome'); + **Making Replacements In Lines** You may also define place-holders in your language lines: @@ -80,3 +85,9 @@ You may then use the `Lang::choice` method to retrieve the line: Since the Laravel translator is powered by the Symfony Translation component, you may also create more explicit pluralization rules easily: 'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many', + + + +## Validation + +For localization for validation errors and messages, take a look at the documentation on Validation. diff --git a/migrations.md b/migrations.md index 02faec5d15a..21fc089b7e7 100644 --- a/migrations.md +++ b/migrations.md @@ -45,7 +45,7 @@ The `--table` and `--create` options may also be used to indicate the name of th php artisan migrate --package=vendor/package -> **Note:** If you receive a "class not found" error when running migrations, try running the `composer update` command. +> **Note:** If you receive a "class not found" error when running migrations, try running the `composer dump-autoload` command. ## Rolling Back Migrations @@ -97,6 +97,10 @@ To seed your database, you may use the `db:seed` command on the Artisan CLI: php artisan db:seed +By default, the `db:seed` command runs the `DatabaseSeeder` class, which may be used to call other seed classes. However, you may use the `--class` option to specify a specific seeder class to run individually: + + php artisan db:seed --class=UserTableSeeder + You may also seed your database using the `migrate:refresh` command, which will also rollback and re-run all of your migrations: php artisan migrate:refresh --seed diff --git a/packages.md b/packages.md index 2f6ae5ff64c..cda1ac0b08e 100644 --- a/packages.md +++ b/packages.md @@ -38,6 +38,8 @@ Once the `workbench` command has been executed, your package will be available w Once the provider has been registered, you are ready to start developing your package! However, before diving in, you may wish to review the sections below to get more familiar with the package structure and development workflow. +> **Note:** If your service provider cannot be found, run the `php artisan dump-autoload` command from your application's root directory + ## Package Structure @@ -71,6 +73,16 @@ When creating a package using the `workbench`, the `boot` command will already c This method allows Laravel to know how to properly load the views, configuration, and other resources for your application. In general, there should be no need for you to change this line of code, as it will setup the package using the workbench conventions. +By default, after registering a package, its resources will be available using the "package" half of `vendor/package`. However, you may pass a second argument into the `package` method to override this behavior. For example: + + // Passing custom namespace to package method + $this->package('vendor/package', 'custom-namespace'); + + // Package resources now accessed via custom-namespace + $view = View::make('custom-namespace::foo'); + +There is not a "default location" for service provider classes. You may put them anywhere you like, perhaps organizing them in a `Providers` namespace within your `app` directory. The file may be placed anywhere, as long as Composer's [auto-loading facilities](http://getcomposer.org/doc/01-basic-usage.md#autoloading) know how to load the class. + ## Package Conventions @@ -95,6 +107,12 @@ After the `workbench` command has created your package. You may `git init` from Since your packages are in the `workbench` directory, you may be wondering how Composer knows to autoload your package's files. When the `workbench` directory exists, Laravel will intelligently scan it for packages, loading their Composer autoload files when the application starts! +If you need to regenerate your package's autoload files, you may use the `php artisan dump-autoload` command. This command will regenerate the autoload files for your root project, as well as any workbenches you have created. + +**Running The Artisan Autoload Command** + + php artisan dump-autoload + ## Package Routing @@ -126,6 +144,18 @@ However, if your package contains a single configuration file, you may simply na Config::get('package::option'); +Sometimes, you may wish to register package resources such as views outside of the typical `$this->package` method. Typically, this would only be done if the resources were not in a conventional location. To register the resources manually, you may use the `addNamespace` method of the `View`, `Lang`, and `Config` classes: + +**Registering A Resource Namespace Manually** + + View::addNamespace('package', __DIR__.'/path/to/views'); + +Once the namespace has been registered, you may use the namespace name and the "double colon" syntax to access the resources: + + return View::make('package::view.name'); + +The method signature for `addNamespace` is identical on the `View`, `Lang`, and `Config` classes. + ### Cascading Configuration Files When other developers install your package, they may wish to override some of the configuration options. However, if they change the values in your package source code, they will be overwritten the next time Composer updates the package. Instead, the `config:publish` artisan command should be used: diff --git a/pagination.md b/pagination.md index 4456ea422d8..26b9b8708fc 100644 --- a/pagination.md +++ b/pagination.md @@ -24,7 +24,9 @@ You may also paginate [Eloquent](/docs/eloquent) models: **Paginating An Eloquent Model** - $users = User::where('votes', '>', 100)->paginate(15); + $allUsers = User::paginate(15); + + $someUsers = User::where('votes', '>', 100)->paginate(15); The argument passed to the `paginate` method is the number of items you wish to display per page. Once you have retrieved the results, you may display them on your view, and create the pagination links using the `links` method: @@ -46,6 +48,7 @@ You may also access additional pagination information via the following methods: - `getTotal` - `getFrom` - `getTo` +- `count` Sometimes you may wish to create a pagination instance manually, passing it an array of items. You may do so using the `Paginator::make` method: @@ -53,6 +56,16 @@ Sometimes you may wish to create a pagination instance manually, passing it an a $paginator = Paginator::make($items, $totalItems, $perPage); +**Customizing The Paginator URI** + +You may also customize the URI used by the paginator via the `setBaseUrl` method: + + $users = User::paginate(); + + $users->setBaseUrl('custom/url'); + +The example above will create URLs like the following: http://example.com/custom/url?page=2 + ## Appending To Pagination Links @@ -62,4 +75,4 @@ You can add to the query string of pagination links using the `appends` method o This will generate URLs that look something like this: - http://example.com/something?page=2&sort=votes \ No newline at end of file + http://example.com/something?page=2&sort=votes diff --git a/queries.md b/queries.md index 82a2ec65409..2f709bc60b5 100644 --- a/queries.md +++ b/queries.md @@ -116,6 +116,12 @@ The query builder may also be used to write join statements. Take a look at the ->join('orders', 'users.id', '=', 'orders.user_id') ->select('users.id', 'contacts.phone', 'orders.price'); +**Left Join Statement** + + DB::table('users') + ->leftJoin('posts', 'users.id', '=', 'posts.user_id') + ->get(); + You may also specify more advanced join clauses: DB::table('users') @@ -197,8 +203,16 @@ Sometimes you may need to use a raw expression in a query. These expressions wil DB::table('users')->increment('votes'); + DB::table('users')->increment('votes', 5); + DB::table('users')->decrement('votes'); + DB::table('users')->decrement('votes', 5); + +You may also specify additional columns to update: + + DB::table('users')->increment('votes', 1, array('name' => 'John')); + ## Inserts @@ -271,4 +285,4 @@ You may easily cache the results of a query using the `remember` method: $users = DB::table('users')->remember(10)->get(); -In this example, the results of the query will be cached for ten minutes. While the results are cached, the query will not be run against the database, and the results will be loaded from the default cache driver specified for your application. \ No newline at end of file +In this example, the results of the query will be cached for ten minutes. While the results are cached, the query will not be run against the database, and the results will be loaded from the default cache driver specified for your application. diff --git a/queues.md b/queues.md index 61f89059cb8..0892b8e5148 100644 --- a/queues.md +++ b/queues.md @@ -129,6 +129,10 @@ You may also set the length of time (in seconds) each job should be allowed to r php artisan queue:listen --timeout=60 +In addition, you may specify the number of seconds to wait before polling for new jobs: + + php artisan queue:listen --sleep=5 + To process only the first job on the queue, you may use the `queue:work` command: **Processing The First Job On The Queue** diff --git a/quick.md b/quick.md index c70527da35a..a3c47f7ae6b 100644 --- a/quick.md +++ b/quick.md @@ -10,11 +10,23 @@ ## Installation -To install the Laravel framework, you may issue the following command from your terminal: +The Laravel framework utilizes [Composer](http://getcomposer.org) for installation and dependency management. If you haven't already, start by [installing Composer](http://getcomposer.org/doc/00-intro.md). - composer create-project laravel/laravel your-project-name +Now you can install Laravel by issuing the following command from your terminal: -Or, you may also download a copy of the [repository from Github](https://github.com/laravel/laravel/archive/master.zip). Next, after [installing Composer](http://getcomposer.org), run the `composer install` command in the root of your project directory. This command will download and install the framework's dependencies. + composer create-project laravel/laravel your-project-name --prefer-dist + +This command will download and install a fresh copy of Laravel in a new `your-project-name` folder within your current directory. + +If you prefer, you can alternatively download a copy of the [Laravel repository from Github](https://github.com/laravel/laravel/archive/master.zip) manually. Next run the `composer install` command in the root of your manually created project directory. This command will download and install the framework's dependencies. + + +### Permissions + +After installing Laravel, you may need to grant the web server write permissions to the `app/storage` directories. See the [Installation](/docs/installation) documentation for more details on configuration. + + +### Directory Structure After installing the framework, take a glance around the project to familiarize yourself with the directory structure. The `app` directory contains folders such as `views`, `controllers`, and `models`. Most of your application's code will reside somewhere in this directory. You may also wish to explore the `app/config` directory and the configuration options that are available to you. @@ -73,7 +85,7 @@ Wonderful! Now you have setup a simple view that extends a layout. Next, let's s To create a table to hold our data, we'll use the Laravel migration system. Migrations let you expressively define modifications to your database, and easily share them with the rest of your team. -First, let's configure a database connection. You may configure all of your database connections from the `app/config/database.php` file. By default, Laravel is configured to use SQLite, and an SQLite database is included in the `app/database` directory. If you wish, you may change the `driver` option to `mysql` and configure the `mysql` connection credentials within the database configuration file. +First, let's configure a database connection. You may configure all of your database connections from the `app/config/database.php` file. By default, Laravel is configured to use MySQL, and you will need to supply connection credentials within the database configuration file. If you wish, you may change the `driver` option to `sqlite` and it will use the SQLite database included in the `app/database` directory. Next, to create the migration, we'll use the [Artisan CLI](/docs/artisan). From the root of your project, run the following from your terminal: diff --git a/redis.md b/redis.md index a572faff126..8560f4a4c94 100644 --- a/redis.md +++ b/redis.md @@ -29,6 +29,8 @@ The default server configuration should suffice for development. However, you ar The `cluster` option will tell the Laravel Redis client to perform client-side sharding across your Redis nodes, allowing you to pool nodes and create a large amount of available RAM. However, note that client-side sharding does not handle failover; therefore, is primarily suited for cached data that is available from another primary data store. +If your Redis server requires authentication, you may supply a password by adding a `password` key / value pair to your Redis server configuration array. + ## Usage diff --git a/requests.md b/requests.md index 20341878db3..26cb989ec36 100644 --- a/requests.md +++ b/requests.md @@ -36,7 +36,11 @@ You may access all user input with a few simple methods. You do not need to worr $input = Input::except('credit_card'); -Some JavaScript libraries such as Backbone may send input to the application as JSON. You may access this data via `Input::get` like normal. +When working on forms with "array" inputs, you may use dot notation to access the arrays: + + $input = Input::get('products.0.name'); + +> **Note:** Some JavaScript libraries such as Backbone may send input to the application as JSON. You may access this data via `Input::get` like normal. ## Cookies @@ -53,6 +57,12 @@ All cookies created by the Laravel framework are encrypted and signed with an au $response->withCookie(Cookie::make('name', 'value', $minutes)); +**Queueing A Cookie For The Next Response** + +If you would like to set a cookie before a response has been created, use the `Cookie::queue()` method. The cookie will automatically be attached to the final response from your application. + + Cookie::queue($name, $value, $minutes); + **Creating A Cookie That Lasts Forever** $cookie = Cookie::forever('name', 'value'); @@ -110,6 +120,14 @@ The object returned by the `file` method is an instance of the `Symfony\Componen $path = Input::file('photo')->getRealPath(); +**Retrieving The Original Name Of An Uploaded File** + + $name = Input::file('photo')->getClientOriginalName(); + +**Retrieving The Extension Of An Uploaded File** + + $extension = Input::file('photo')->getClientOriginalExtension(); + **Retrieving The Size Of An Uploaded File** $size = Input::file('photo')->getSize(); @@ -149,6 +167,13 @@ The `Request` class provides many methods for examining the HTTP request for you **Retrieving Values From $_SERVER** $value = Request::server('PATH_INFO'); + +**Determining If The Request Is Over HTTPS** + + if (Request::secure()) + { + // + } **Determine If The Request Is Using AJAX** @@ -157,9 +182,9 @@ The `Request` class provides many methods for examining the HTTP request for you // } -**Determining If The Request Is Over HTTPS** +**Detect any type of JSON request** - if (Request::secure()) + if (Request::ajax() or Request::isJson() or Request::wantsJson()) { // } diff --git a/responses.md b/responses.md index 4655e4da85d..24ae23317a3 100644 --- a/responses.md +++ b/responses.md @@ -39,6 +39,12 @@ A `Response` instance inherits from the `Symfony\Component\HttpFoundation\Respon return Redirect::to('user/login'); +**Returning A Redirect With Flash Data** + + return Redirect::to('user/login')->with('message', 'Login Failed'); + +> **Note:** Since the `with` method flashes data to the session, you may retrieve the data using the typical `Session::get` method. + **Returning A Redirect To A Named Route** return Redirect::route('login'); @@ -89,12 +95,14 @@ The second argument passed to `View::make` is an array of data that should be ma **Passing Data To Views** - $view = View::make('greeting', $data); - $view = View::make('greeting')->with('name', 'Steve'); In the example above the variable `$name` would be accessible from the view, and would contain `Steve`. +If you wish, you may pass an array of data as the second parameter given to the `make` method: + + $view = View::make('greetings', $data); + You may also share a piece of data across all views: View::share('name', 'Steve'); @@ -119,7 +127,7 @@ The sub-view can then be rendered from the parent view: ## View Composers -View composers are callbacks or class methods that are called when a view is created. If you have data that you want bound to a given view each time that view is created throughout your application, a view composer can organize that code into a single location. Therefore, view composers may function like "view models" or "presenters". +View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want bound to a given view each time that view is rendered throughout your application, a view composer can organize that code into a single location. Therefore, view composers may function like "view models" or "presenters". **Defining A View Composer** @@ -128,7 +136,7 @@ View composers are callbacks or class methods that are called when a view is cre $view->with('count', User::count()); }); -Now each time the `profile` view is created, the `count` data will be bound to the view. +Now each time the `profile` view is rendered, the `count` data will be bound to the view. You may also attach a view composer to multiple views at once: @@ -154,6 +162,15 @@ A view composer class should be defined like so: Note that there is no convention on where composer classes may be stored. You are free to store them anywhere as long as they can be autoloaded using the directives in your `composer.json` file. +### View Creators + +View **creators** work almost exactly like view composers; however, they are fired immediately when the view is instantiated. To register a view creator, simple use the `creator` method: + + View::creator('profile', function($view) + { + $view->with('count', User::count()); + }); + ## Special Responses diff --git a/routing.md b/routing.md index 522e55fb578..c9448a1ffb8 100644 --- a/routing.md +++ b/routing.md @@ -84,6 +84,23 @@ Often, you will need to generate URLs to your routes, you may do so using the `U }) ->where('id', '[0-9]+'); +Of course, you may pass an array of constraints when necessary: + + Route::get('user/{id}/{name}', function($id, $name) + { + // + }) + ->where(array('id' => '[0-9]+', 'name' => '[a-z]+')) + +If you would like a route parameter to always be constrained by a given regular expression, you may use the `pattern` method: + + Route::pattern('id', '[0-9]+'); + + Route::get('user/{id}', function($id) + { + // Only called if {id} is numeric. + }); + ## Route Filters @@ -108,6 +125,10 @@ If a response is returned from a filter, that response will be considered the re return 'You are over 200 years old!'; })); +**Attaching A Filter To A Controller Action** + + Route::get('user', array('before' => 'old', 'uses' => 'UserController@showProfile')); + **Attaching Multiple Filters To A Route** Route::get('user', array('before' => 'auth|old', function() @@ -228,6 +249,7 @@ Laravel routes are also able to handle wildcard sub-domains, and pass you wildca }); }); + ## Route Prefixing diff --git a/schema.md b/schema.md index be701864eaf..1306953941c 100644 --- a/schema.md +++ b/schema.md @@ -36,7 +36,7 @@ To specify which connection the schema operation should take place on, use the ` Schema::connection('foo')->create('users', function($table) { - $table->increments('id'): + $table->increments('id'); }); To drop a table, you may use the `Schema::drop` method: @@ -60,12 +60,14 @@ The table builder contains a variety of column types that you may use when build Command | Description ------------- | ------------- `$table->increments('id');` | Incrementing ID to the table (primary key). +`$table->bigIncrements('id');` | Incrementing ID using a "big integer" equivalent. `$table->string('email');` | VARCHAR equivalent column `$table->string('name', 100);` | VARCHAR equivalent with a length `$table->integer('votes');` | INTEGER equivalent to the table `$table->bigInteger('votes');` | BIGINT equivalent to the table `$table->smallInteger('votes');` | SMALLINT equivalent to the table `$table->float('amount');` | FLOAT equivalent to the table +`$table->double('column', 15, 8);` | DOUBLE equivalent with precision `$table->decimal('amount', 5, 2);` | DECIMAL equivalent with a precision and scale `$table->boolean('confirmed');` | BOOLEAN equivalent to the table `$table->date('created_at');` | DATE equivalent to the table diff --git a/security.md b/security.md index a5d3db4923d..ddb589e7cd7 100644 --- a/security.md +++ b/security.md @@ -179,6 +179,11 @@ You may also use HTTP Basic Authentication without setting a user identifier coo return Auth::onceBasic(); }); +If you are using PHP FastCGI, HTTP Basic authentication will not work correctly by default. The following lines should be added to your `.htaccess` file: + + RewriteCond %{HTTP:Authorization} ^(.+)$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + ## Password Reminders & Reset @@ -262,7 +267,11 @@ Again, notice we are using the `Session` to display any errors that may be detec Route::post('password/reset/{token}', function() { - $credentials = array('email' => Input::get('email')); + $credentials = array( + 'email' => Input::get('email'), + 'password' => Input::get('password'), + 'password_confirmation' => Input::get('password_confirmation') + ); return Password::reset($credentials, function($user, $password) { @@ -276,6 +285,8 @@ Again, notice we are using the `Session` to display any errors that may be detec If the password reset is successful, the `User` instance and the password will be passed to your Closure, allowing you to actually perform the save operation. Then, you may return a `Redirect` or any other type of response from the Closure which will be returned by the `reset` method. Note that the `reset` method automatically checks for a valid `token` in the request, valid credentials, and matching passwords. +By default, password reset tokens expire after one hour. You may change this via the `reminder.expire` option of your `app/config/auth.php` file. + Also, similarly to the `remind` method, if an error occurs while resetting the password, the `reset` method will return a `Redirect` to the current URI with an `error` and `reason`. @@ -297,6 +308,6 @@ You may also set the cipher and mode used by the encrypter: **Setting The Cipher & Mode** - Crypt::setMode('crt'); + Crypt::setMode('ctr'); Crypt::setCipher($cipher); diff --git a/session.md b/session.md index 64cd7e40a8e..ed8994eb419 100644 --- a/session.md +++ b/session.md @@ -4,6 +4,7 @@ - [Session Usage](#session-usage) - [Flash Data](#flash-data) - [Database Sessions](#database-sessions) +- [Session Drivers](#session-drivers) ## Configuration @@ -19,6 +20,10 @@ The session configuration is stored in `app/config/session.php`. Be sure to revi Session::put('key', 'value'); +**Push A Value Onto An Array Session Value** + + Session::push('user.teams', 'developers'); + **Retrieving An Item From The Session** $value = Session::get('key'); @@ -29,6 +34,10 @@ The session configuration is stored in `app/config/session.php`. Be sure to revi $value = Session::get('key', function() { return 'default'; }); +**Retrieving All Data From The Session** + + $data = Session::all(); + **Determining If An Item Exists In The Session** if (Session::has('users')) @@ -81,4 +90,17 @@ Of course, you may use the `session:table` Artisan command to generate this migr composer dump-autoload - php artisan migrate \ No newline at end of file + php artisan migrate + + +## Session Drivers + +The session "driver" defines where session data will be stored for each request. Laravel ships with several great drivers out of the box: + +- `native` - sessions will be handled by internal PHP session facilities. +- `cookie` - sessions will be stored in secure, encrypted cookies. +- `database` - sessions will be stored in a database used by your application. +- `memcached` / `redis` - sessions will be stored in one of these fast, cached based stores. +- `array` - sessions will be stored in a simple PHP array and will not be persisted across requests. + +> **Note:** The array driver is typically used for running [unit tests](/docs/testing), so no session data will be persisted. \ No newline at end of file diff --git a/templates.md b/templates.md index b89a828312d..d3b30d9ba8d 100644 --- a/templates.md +++ b/templates.md @@ -65,6 +65,10 @@ Blade is a simple, yet powerful templating engine provided with Laravel. Unlike Note that views which `extend` a Blade layout simply override sections from the layout. Content of the layout can be included in a child view using the `@parent` directive in a section, allowing you to append to the contents of a layout section such as a sidebar or footer. +Sometimes, such as when you are not sure if a section has been defined, you may wish to pass a default value to the `@yield` directive. You may pass the default value as the second argument: + + @yield('section', 'Default Content'); + ## Other Blade Control Structures @@ -74,10 +78,18 @@ Note that views which `extend` a Blade layout simply override sections from the The current UNIX timestamp is {{ time() }}. -To escape the output, you may use the triple curly brace syntax: +If you need to display a string that is wrapped in curly braces, you may escape the Blade behavior by prefixing your text with an `@` symbol: + +**Displaying Raw Text With Curly Braces** + + @{{ This will not be processed by Blade }} + +Of course, all user supplied data should be escaped or purified. To escape the output, you may use the triple curly brace syntax: Hello, {{{ $name }}}. +> **Note:** Be very careful when echoing content that is supplied by users of your application. Always use the triple curly brace syntax to escape any HTML entities in the content. + **If Statements** @if (count($records) === 1) @@ -109,6 +121,20 @@ To escape the output, you may use the triple curly brace syntax: **Including Sub-Views** @include('view.name') + +You may also pass an array of data to the included view: + + @include('view.name', array('some'=>'data')) + +**Overwriting Sections** + +By default, sections are appended to any previous content that exists in the section. To overwrite a section entirely, you may use the `overwrite` statement: + + @extends('list.item.container') + + @section('list.item.content') +

    This is an item of type {{ $item->type }}

    + @overwrite **Displaying Language Lines** diff --git a/testing.md b/testing.md index 0a5d1ee1056..1985242a8c0 100644 --- a/testing.md +++ b/testing.md @@ -73,6 +73,8 @@ To call a HTTPS route, you may use the `callSecure` method: $response = $this->callSecure('GET', 'foo/bar'); +> **Note:** Route filters are disabled when in the testing environment. To enable them, add `Route::enableFilters()` to your test. + ### DOM Crawler You may also call a route and receive a DOM Crawler instance that you may use to inspect the content: @@ -103,7 +105,7 @@ We can mock the call to the `Event` class by using the `shouldReceive` method on public function testGetIndex() { - Event::shouldReceive('fire')->once()->with(array('name' => 'Dayle')); + Event::shouldReceive('fire')->once()->with('foo', array('name' => 'Dayle')); $this->call('GET', '/'); } diff --git a/validation.md b/validation.md index 6898ba36ffb..732fcea6784 100644 --- a/validation.md +++ b/validation.md @@ -4,6 +4,7 @@ - [Working With Error Messages](#working-with-error-messages) - [Error Messages & Views](#error-messages-and-views) - [Available Validation Rules](#available-validation-rules) +- [Conditionally Adding Rules](#conditionally-adding-rules) - [Custom Error Messages](#custom-error-messages) - [Custom Validation Rules](#custom-validation-rules) @@ -29,6 +30,21 @@ Multiple rules may be delimited using either a "pipe" character, or as separate array('name' => 'Dayle'), array('name' => array('required', 'min:5')) ); + +**Validating Multiple Fields** + + $validator = Validator::make( + array( + 'name' => 'Dayle', + 'password' => 'lamepassword', + 'email' => 'email@example.com' + ), + array( + 'name' => 'required', + 'password' => 'required|min:8', + 'email' => 'required|email|unique:users' + ) + ); Once a `Validator` instance has been created, the `fails` (or `passes`) method may be used to perform the validation. @@ -154,6 +170,7 @@ Below is a list of all available validation rules and their function: - [Required](#rule-required) - [Required If](#rule-required-if) - [Required With](#rule-required-with) +- [Required Without](#rule-required-without) - [Same](#rule-same) - [Size](#rule-size) - [Unique (Database)](#rule-unique) @@ -227,7 +244,7 @@ The field under validation must be formatted as an e-mail address. #### exists:_table_,_column_ -The field under validation must exists on a given database table. +The field under validation must exist on a given database table. **Basic Usage Of Exists Rule** @@ -312,6 +329,11 @@ The field under validation must be present if the _field_ field is equal to _val The field under validation must be present _only if_ the other specified fields are present. + +#### required_without:_foo_,_bar_,... + +The field under validation must be present _only when_ the other specified fields are not present. + #### same:_field_ @@ -339,11 +361,45 @@ The field under validation must be unique on a given database table. If the `col 'email' => 'unique:users,email_address,10' +**Adding Additional Where Clauses** + +You may also specify more conditions that will be added as "where" clauses to the query: + + 'email' => 'unique:users,email_address,NULL,id,account_id,1' + +In the rule above, only rows with an `account_id` of `1` would be included in the unique check. + #### url The field under validation must be formatted as an URL. + +## Conditionally Adding Rules + +Sometimes you may wish to require a given field only if another field has a greater value than 100. Or you may need two fields to have a given value only when another field is present. Adding these validation rules doens't have to be a pain. First, create a `Validator` instance with your _static rules_ that never change: + + $v = Validator::make($data, array( + 'email' => 'required|email', + 'games' => 'required|numeric', + )); + +Let's assume our web application is for game collectors. If a game collector registers with our application and they own more than 100 games, we want them to explain why they own so many games. For example, perhaps they run a game re-sell shop, or maybe they just enjoy collecting. To conditionally add this requirement, we can use the `sometimes` method on the `Validator` instance. + + $v->sometimes('reason', 'required|max:500', function($input) + { + return $input->games >= 100; + }); + +The first argument passed to the `sometimes` method is the name of the field we are conditionally validating. The second argument is the rules we want to add. If the `Closure` passed as the third argument returns `true`, the rules will be added. This method makes it a breeze to build complex conditional validations. You may even add conditional validations for several fields at once: + + $v->sometimes(array('reason', 'cost'), 'required', function($input) + { + return $input->games >= 100; + }); + +> **Note:** The `$input` parameter passed to your `Closure` will be an instance of `Illuminate\Support\Fluent` and may be used as an object to access your input and files. + ## Custom Error Messages @@ -378,6 +434,7 @@ Sometimes you may wish to specify a custom error messages only for a specific fi In some cases, you may wish to specify your custom messages in a language file instead of passing them directly to the `Validator`. To do so, add your messages to `custom` array in the `app/lang/xx/validation.php` language file. + **Specifying Custom Messages In Language Files** 'custom' => array( @@ -398,8 +455,6 @@ Laravel provides a variety of helpful validation rules; however, you may wish to return $value == 'foo'; }); -> **Note:** The name of the rule passed to the `extend` method must be "snake cased". - The custom validator Closure receives three arguments: the name of the `$attribute` being validated, the `$value` of the attribute, and an array of `$parameters` passed to the rule. You may also pass a class and method to the `extend` method instead of a Closure: