Laravel教程 五:MVC的基本流程

JellyBool

JellyBool

期间受到很多私事影响,终于还是要好好写写laravel的教程了。

上一篇我们说了数据库和Eloquent的基本用法,如计划一样,这一篇文章我们说说Laravel中Model,Controller,Views的工作流程,也就是下面这个顺序:

1.注册路由 ---> 2.创建控制器 ---> 3. 控制器中获取数据库数据 ---> 4.在视图中展示数据

英文的表达可能会更加贴切一点:

1.register routes ---> 2.make a controller ---> 3.fetch data from database ---> 4. load a view to display data

在laravel中,最常见的流程就是这个样子的,我们在实现某个功能的时候,通常就是走上面的这个流程。比如我们这个blog项目中,我们需要实现下面的功能:

1. 展示所有的文章  // blog首页
2. 展示一篇文章   //文章详情页
3. 创建一篇文章   // 文章发布页面
4. 修改一篇文章  // 文章修改页面
5. 删除一篇文章  // 后台管理

在这一篇文章中,我们集中精力解决一下第一个功能,所以我们按照上面的流程来走一遍:

PS : 上次我们使用artisan tinker这个工具在命令行中对数据库的数据进行了CRUD,现在就要将这些应用到MVC当中了。

注册路由

我们这里会从头开始,也就是会先删除app/Http/Controllers/ArticleController.php这个文件

在系列文章的第二篇当中,我们在app/Http/routes.php中注册了我们首页的路由:

Route::get('/','ArticleController@index');

可以直接使用这个路由,所以我们可以进入下一步。

创建控制器

这里需要注意的是,如果你使用了Homestead,请先ssh登录到你的虚拟机中执行命令;还有就是,请先删除之前课程遗留的ArticleController,如果你想偷懒,可以跳过这一步

创建控制器的时候你可以手动创建,不过还是推荐使用artisan这个命令行工具,在项目目录之下,命令行执行:

php artisan make:controller ArticleController --plain

这里需要说明的是--plain这个参数表明只要一个简单的controller,里面不需要生成一堆如show(),create()等方法。

控制器中获取数据库数据

打开这个重新创建的ArticleController.php:

class ArticleController extends Controller
{

    public function index()
    {
        $articles = Article::all();

        return $articles;
    }
}

我们创建一个index()方法,这是因为我们在routes.php当中注册的路由指定要加载ArticleControllerindex()方法,我们在index()方法中使用Article::all()将数据库中articles这张表中的所有的记录查找出来,直接返回。

我们用浏览器来访问试试,会看到类似下面这个情况:

替代文字

对,如你看到的一样,如果你直接返回查找到得数据,Laravel会默认将这些数据转换成json格式,因为laravel可能是出于这样的考虑:一般这种情况下地返回,通常都是在创建api功能,比如你为你的一个手机App写的api一样,json数据无疑是很好的选择。

顺便安利一下大家使用百度团队的这个FeHelper这个chrome插件:

https://github.com/zxlie/FeHelper

但是在这里我们并不是想直接返回json,取而代之的是,我们的目的是加载视图,将数据展示出来。所以这就是我们下一步的工作了

在视图中展示数据

这里我们首先需要修改的是ArticleController中的index()方法:

public function index()
{
    $articles = Article::all();

    return view('articles.index',compact('articles'));
}

我们只是修改了return这一行的代码,使用view()方法加载视图,这个视图就是位于resources/views/articles/中的index.blade.php(我们还没有创建),最后使用compact('articles')将数据传给视图文件:关于这个视图传递变量的问题,你可以参考教程的第三篇

然后,我们需要创建我们的视图文件,在resources/views/articles/下创建index.blade.php文件:

@extends('app')
@section('content')
 <h1>这是index.blade.php</h1>
@endsection

写上上面的内容,关于视图文件的blade模板,可以参考教程的第三篇,然后浏览器访问一下看看:

替代文字

