一、建立短网址网站

在整本书中,我们将使用 Laravel PHP 框架来构建不同类型的网络项目。

在这一章中,我们将看到如何用 Laravel 框架的基础知识构建一个 URL Shortener 网站。涵盖的主题包括:

  • 创建一个数据库并迁移我们的网址缩写表
  • 创建我们的表单
  • 创建我们的链接模型
  • 将数据保存到数据库
  • 从数据库中获取单个网址并重定向

创建数据库并迁移我们的网址缩写表

迁移就像应用数据库的版本控制。它们允许团队(或您自己)修改数据库模式,并提供关于当前模式状态的最新信息。要创建和迁移您的网址浏览器数据库,请执行以下步骤:

  1. 首先,我们要创建一个数据库,并定义到 Laravel 的连接信息。为此,我们打开app/config下的database.php文件,然后填写所需的凭据。默认情况下,Laravel 支持 MySQL、SQLite、PostgreSQL 和 SQLSRV(微软 SQL Server)。在本教程中,我们将使用 MySQL。
  2. 我们将不得不创建一个 MySQL 数据库。为此,打开您的 MySQL 控制台(或 phpMyAdmin),并写下以下查询:

    ```php CREATE DATABASE urls

    ```

  3. 前面的命令会为我们生成一个名为urls的新 MySQL 数据库。成功生成数据库后,我们将定义数据库凭据。为此,请在app/config下打开您的database.php文件。在该文件中,您将看到多个数组与数据库定义一起返回。

  4. The default key defines what database driver to be used, and each database driver key holds individual credentials. We just need to fill the one that we will be using. In our case, I've made sure that the default key's value is mysql. To set the connection credentials, we will be filling the value of the mysql key with our database name, username, and password. In our case, since we have a database named urls, with the username as root and without a password, our mysql connection settings in the database.php file will be as follows:

    php 'mysql' => array( 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'database', 'username' => 'root', 'password' => '', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ),

    类型

    您可以从您在http://www.packtpub.com的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问http://www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

  5. Now, we will be using the Artisan CLI to create migrations. Artisan is a command-line interface specially made for Laravel. It provides numerous helpful commands to help us in development. We'll be using the following migrate:make command to create a migration on Artisan:

    ```php php artisan migrate:make create_links_table --table=links --create

    ```

    该命令有两个部分:

    • 首先是migrate:make create_links_table。该命令的这一部分创建了一个名为2013_05_01_194506_create_links_table.php的迁移文件。我们将进一步检查文件。
    • 命令的第二部分是--table=links --create
      • --table=links选项指向数据库名称。
      • --create选项用于在数据库服务器上创建表,我们已经给了--table=links选项。
    • 正如你看到的,不像 Laravel 3,当你运行前面的命令时,它会创建迁移表和我们的迁移。可以在app/database/migrations下访问迁移文件,代码如下:

    php <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateLinksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('links', function(Blueprint $table) { $table->increments('id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('links'); } }

  6. Let's inspect the sample migration file. There are two public functions which are declared as up() and down(). When you execute the following migrate command, the contents of the up() function will be executed:

    ```php php artsian migrate

    ```

    在我们的例子中,这个命令将执行所有的迁移并创建links表。

    如果运行迁移文件时收到class not found错误,请尝试运行composer update命令。

  7. 我们也可以回滚到上一次迁移,就像从一开始就没有执行过一样。我们可以通过使用以下命令来做到这一点:

    ```php php artisan migrate:rollback

    ```

  8. 在某些情况下,我们可能还希望回滚所有已创建的迁移。这可以通过以下命令完成:

    ```php php artisan migrate:reset

    ```

  9. 而在开发阶段,我们可能会忘记添加/删除一些字段,甚至忘记创建一些表,我们可能想要回滚所有内容并重新混合它们。这可以使用以下命令完成:

    ```php php artisan migrate:refresh

    ```

  10. 现在,让我们添加我们的字段。我们已经创建了两个名为urlhash的额外字段。url字段将保存实际的网址,出现在hash字段中的网址将被重定向到该网址。hash字段将保存url字段中出现的网址的缩短版本。迁移文件的最终内容如下代码所示:

    php <?php use Illuminate\Database\Migrations\Migration; class CreateLinksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('links', function(Blueprint $table) { $table->increments('id'); $table->text('url'); $table->string('hash',400); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('links'); } }

创造我们的形态

现在让我们制作第一个表单视图。

  1. 将以下代码保存为app/views下的form.blade.php。文件的扩展名是blade.php,因为我们将受益于 Laravel 4 的内置模板引擎 Blade 。表格中可能有一些方法你还不明白,但不用担心。我们将在本章中介绍有关此表单的所有内容。

    ```php <!DOCTYPE html>

    URL Shortener

    Uber-Shortener

    {{Form::open(array('url'=>'/','method'=>'post'))}}
      {{Form::text('link',Input::old('link'),array('placeholder'=>'Insert your URL here and press enter!'))}}
      {{Form::close()}}
    </div>
    

    ```

  2. Now save the following codes as styles.css under public/img/css:

    php div#container{padding-top:100px; text-align:center; width:75%; margin:auto; border-radius:4px} div#container h2{font-family:Arial,sans-serif; font-size:28px; color:#555} div#container h3{font-family:Arial,sans-serif; font-size:28px} div#container h3.error{color:#a00} div#container h3.success{color:#0a0} div#container input{display:block; width:90%; float:left; font-size:24px; border-radius:5px} div#error,div#success{border-radius:3px; display:block; width:90%; padding:10px} div#error{background:#ff8080; border:1px solid red} div#success{background:#80ff80; border:1px solid #0f0}

    这个代码会给你生成一个如下图所示的表单:

    Creating our form

    可以看到,我们用了一个 CSS 文件对表单进行了一点整理,但是表单的实际部分在View文件的底部,在div里面有容器的 ID。

  3. 我们使用了 Laravel 的内置Form类来生成表单,我们使用了Input库的old()方法。现在让我们挖掘代码:

    • Form::open():创建<form>开始标记。在第一个提供的参数中,您可以定义表单的发送方式和发送位置。它可以是控制器的动作、直接的网址或命名的路由。
    • Form::text():创建一个<input>标签,类型为文本。第一个参数是输入的名称,第二个参数是输入的值,在第三个参数给出的数组中,可以定义<input>标签的资产和其他属性。
    • Input::old():它会从一个表单返回旧的输入,在表单与输入一起返回之后。第一个参数是提交的旧输入的名称。在我们的例子中,如果表单在提交后返回(例如,如果表单验证失败),文本字段将填充我们的旧输入,我们可以在以后的请求中重用它。
    • Form::close():关闭 <form>标签。
  4. 创建我们的链接模型

    为了让从 Laravel 的名为Eloquent的 ORM 类中受益,我们需要定义一个模型。将以下代码保存为app/models下的Link.php:

    <?php
    class Link extends Eloquent {
      protected $table = 'links';
      protected $fillable = array('url','hash');
      public $timestamps = false;
    }
    

    雄辩模型很容易理解。

    • 变量$table用于定义模型的表名,但不是强制的。即使我们不定义这个变量,它也会使用模型名的复数形式作为数据库表名。例如,如果模型名是 post,它将默认查找 post 的表。这样,您可以为表使用任何模型名称。
    • 受保护的$fillable变量定义了可以(大量)创建和更新哪些列。默认情况下,Laravel 4 使用Eloquent阻止所有列的值的质量分配。这样,例如,如果您有一个users表,并且您是唯一的用户/管理员,批量分配可以保护您的数据库免受其他用户的添加。
    • 公共$timestamps变量检查模型在分别创建和更新查询时是否应该尝试默认设置时间戳created_atupdated_at。由于我们的章节不需要这些功能,我们将通过将该值设置为false来禁用它。

    我们现在需要定义这个视图来显示我们是否可以导航到我们虚拟主机的索引页面。您可以从routes.php中定义的控制器或直接从routes.php中进行。由于我们的应用很小,直接从routes.php定义它们就足够了。要对此进行定义,请打开app文件夹下的routes.php文件,并添加以下代码:

    Route::get('/', function()  
    {
      return View::make('form');
    });
    

    如果您已经有一个以Route::get('/', function()开头的部分,您应该用之前的代码替换该部分。

    Laravel 可以监听getpostputdelete请求。由于我们的动作是一个get动作(因为我们将在浏览器中导航而不发布),我们的路线类型将是get,并且因为我们想要在根页面上显示视图,我们的Route::get()方法的第一个参数将是/,并且我们包装一个闭包函数作为第二个参数来定义我们想要做什么。在我们的例子中,我们将返回之前生成的app/views下的form.blade.php,所以我们只需键入return View::make('form')。该方法从views文件夹返回form.blade.php视图。

    如果视图在子目录中,它将被称为subfolder.form

    将数据保存到数据库

    现在我们需要写一条路线,这条路线将不得不听从我们post的要求。为此,我们在app文件夹下打开routes.php文件,并添加以下代码:

    Route::post('/',function(){
      //We first define the Form validation rule(s)
      $rules = array(
        'link' => 'required|url'
      );
      //Then we run the form validation
      $validation = Validator::make(Input::all(),$rules);
      //If validation fails, we return to the main page with an error info
      if($validation->fails()) {
        return Redirect::to('/')
        ->withInput()
        ->withErrors($validation);
      } else {
        //Now let's check if we already have the link in our database. If so, we get the first result
        $link = Link::where('url','=',Input::get('link'))
        ->first();
        //If we have the URL saved in our database already, we provide that information back to view.
        if($link) {
          return Redirect::to('/')
          ->withInput()
          ->with('link',$link->hash);
          //Else we create a new unique URL
        } else {
          //First we create a new unique Hash
          do {
            $newHash = Str::random(6);
          } while(Link::where('hash','=',$newHash)->count() > 0);
    
          //Now we create a new database record
          Link::create(array('url' => Input::get('link'),'hash' => $newHash
          ));
    
          //And then we return the new shortened URL info to our action
          return Redirect::to('/')
          ->withInput()
          ->with('link',$newHash);
        }
      }
    });
    

    验证用户的输入

    使用我们现在编码的post动作功能,我们将使用 Laravel 内置的Validation类来验证用户的输入。这个类帮助我们防止无效输入进入我们的数据库。

    我们首先定义一个$rules数组来设置每个字段的规则。在我们的例子中,我们希望链接具有有效的网址格式。然后我们可以使用Validator::make()方法运行表单验证,并将其分配给$validation变量。让我们了解一下的参数Validator::make()方法:

    • Validator::make()方法的第一个参数采用一组要验证的输入和值。在我们的例子中,整个表单只有一个名为 link 的字段,所以我们放了Input::all()方法,它返回表单的所有输入。
    • 第二个参数接受要检查的验证规则。存储的$validation变量为我们保存了一些信息。例如,我们可以检查验证是失败还是通过(使用$validation->fails()$validation->passes())。这两个方法返回布尔结果,所以我们可以很容易地检查验证是通过还是失败。此外,$validation变量保存了一个方法messages(),其中包含验证失败的信息。我们可以用$validation->messages()抓住他们。

    如果表单验证失败,我们将用户重定向回我们的索引页面(return Redirect::to('/')),该页面保存着 URL shortener 表单,我们将一些 flash 数据返回给表单。在 Laravel 中,我们通过将withVariableName对象添加到重定向页面来实现这一点。在这里使用with是强制性的,这将告诉拉弗尔我们正在返回一些额外的东西。我们可以这样做来重定向和创建视图。如果我们正在制作视图并向最终用户显示一些内容,那些withVariableName将是变量,我们可以使用$VariableName直接调用它们,但是如果我们重定向到具有withVariableName对象的页面,VariableName将是闪存会话数据,我们可以使用Session类(Session::get('VariableName'))调用它。

    在我们的例子中,为了返回错误,我们使用了一种特殊的方法withErrors($validation),而不是返回$validation->messages()。我们也可以使用它返回,但是$errors变量总是在视图上定义的,所以我们可以直接使用$validation变量作为withErrors()的参数。 withInput()方法也是一种特殊的方法,会将结果返回到表单中。

    //If validation fails, we return to the main page with an error info
    if($validation->fails()) {
      return Redirect::to('/')
      ->withInput()
      ->withErrors($validation);
    }
    

    如果用户忘记了表单中的一个字段,如果验证失败并再次显示带有错误信息的表单,使用withInput()方法,表单可以再次填充旧的输入。为了在 Laravel 中显示这些旧的输入,我们使用了Input类的old()方法。例如,Input::old('link')将返回给我们名为link的表单域的旧输入。

    将消息返回视图

    要将错误消息返回到表单中,我们可以在form.blade.php中添加以下 HTML 代码:

    @if(Session::has('errors'))
    <h3 class="error">{{$errors->first('link')}}</h3>
    @endif
    

    正如您已经猜到的那样,Session::has('variableName')返回一个布尔值来检查会话是否有变量名。然后,使用 Laravel 的Validator类的first('formFieldName')方法,我们返回了表单域的第一条错误消息。在我们的例子中,我们显示了link表单字段的第一条错误消息。

    深入控制器并处理表单

    在我们的示例中,表单验证成功完成时执行的验证检查部分的else部分保存了链接的进一步处理。在本节中,我们将执行以下步骤:

    1. 检查链接是否已经在我们的数据库中。
    2. 如果链接已经在我们的数据库中,返回缩短的链接。
    3. 如果链接不在我们的数据库中,为链接创建一个新的随机字符串(将在我们的网址中的散列)。
    4. 使用提供的值在数据库中创建新记录。
    5. 将缩短的链接返回给用户。

    现在,让我们来挖掘代码。

    1. Here's the first part of our code:

      php // Now let's check if we already have the link in our database. If so, we get the first result $link = Link::where('url','=',Input::get('link')) ->first();

      首先,我们使用流畅查询生成器where()方法检查该网址是否已经存在于我们的数据库中,并通过first()方法获得第一个结果,并将其分配给$link变量。您可以使用流畅的查询方法和雄辩的表单。如果这让您感到困惑,请不要担心,我们将在后面的章节中进一步讨论。

    2. This is the next part of our controller method's code:

      php //If we have the URL saved in our database already, we provide that information back to view. if($link) { return Redirect::to('/') ->withInput() ->with('link',$link->hash);

      如果我们在数据库中保存了网址,$link变量将保存从数据库中获取的链接信息的对象。所以通过一个简单的if()子句,我们可以检查是否有结果。如果有返回的结果,我们可以使用$link->columnname进行访问。

      在我们的例子中,如果查询有结果,我们将输入和链接重定向回表单。正如我们在这里所使用的,with()方法也可以使用两个参数,而不是使用骆驼的情况— withName('value')with('name','value')完全相同。所以,我们可以用名为with('link',$link->hash)的 flash 数据返回哈希代码。为了展示这一点,我们可以在表单中添加以下代码:

      php @if(Session::has('link')) <h3 class="success"> {{Html::link(Session::get('link'),'Click here for your shortened URL')}}</h3> @endif

      Html类帮助我们轻松编写 HTML 代码。link()方法需要以下两个参数:

      • 第一个参数是link。如果我们直接提供一个字符串(在我们的例子中是散列字符串),类将自动识别它,并从我们的网站中创建一个内部网址。
      • 第二个参数是有链接的字符串。

      可选的第三个参数必须是一个数组,将属性(如类、标识和目标)保存为二维数组。

    3. The following is the next part of our code:

      php //Else we create a new unique URL } else { //First we create a new unique Hash do { $newHash = Str::random(6); } while(Link::where('hash','=',$newHash)->count() > 0);

      如果没有结果(变量的 else 子句),我们使用Str类的random()方法创建一个六个字符长的字母数字随机字符串,并使用 PHP 自己的 do-while 语句每次检查它以确保它是一个唯一的字符串。对于现实世界的应用,您可以使用另一种方法来缩短,例如,将标识列中的条目转换为 base_62,并将其用作哈希值。这样,网址会更干净,而且总是更好的做法。

    4. This is the next part of our code:

      php //Now we create a new database record Link::create(array( 'url' => Input::get('link'), 'hash' => $newHash ));

      一旦我们有了唯一的散列,我们就可以用 Laravel 的雄辩 ORM 的create()方法将链接和散列值添加到数据库中。唯一的参数应该是二维数组,其中数组的键保存数据库列名,数组的值保存要作为新行插入的值。

      在我们的案例中,url列必须具有来自表单的link字段的值。我们可以使用拉威尔的Input类的get()方法捕捉来自post请求的这些值。在我们的例子中,我们可以使用Input::get('link')捕获来自post请求的link表单字段的值(我们将使用意大利面条代码$_POST['link']捕获该值),并像前面一样将哈希值返回给视图。

    5. This is the final part of our code:

      php //And then we return the new shortened URL info to our action return Redirect::to('/') ->withInput() ->with('link',$newHash);

      现在,在输出端,我们被重定向到oursite.dev/hashcode。变量$newHash中存储有一个链接;我们需要捕获这个哈希代码并查询我们的数据库,如果有记录,我们需要重定向到实际的 URL。

    从数据库中获取单个 URL 并重定向

    现在,在我们第一章的最后一部分中,我们需要从生成的网址中获取hash部分,如果有值,我们需要将其重定向到存储在我们数据库中的网址。为此,在app文件夹下的routes.php文件末尾添加以下代码:

    Route::get('{hash}',function($hash) {
      //First we check if the hash is from a URL from our database
      $link = Link::where('hash','=',$hash)
      ->first();
      //If found, we redirect to the URL
      if($link) {
        return Redirect::to($link->url);
        //If not found, we redirect to index page with error message
      } else {
        return Redirect::to('/')
        ->with('message','Invalid Link');
      }
    })->where('hash', '[0-9a-zA-Z]{6}');
    

    在前面的代码中,与其他路由定义不同,我们在名称hash周围添加了花括号,告诉 Laravel 这是一个参数;使用where()方法,我们定义了名称参数将如何。第一个参数是变量的名称(在我们的例子中是hash,第二个参数是一个正则表达式,用于过滤参数。在我们的例子中,正则表达式过滤一个 6 个字符长的字母数字字符串。这样,我们可以从一开始就过滤我们的网址并保护它们,并且我们不必检查url参数是否有我们不想要的东西(例如,如果在标识列中输入字母而不是数字)。要从数据库中获取单个 URL 并进行重定向,我们执行以下步骤:

    1. Route类中,我们首先进行一个搜索查询,就像我们在前面的部分中所做的那样,并检查我们的数据库中是否有来自某个 URL 的给定散列的链接,并将其设置为一个名为$link的变量。

      php //First we check if the hash is from an URL from our database $link = Link::where('hash','=',$hash) ->first();

    2. 如果有结果,我们将页面重定向到我们数据库的url列,该列有用户应该重定向到的链接。

      php //If found, we redirect to the link if($link) { return Redirect::to($link->url); }

    3. If there is no result, we redirect the user back to our index page using the $message variable, which holds the value Invalid Link.

      php //If not found, we redirect to index page with error message } else { return Redirect::to('/') ->with('message','Invalid Link'); }

      要在表单中显示Invalid Link消息,请在位于app/views下的form.blade.php文件中添加以下代码:

      php @if(Session::has('message')) <h3 class="error">{{Session::get('message')}}</h3> @endif

    总结

    在本章中,我们通过制作一个简单的 URL shortener 网站,介绍了 Laravel 的路线、模型、artisan 命令和数据库驱动程序的基本用法。完成本章后,您现在可以通过迁移创建数据库表,使用 Laravel 表单生成器类编写简单的表单,使用Validation类验证这些表单,并使用流畅的查询生成器或雄辩的表单处理这些表单和向表中插入新数据。在下一章中,我们将介绍这些优秀特性的高级用法。