视图文件正确之后,我们需要将传递给视图的$articles变量的内容展示出来:

@extends('app')
@section('content')
    @foreach($articles as $article)
        <h1>{{ $article->title }}</h1>
        <p>{{ $article->intro }}</p>
        <hr>
    @endforeach
@endsection

我们使用@foreach来将所有的文章循环出来,浏览器访问看看:

替代文字

这里我们的首页展示也就基本完成了,然而在我们的实际blog中,我们会在每个标题出给出我们的文章链接,也就是为每个文章添加一个详情展示的页面,用户点击文章的链接之后,我们展示相应的文章详情。我们来实现这个功能

显示文章详情

通过文章展示来快速体验上面的流程:

1.注册路由

来到app/Http/routes.php中,我们增加一个路由:

Route::get('articles/{id}','ArticleController@show');

上面的路由articles/{id}指定我们需要加载ArticleController中的show()方法。这里需要注意的是{id}这个表达:这是表示id是一个路由变量,也就是当我们访问类似下面这两个路由的时候:

http://blog.dev/articles/1 //id 为1
http://blog.dev/articles/foo // id为foo

先不急着访问,因为我们还没有创建show()方法,这里只是作为说明。

在laravel中,路由变量写在{}括号中,这个id对应我们等下写的show()方法的参数。

2.编写show()

在ArticleController增加show()方法:

public function show($id)
{
    return $id;
}

我们在show($id)方法中,首先接受参数id,然后直接返回。现在我们可以访问上面的两个url了,看到的类似下面这个效果:

替代文字

3.获取数据

然而在show()方法中,我们也是需要从数据库中加载获取数据,所以我们先修改show()方法:

public function show($id)
{
    $article = Article::find($id);
    return $article;
}

我们通过find()方法从数据库中查找一条记录,然后直接返回,我们来看看效果:

替代文字

4.加载视图

获取数据之后,我们需要加载相应地视图来展示数据,还是修改show()方法:

public function show($id)
{
    $article = Article::find($id);
    return view('articles.show',compact('article'));
}

类似地,我们使用view()加载show.blade.php,然后compact()将变量传递过去。所以我们去创建show.blade.php视图文件吧:

@extends('app')
@section('content')
        <h1>{{ $article->title }}</h1>
        <hr>
        <p>{{ $article->content }}</p>
@endsection

这里跟index.blade.php视图文件差不多,我们只是去掉了@foreach,在来访问一下看看:

替代文字

到这里,我们的文章展示页面也可以说是完成了,然而当我们访问这个下面这个链接的时候:

http://blog.dev/articles/3

报错了!

替代文字

这是因为我们在show()方法中使用$article = Article::find($id);来查找一篇文章,但是我们的数据库中的articles表并没有id3的记录,也就是id3的时候,$article变量已经是null了,这个时候我们如果还是希望在视图中使用{{ $article->title }},所以才会出现错误:

Trying to get property of non-object.... 

PS: 如果你想调试,看看$article到底是什么,你可以在laravel中使用dd($article)来调试

那这个要怎么解决呢?有两种方法:

第一,自己写个if条件判断:

public function show($id)
   {
       $article = Article::find($id);
       if(is_null($article)){
           abort(404);
       }
       return view('articles.show',compact('article'));
   }

如果$article为空,直接abort()一个404页面。再来访问一下:

替代文字

这里貌似还是会看到一堆错误,为什么呢?那是因为在.env中我们设置了APP_DEBUG=true,所以还会有下面的一堆错误,我们在实际的线上部署环境中,APP_DEBUG=false才是我们的设置。我们来体验一把将APP_DEBUG=false,见证一下我们的404页面:

替代文字

这个404页面,你可以自定义:就是在resources/views/errors/文件夹下创建一个404.blade.php

实际例子就是这样的(彩蛋):

https://jellybool.com/show404page

你也可以在我的blog地址栏随便输入一堆东西,看看找不到文章的时候是什么样的404 page 。

第二,使用findOrFail()

上面的条件判断其实很不错了,但是这里我还是推荐使用findOrFail()这个方法:

public function show($id)
    {
        $article = Article::findOrFail($id);
       
        return view('articles.show',compact('article'));
    }

findOrFail()表示首先尝试find,如果找不到就fail,抛出一个Eloquent Exception,所以我们再来访问尝试一下:

替代文字

我们应该会得到一样的结果.

然后我们回到我们的index.blade.php中为每篇文章添加链接:

  @foreach($articles as $article)
      <h1><a href="/articles/{{ $article->id }}">{{ $article->title }}</a></h1>
      <p>{{ $article->intro }}</p>
      <hr>
  @endforeach

访问来看看:

替代文字

注意我们这里直接使用了href="/articles/{{ $article->id }}"进行链接,你也可以使用action()这个方法:

 @foreach($articles as $article)
        <h1><a href="{{ action('ArticleController@show',[$article->id]) }}">{{ $article->title }}</a></h1>
        <p>{{ $article->intro }}</p>
        <hr>
    @endforeach

action()这个方法第一个参数表明要加载ArticleControllershow()方法,跟routes一样,第二个参数用数组传入相应地参数[$article->id]

你还有第三种选择,使用url()方法:

@foreach($articles as $article)
      <h1><a href="{{ url('articles/',$article->id) }}">{{ $article->title }}</a></h1>
      <p>{{ $article->intro }}</p>
      <hr>
  @endforeach

url()方法第一个参数传入url路径,第二个参数直接传入变量。

上面的三种方法,选择一种自己喜欢的就可以了。

下一节

就写到这里吧,这一节大概也就是这样的内容了,不知道这样介绍,大家对Laravel的Model Controller Views的工作流程清晰了没,不清晰的话可以评论问我。。。

下一节,我即将说说怎么实现创建一篇文章,就会顺带介绍Laravel的Forms表单。

最后,

Happy Hacking

本文由 JellyBool 创作, 转载和引用遵循 署名-非商业性使用 2.5 中国大陆 进行许可。

共有 85 条评论

openwrtmail

在ArticleController里用Article方法获取数据库,提示

openwrtmail

FatalErrorException in ArticleController.php line 12:
Class ‘App\Http\Controllers\Article’ not found
好奇怪啊

hlhsbczd123 回复 openwrtmail

这位同学一看就没有灵性,这波很亏,看提示就没有导入Model啊

JellyBool

@openwrtmail 在ArticleController.php头部使用:

use App\Article;
TimeIsGoOn

报错了

代码

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use  App\Article;
class ArticleController extends Controller
{
	public function index(){
		  //
		       $articles = Article::all();

		        return $articles;
	}
  
}
PDOException in Connector.php line 50:
SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)
in Connector.php line 50
at PDO->__construct('mysql:host=localhost;dbname=homestead', 'root', 'root', array('0', '2', '0', false, '0')) in Connector.php line 50
at Connector->createConnection('mysql:host=localhost;dbname=homestead', array('driver' => 'mysql', 'host' => 'localhost', 'database' => 'homestead', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, 'name' => 'mysql'), array('0', '2', '0', false, '0')) in MySqlConnector.php line 22
at MySqlConnector->connect(array('driver' => 'mysql', 'host' => 'localhost', 'database' => 'homestead', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, 'name' => 'mysql')) in ConnectionFactory.php line 60
at ConnectionFactory->createSingleConnection(array('driver' => 'mysql', 'host' => 'localhost', 'database' => 'homestead', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, 'name' => 'mysql')) in ConnectionFactory.php line 49
at ConnectionFactory->make(array('driver' => 'mysql', 'host' => 'localhost', 'database' => 'homestead', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false), 'mysql') in DatabaseManager.php line 175
at DatabaseManager->makeConnection('mysql') in DatabaseManager.php line 67

我在

sun@sun:~/Code/blog$ php artisan tinker
Psy Shell v0.5.2 (PHP 5.6.4-4ubuntu6.2 — cli) by Justin Hileman
>>> Article::all()
PHP Fatal error:  Class 'Article' not found in eval()'d code on line 1
>>> \App\Article::all();
=> Illuminate\Database\Eloquent\Collection {#678
     all: [
       App\Article {#679
         id: "1",
         title: "this is a demo",
         intro: "",
         content: "",
         introduction: "",
         value: "",

使用是正常的,密码用户名什么的也都对

JellyBool

@TimeIsGoOn 数据库配置我看一下

TimeIsGoOn
APP_ENV=local
APP_DEBUG=true
APP_KEY=ZFbW1zJMffhZnBuRzkaoiZFpJJ8Dt9tn

DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=root
DB_PASSWORD=root

CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

    'mysql' => [
        'driver'    => 'mysql',
        'host'      => env('DB_HOST', 'localhost'),
        'database'  => env('DB_DATABASE', 'forge'),
        'username'  => env('DB_USERNAME', 'forge'),
        'password'  => env('DB_PASSWORD', ''),
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
        'strict'    => false,
    ],

JellyBool

DB_PASSWORD=root改为DB_PASSWORD=secret

TimeIsGoOn

可是我的密码就是root

TimeIsGoOn

唉,我把虚拟机去了,直接配成apache
一切都正常了,
有一点,输出的格式和你不一样,我的只是简单的json字符串输出,不像你的那么好看
哦,原来是google的插件

JellyBool

@TimeIsGoOn 对,是百度团队出的chrome插件

TimeIsGoOn

我有种想看源码的想法,
用框架就是,出了问题都找不到问题在哪里,日志里面错误一大坨,
我把自动加载的文件打出来,大概顺序来是200个左右,
这个框架真是好大,
我倒是好奇,里面很多的 包是如何整合在一起来进行使用的

JellyBool

@TimeIsGoOn 看吧,今天有的悲伤

richardxxx0x

@JellyBool .env里面可以用mysql的配置吗?如果在搞个homestead安装一直失败。

JellyBool

.env可以配置mysql的啊,安装homestead失败?如果新手,我推荐直接php artisan serve

@richardxxx0x

richardxxx0x

@JellyBool 嗯,通过 vagrant box add laravel/homestead 安装,下载一直失败,删除 rm -rf ~/.vagrant.d/tmp ,再重新下载,下载一会就中断了,估计是网络原因。我是新手,感觉laravel的MVC开发模式很赞~~

JellyBool

这种原因还是买个VPN吧,也就10多块一个月而已。 @richardxxx0x

richardxxx0x

@JellyBool 在index.blade.php 中 @extends(‘app’),出现错误 View [app] not found. (View: /home/kali/Desktop/blog/resources/views/articles/index.blade.php), 这是什么情况?难道还得在 resources/views中创建app.blade.php 吗?还是这个版本没有呢?谢谢。

看了教程六,发现需要自己创建个app.blade.php,这个问题已经解决了。

JellyBool

好的,解决了就好 @richardxxx0x

mailman

这个是怎么回事,找不到Article类

mailman
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;

class ArticleController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
       $acticles = Article::all();
        // return view('article.lists',$acticles );
       return $acticles;
    }
mailman

解决了。
use App\Article

codekissyoung


我测试下图片上传

codekissyoung

很nice的功能啊!^ V ^

jeak84

奇怪,访问localhost/articles/1 页面为空白,没有任何错误提示,然后在phpstorm里面的Article::find($id)方法会提示:method ‘find’ not found in class \App\Aticle ,真是不知道原因在哪里了。

JellyBool

@jeak84 写错了 method 'find' not found in class \App\Aticle ?

jeak84

是“method ‘find’ not found in class \App\Article”,表现就是Aticle::all( )方法可以使用,无法使用Aticle::find(id )和Aticle::findOrFail(id),是不是缺少什么组件啊,困惑中。

jeak84

@JellyBool 排除不是这两个方法的原因,但仍然不清楚为什么不显示任何内容,也不报错。

jeak84

@JellyBool 问题解决,是视图文件有错误。

rootx

use App\Article
.evn修改的数据库连接可能要重启php artisan serve才能取得到数据库article数据

DavidWang

###标题

frankstar

url()方法中应该改为url(‘articles/’.$article->id);逗号会报错 应是点号吧。。zzz

Anonymous

好像没有用到模型文件,那模型文件的作用是什么呢,生成一个模型文件后,好像就没它什么事了。

Anonymous

我们在使用类似下面的语句的时候就是使用了Eloquent Model:
Article::findOrFail($id)
Article::all()
其中的Article就是模型,只不过我们都是使用了Laravel的标准来创建,不用在模型中制定很多东西,比如我们的表就是articles,这个laravel会直接默认,所以我们不用声明,而后面说到的Eloquent Relationship就会更清楚地用到了。

Anonymous

不知道这样的解释清楚了没。。。

Anonymous

模型文件中基本不用写什么代码了吗?

Anonymous

我看到有些教程中,把增删改查的代码写到model文件中,和写到控制器中相比,哪种方式更好啊?或者说,哪种方式让不同文件的分工体现得更清晰?

Anonymous

现在来说是不怎么用写代码在Model里面,后面的Relationship和setAttributes会写一些

Anonymous

期待更新…

Anonymous

这个我是比较喜欢放在controller里面的,因为这对于我个人来说貌似思路比较清晰一些(controller fetch data)。所以我觉得这个看个人吧

Anonymous

在PHPStorm里Article::findOrFail($id) 这个不能补全,请问博主用的什么IDE

Anonymous

还是phpstorm,你用一下这个https://github.com/barryvdh/laravel-ide-helper

Anonymous

我装过这个,但是装了以后,程序报错了。

Anonymous

怎么会,报什么错

Anonymous

网页里对应的php文件在哪个路径下,没有找到。

Anonymous

博主,Model.php里只有findOrNew方法,没有findOrFail,也没有find方法,怎么破?

Anonymous

什么意思?你是说看源码?

Anonymous

直接点raw。这里https://gist.githubusercontent.com/barryvdh/bb6ffc5d11e0a75dba67/raw/1467343b1140f568c8e30656926180cd283f283c/.phpstorm.meta.php

Anonymous

不是,是我的Article::findOrFail($id);方法没有代码补全,然后我查看继承的Model.php里根本就没有这个方法。

Anonymous

Illuminate\Database\Eloquent\Builder看看这个类

Anonymous

没有找到这个类,我Illuminate下只有Html一个文件夹 - -!

Anonymous

直接phpstorm就搜出来了

Anonymous

use App\Article;
应该控制器里面加上这句代码的、博主的不会报错?

Anonymous

我加了的,所以不会报错。这种情况只要你用一个好的ide,自动就解决这种问题了

Anonymous

写php一直用的sublime。。不过我觉得这点应该再文章中点明一下比较好。

Anonymous

好的。。。我改一下。

Anonymous

博主好谦虚。

Anonymous

文章已修改。感谢指出

Anonymous

谢谢博主、

Anonymous

没事,交流是好事

Anonymous

我用的nginx 配置到了public目录下,加链接的时候出错。。

Anonymous

出现404错误。

Anonymous

加链接的时候出现错误是什么情况,至于配nginx,可以直接参考这里。。

https://jellybool.com/post/setup-laravel-project-on-aliyun-ecs

Anonymous

就是这篇文章,显示详情的那部分,一直过不去。按照第二种方法做,都是出现404错误,博主给指导一下啊。

Anonymous

不知道为什么你的评论都被过滤为垃圾评论

Anonymous

加qq其实没什么用,这些技术问题从来都不会在qq上得以解决

Anonymous

博主为什么我输出的内容 默认格式不是json,在哪里可以修改?

Anonymous

jellybool,那个url方法增加链接,我这边遇到了一个情况,

<h1><a href="{ url('articles/',$article->id) }">{ $article->title }</a></h1>

上面这句话如果直接敲的话,会遇到一个错误,地址栏会在id前面会多一个斜杠,应该将articles后面的斜杠去掉[嘻嘻]

Anonymous

查询数据库的操作最好都放在moden里面,在controller里面调用,你现在调用的都是laravel的标准函数,才直接在controller写

Anonymous

循环不在view里面,在control里面做,直接输出到view,我习惯这样做
要就在view参php的代码,要就在php参html代码,我习惯后者

Anonymous

最近在使用Laravel开发个后台管理系统,不知Laravel如何在已有数据表中插入新的字段?知道要修改database/migrations目录下的迁移文件,但是每次rollback后执行php artisan migrate都会把原表中数据清除了。如果网站上线之后添加功能,需要在数据表中新增字段,该如何做呢?

我的问题跟这篇文章没什么关系,只是在学习Laravel过程中遇到的问题,谷歌找了很久,没有这方面的回答,希望有人能回答一下,谢谢啦

BiggerHeader 回复 Anonymous

可以重新生成一个数据迁移文件,指定表。例如
php artisan make:migration create_users_table --create=users

php artisan make:migration add_votes_to_users_table --table=users

BiggerHeader 回复 Anonymous

第二个就是你要的添加字段不删除数据

Anonymous

生成一个新的migration直接加就是

一心为谁

楼主,页面路由: Route::get(‘articles/{id}’,‘ArticleController@show’); 访问直接404 Not found , The requested URL /user/name was not found on this server. 这是什么情况,除了 / 的路由,其他的路由形式都访问404.

JellyBool 回复 一心为谁

nginx 或者 apache 的重写没配置好

xyj7473423

博主你好
我在学习laravel的时候遇到一个问题 找了好久了都没搞定 不知道咋回事
帮忙看下啊 万分感激

E:\wamp\www\laravel\blog>php artisan tinker
Psy Shell v0.7.2 (PHP 5.5.12 鈥?cli) by Justin Hileman
>>> $article = new App\Article;
=> App\Article {#629}
>>> $article->title='sunwukong';
=> "sunwukong"
>>> $article->save()
InvalidArgumentException with message 'Unsupported driver [laravel]'
>>> $article->save();
JellyBool 回复 xyj7473423

基本上就是数据库的配置错了,大概就是把 mysql 改成 laravel 了

xyj7473423 回复 JellyBool

嗯嗯 是的呢 谢谢博主 已经解决了

xyj7473423

E:\wamp\www\laravel\blog>php artisan tinker

Psy Shell v0.7.2 (PHP 5.5.12 鈥?cli) by Justin Hileman
>>> $article = new App\Article;
=> App\Article {#629}
>>> $article->title='sunwukong';
=> "sunwukong"
>>> $article->save()
InvalidArgumentException with message 'Unsupported driver [laravel]'
>>> $article->save();
vartist

求救啊!我从数据库读出的数据格式貌似是 object, 我不知道咋弄的

Limbo {#175#fillable: array:2 [▶]
  #connection: null
  #table: null
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:5 [▶]
  #original: array:5 [▶]
  #casts: []
  #dates: []
  #dateFormat: null
  #appends: []
  #events: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #hidden: []
  #visible: []
  #guarded: array:1 [▶]
}

还有就是我的 laravel 项目里面没有 .env,版本是5.4.16, 我要自己创建吗?数据库的操作设置我都是在配置文件直接写的。。。囧

JellyBool 回复 vartist

取出来正常就是 object 啊。。。自己创建 env 文件呗

vartist 回复 JellyBool

好了,是我写的有问题,有些慌了。
另外,我发现 .env 那个文件我有的,它默认隐藏了,好尴尬!