我们来看两种不同的路由,这就是视图文件

  • 栏目:前端 时间:2020-04-30 10:58
<返回列表

本文由码农网 – 任琦磊原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

路由和控制器的关系

1. 视图分离与嵌套

这是一份面向初学者的 Laravel 5.1 中构建 Service Provider 的教程。

路由文件地址在appHttproutes.php,我们来看两种不同的路由。

在 learnlaravel 文件夹下运行命令:

我在自己过去的博客中提到了我喜欢 Laravel 5.1 的架构,尤其是它引入了Service Provider,从而使你模块化的构建应用成为了可能。应用的配置常常可能成为棘手的任务,完全取决于你正在使用的框架,但幸运的是,我们正在使用的 Laravel 让这件事变得相当简单。

Route::get('/', function () {
    return view('welcome');
});


Route::get('/hi', function () {
    return 'hello world';
});
php artisan generate:view admin._layouts.default

韦德体育 1

以上均为绑定匿名函数的路由,虽然可以返回视图,也可以返回字符串,但本质都是一样的。

这时候generator插件帮我们创建了app/views/admin/_layouts/default.blade.php 文件,将内容修改为:

所以让我们开始创建一个用于演示的路由(route)。到 app/Http/routes.php中添加下面这条路由:

Route::get('/blog','BlogController@index');
Route::get('/post/{slug}','BlogController@showPost');
<!doctype html>
<html>
<head>
 <meta charset="utf-8">
 <title>Learn Laravel 4</title>
 @include('admin._partials.assets')
</head>
<body>
<div class="container">
 <div class="navbar navbar-inverse navbar-fixed-top">
 <div class="navbar-inner">
 <div class="container">
 <a class="brand" href="{{ URL::route('admin.pages.index') }}">Learn Laravel 4</a>
 @include('admin._partials.navigation')
 </div>
 </div>
</div>
<hr>
 @yield('main')
</div>
</body>
</html>
Route::resource('demo', 'DemoController');

这两种是绑定控制器的路由,控制器类BlogController下有两个函数,index和showPost,可以调用。

这就是视图文件,MVC中的V。视图需要仔细讲一下。

通过使用 Route::resource,我们就获得了预定义好的 index,show,create,edit,update,store 和 destroy 路由。

那么问题来了,究竟应该选用哪种?

views文件夹为视图文件夹,视图文件夹可以嵌套,就像我上面一样创建了admin/_layout嵌套文件夹,在里面创建了一个叫default.blade.php的文件,那么以后我们在Laravel内任何地方要用到这个视图的时候,他就叫admin._layouts.default。

为了实现良好的对称性,现在我们可以使用 artisan 命令行工具来为我们创建对应的控制器(controller)。键入如下指令:

你不可能把复杂的业务逻辑都写在一个匿名函数里,所以你要学会新建控制器。

我们看到,上面代码的第七行是 @include('admin._partials.assets') ,根据上面我们刚刚了解的知识,这表示载入了另外一个文件。blade是Laravel的模板引擎,此处的 @include 表示直接把那个文件的所有代码带入进来放到这里,变成当前视图的一部分。

php artisan make:controller DemoController
Route::get('/mvc', 'MyController@hello');  

注意看第25行 @yield('main') ,这表示什么呢?这个有点复杂,我们稍后再讲。

让我们打开创建好的文件,将 index 方法修改为如下内容:

一.添加新控制器

2. 权限验证

public function index()
{
    return view('demo.index');
}

控制器文件夹地址在Laravel文件夹下appHttpControllers,我们继续使用artisan控制台创建一个新控制器

Laravel支持标准HTTP认证,但是在此处我们需要构建blog系统,所以我们将编写完善的管理员登陆系统,从页面登录。

现在让我们继续在 app/Resources/views 目录下创建一个名为 Demo 的文件夹,并在文件夹中创建一个名为 index.blade.php 的视图(view)文件,内容如下:

php artisan make:controller MyController

用命令行创建app/views/admin/auth/login.blade.php文件,代码如下:

@extends('layouts.master')

@section('content')
<h1>Demo Page</h1>
@endsection

然后,回到控制器目录,一份新的MyController.php文件被创建了,代码如下:

@extends('admin._layouts.default')
@section('main')
 <div id="login" class="login">
 {{ Form::open() }}
 @if ($errors->has('login'))
 <div class="alert alert-error">{{ $errors->first('login', ':message') }}</div>
 @endif
 <div class="control-group">
 {{ Form::label('email', 'Email') }}
 <div class="controls">
 {{ Form::text('email') }}
 </div>
 </div>
 <div class="control-group">
 {{ Form::label('password', 'Password') }}
 <div class="controls">
 {{ Form::password('password') }}
 </div>
 </div>
 <div class="form-actions">
 {{ Form::submit('Login', array('class' => 'btn btn-inverse btn-login')) }}
 </div>
 {{ Form::close() }}
 </div>
@stop

这个例子中我们正在调用一个我已经在 layouts 文件夹中创建了的 master 页面master.blade.php。如果你的 master 用了另一个名字,那么这里你得替换掉。如果你没有 master 页面,那么就删掉第一行 extends 的全部内容,包括 @sectioin 申明。

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

use AppHttpRequests;

class MyController extends Controller
{
    //
}

大家应该注意到了前两行:

假设你已经配置好了你的开发环境并解析了你的域名,那么当你访问路由yourapplication.com/demo,你应该可以看到内容 Demo Page 了。

我们修改MyController类,同时再创建一个视图。

@extends('admin._layouts.default')@section('main')

好的,那么现在就让我们来创建一个Service Provider。这个Service Provider不会做太多特别有用的事情。它只是用来向你展示如何搭建它。

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

use AppHttpRequests;

class MyController extends Controller
{
    public function hello()
    {
        return View('myview');
    }
}

这代表什么?实际上,以后我们会了解到,在controller中调用view的时候,调用的只是这个login.blade.php文件,第一行表示,此视图是admin._layouts.default的子视图,这时blade引擎会把这个视图也载入进来,怎么组装呢?这时候下面那个@section('main')就该出场了,被它包裹的代码将会直接放到admin._layouts.default中的@yield('main')中。section和yield可以任意搭配,只要两个视图之间有调用关系,他们就可以这样用,非常灵活。

让我们在 app 目录下创建一个 Helpers 文件夹。然后在 Helpers 文件夹里,创建一个 Contracts 文件夹。在 Contracts 文件夹里,创建文件RocketShipContract.php 并写入下面的内容:

如果这么写,就意味着一旦用户访问URL:laravel/public/mvc,路由就会交给MyController控制器的hello函数,hello函数返回myview视图,即返回myview.blade.php

写到这里大家可能有个疑问,为什么示例代码里空行那么多?这一点就是个人经验了。blade引擎的所有标签都会在视图编译时用正则处理,引擎本身有一个问题,算不上bug,就是换行符会被处理掉,导致前后行和这一行都紧紧地挤在一起,在前端浏览器中 查看源代码 时,比较不清晰,前后加上空行可以解决这个问题。当然这可能是一个自动的 压缩 特性,不再深入讨论。

<?php

namespace AppHelpersContracts;

Interface RocketShipContract
{

    public function blastOff();

}

 我们看看myview.blade.php的代码

增加控制器文件app/controllers/admin/AuthController.php,这时候有人就说了,这我知道,哈哈,运行

正如你所知,接口(interface)是一种用来强化架构的契约(contract)。为了定义类的接口,它必须包含名为 blastOff 的公共函数(public function)。

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">{{ $d1 }}</div>

                <div class="panel-body">
                    this is my view!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

php artisan generate:controller admin.AuthController

所以为什么要费力地创建一个契约呢?其实,Laravel 有一个神奇的功能是你可以类型提示契约,Service Provider会返回一个受它约束的具体类的实例。这实现了无与伦比的灵活性和松耦合的结构,因为你的工作将可以轻松地通过一行代码来完成。我们即将看到这是如何工作的。

这里的{{ $d1 }}希望用一个变量的值来代替,所以,我们把MyController控制器修改为

这个想法是对的,但你运行一下试试?会直接在app/controllers目录下创建一个 admin.AuthController.php 文件,有人又说,那我用 admin/AuthController 总行了吧,你试一下?也不行。所以我们要先在app/controllers 下手动创建 admin 文件夹,这时候,再命令行输入:

首先,让我们创建一个具体类。在 app/Helpers 文件夹中,创建RocketShip.php,代码如下:

class MyController extends Controller
{
    public function hello()
    {
        return View('myview',['d1'=>'a1']);
    }
}
php artisan generate:controller admin/AuthController
<?php

namespace appHelpers;

use AppHelpersContractsRocketShipContract;

class RocketShip implements RocketShipContract
{

    public function blastOff()
    {

        return 'Houston, we have ignition';

    }

}

  

这样就可以了。接下来改写AuthController.php 的内容为:

你可以看到我们的具体类没有做很多事,但我们则对如何配合在一起更感兴趣。你可以自己决定你想给你的应用提供什么服务。

 

<?php
namespace AppControllersAdmin;
use Auth, BaseController, Form, Input, Redirect, Sentry, View;
class AuthController extends BaseController {
 /**
 * 显示登录页面
 * @return View
 */
 public function getLogin()
 {
 return View::make('admin.auth.login');
 }
 /**
 * POST 登录验证
 * @return Redirect
 */
 public function postLogin()
 {
 $credentials = array(
 'email' => Input::get('email'),
 'password' => Input::get('password')
 );
 try
 {
 $user = Sentry::authenticate($credentials, false);
 if ($user)
 {
 return Redirect::route('admin.pages.index');
 }
 }
 catch(Exception $e)
 {
 return Redirect::route('admin.login')->withErrors(array('login' => $e->getMessage()));
 }
 }
 /**
 * 注销
 * @return Redirect
 */
 public function getLogout()
 {
 Sentry::logout();
 return Redirect::route('admin.login');
 }
}

好的,现在我们要来创建一个符合契约和具体类的Service Provider了。在命令行中键入下面的指令:

这就是我们登录、注销的控制器,MVC中的C。接下来我将讲解命名空间,这是Laravel的基础,或者说是composer的基础,是整个Laravel教程中的重点、难点,希望大家锱铢必较,任何不懂都不要放过。可以到phphub论坛或者golaravel论坛相应帖子下面提问,或者直接发帖提问。

php artisan make:provider RocketShipServiceProvider

我们首先观察这个文件的位置,它位于 app/controllers/admin 目录下,这有什么不同呢?在其他框架如 CI 中,子文件夹直接加上文件夹名就可以直接调用到了,虽然最多只能有一层。而Laravel没有这么简单,涉及到了PHP的命名空间。

回车确认,它就会为你创建好一个类。

  1. composer 支持 PSR-0 及 PSR-4 标准,标准规定 PHP 包以命名空间为区分,向外提供服务,所有暴露出来的类都应该在 作者名包名 命名空间下,例如 luiMFFCMail 类。这样,哪怕是名称一样的包只要是不同作者也可以在

  2. 命名空间可以类比成 Linux 系统中的 目录,在任何目录下都可以直接使用文件名打开当前目录下的所有文件和可执行程序,如果需要打开其他目录下的文件,就需要使用绝对路径或者相对路径。

  3. 大家可能在许多其他教程中见到过controller头部没有 namesapce 申明,更没有那一堆的 use xxx,像这个文件 /BlogController.php。这个文件在第8行直接使用了 Blog 这个类,这是为什么呢?

新文件位于 app/Providers。前往这个文件,修改为如下内容:

因为他们都已经在 learnlaravel 这个 composer 应用的配置文件中声明为自动加载了,而他们没有在顶部声明他们所在的命名空间,这样就会被自动加为顶级命名空间。这个配置文件是 composer.json,对象配置项为autoload 下的classmap 项。这个声明会让 Composer 在生成自动载入文件的时候,自动扫描该文件下所有的类以及所有子文件夹中的类,只要没有声明特定的命名空间,将会被自动加载为顶级空间。【之前表述有误,特此更正!】

<?php

namespace AppProviders;

use IlluminateSupportServiceProvider;
use AppHelpersRocketLauncher;

class RocketShipServiceProvider extends ServiceProvider
{
    protected $defer = true;

    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('AppHelpersContractsRocketShipContract', function(){

            return new RocketShip();

        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['AppHelpersContractsRocketShipContract'];
    }

}

关于命名空间更多详情,可以参考 【PHP 命名空间 入门】。

让我们看一下这一段:

韦德体育 ,OK,到目前为止我们的MVC三元素已经集齐了,那接下来该做什么了呢?配置路由。这里的路由并不是家里用的无线路由 :-D,而是 用户请求的URL到控制器某个方法的转换,function是PHP中代码段的最小单位,所以用户请求的一个路径,如 ,这条URL打给路由之后,路由就会去解析,应该调用哪个function,最终返回结果给用户。

<?php

namespace AppProviders;

use IlluminateSupportServiceProvider;
use AppHelpersRocketShip;

class RocketShipServiceProvider extends ServiceProvider
{

Laravel的路由采用闭包的方式返回结果,在app/routes.php 中增加下列内容:

简单粗暴。我们有了命名空间,use 申明和 class 申明。当你创建Service Provider时,你要导入(import)具体类,像这里我在 use 申明中导入了 RocketShip。

Route::get('admin/logout', array('as' => 'admin.logout', 'uses' => 'AppControllersAdminAuthController@getLogout'));
Route::get('admin/login', array('as' => 'admin.login', 'uses' => 'AppControllersAdminAuthController@getLogin'));
Route::post('admin/login', array('as' => 'admin.login.post', 'uses' => 'AppControllersAdminAuthController@postLogin'));
Route::group(array('prefix' => 'admin', 'before' => 'auth.admin'), function()
{
 Route::any('/', 'AppControllersAdminPagesController@index');
 Route::resource('articles', 'AppControllersAdminArticlesController');
 Route::resource('pages', 'AppControllersAdminPagesController');
});

接下来是:

前三条的意思是hold住两个get请求和一个post请求,下面是一个路由组,规定了一个前缀admin,增加了一个过滤器,auth.admin,内部有一个能同时适应get和post请求的‘/'路径,其完整路径是 资源控制器。

protected $defer = true;

上面说的那个过滤器 auth.admin,是Laravel提供的一个请求过滤器,这个文件就在路由文件的旁边,app/filters.php,在文件末尾增加:

属性 $defer 设置为 true 代表这个类只有在必要的时候才会被加载,这样应用可以更高效地运行。

Route::filter('auth.admin', function()
{
if ( ! Sentry::check()) {
return Redirect::route('admin.login');
}
});

接下来我们有一个 boot 函数,这只是个空的存根,我们不会对它做任何配置。

这样我们的权限验证就完成了。上面的代码意思是,在进入这个路由组中的任何一条路由之前,会先过一遍 auth.admin这个filter,这个filter会调用Sentry::check(),如果为false,将会进入if代码块,将用户的请求跳转到 命名路由‘admin.login',命名路由文档。从这个命名路由的名称大家也能看出来,就是跟访客说:傻逼,干啥呢,登录去~

然后,我们有 register 方法:

这里的 命名路由 功能是为了模仿 Ruby On Rails 的 link_to 到对象 的路由绑定功能,无奈PHP上传即部署无守护进程的特性,使得我们没法维护一个全量代码的路由表,没法像Rails那样实现 资源路由-资源对象-路由调用 三者绑定的功能,只能搞出一个半成品命名路由,人为地解决了当调整 /people 到 /human 时,要求名称改变而功能不变,同时要求代码自适应的需求。

/**
 * Register the application services.
 *
 * @return void
 */
public function register()
{
    $this->app->bind('AppHelpersContractsRocketShipContract', function(){

        return new RocketShip();

    });
}

这时候,我们就可以尝试访问我们的项目了。推荐配置Apache将一个端口指向learnlaravel这个项目的public目录下,即项目通过 这样的地址访问,十分不建议从子文件夹访问。如果你不会,可以运行

你可以看到我们正在使用绑定方法来将契约和具体类绑定到一起。这是就是Service Provider定义具体类方法的地方。所以你可以很便捷地调整你想要绑定的类。之后我们会看到这如何起效。

php artisan serve

最后,我们有 provides 方法:

启动PHP5.4的内建HTTP服务器。地址将会是 127.0.0.1 不可以访问。

/**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['AppHelpersContractsRocketShipContract'];
    }

}

下面,我们在浏览器中访问 /admin,注意URL会自动跳转到 /admin/login,这说明我们的filter起作用了,但你可能得到以下页面

如果你把属性 $defer 设为 true 的话你就会需要这个方法了。

韦德体育 2

不管怎么说,这是一个相当简单的类,只是涵盖了部分的精华。

这说明代码出错了。接下来我们修改 app/config/app.php 第一项为:

好,接下来我们需要告诉我们的应用来找到这个类,我们通过把它添加到config/app.php 中的 providers 数组来实现。

'debug' => true,
/*
         * Application Service Providers...
         */

        AppProvidersAppServiceProvider::class,
        AppProvidersEventServiceProvider::class,
        AppProvidersRouteServiceProvider::class,
        AppProvidersRocketShipServiceProvider::class,

刷新页面,错误提示出来了!有没有感觉Laravel4.2的错误提示很好看啊,确实不错,但我觉得没有4.1之前的好看 :-D。我得到了如下错误:

这里包含了一些其它的Provider作为参考,你可以看到我们的Provider在最后一行。保存完你就可以离开这里继续后面的了。

韦德体育 3

我们来修改一下 DemoController 的 index 方法:

说 AppControllersAdminAuthController 这个类未找到,这是为什么呢?这个文件明明有啊。

public function index(RocketShipContract $rocketship)
{
        $boom = $rocketship->blastOff();

        return view('demo.index', compact('boom'));
}

这就涉及到了另一个问题,Laravel中的autoload问题。Laravel基于命名空间,它只会自动加载所有顶级命名空间的类,就是说我们新增的这个控制器类不是在顶级命名空间下,所以就需要告诉Laravel,我这个类是存在的,怎么告诉它呢?运行

所以在这里,我们键入 RocketShipContract 并传递给实例变量 $rocketship。Laravel 通过Service Provider获知你其实想要的是 RocketShip 类,因为你在服务提供者里把它和契约绑定了。是不是很酷?

composer dump-autoload

然后我们简单地调用 blastoff 方法并把它赋值给一个要传递向视图的变量。让我们来修改一下视图:

可以了,刷新页面,他告诉我

@extends('layouts.master')

@section('content')

    {{ $boom }}

@endsection

View [admin._partials.assets] not found.

你可以看到我正在使用 blade 打印变量。所以浏览器中应该可以看到:

这个确实是,我们还没建立这个文件呢。建立一个空文件即可,如果是用generator建的话,别忘了把里面默认的内容删掉哦。再刷新页面,如果还有问题,我相信这个问题你可以自己解决。

Houston, we have ignition.

OK,一个丑的一逼的页面出现了,为什么它这么丑?(鸽子为什么这么大?)因为我们没有引入任何css和js文件,甚至连导航栏的html都不完整。这不要紧,来,按照我github上的代码,自己复制到相应文件中吧。另外,非常重要的一点,把我的项目中的public文件下的 js 和 css 两个文件夹完全复制到你们的public文件夹中。

所以现在为了更简单的描述能实现什么,我们可以在 Helpers 文件夹中创建第二个具体类。我们把它命名为 RocketLauncher.php,内容如下:

再刷新,如果你看到以下页面,说明你成功了!

<?php

namespace appHelpers;

use AppHelpersContractsRocketShipContract;

class RocketLauncher implements RocketShipContract
{

    public function blastOff()
    {

        return 'Houston, we have launched!';

    }

}

韦德体育 4

你可以发现这个我们的 RocketShip 类很像,只是 blastoff 方法略有不同。所以我们在Service Provider的 register 方法中修改其中一行代码来实现它:

3. 尝试登录

public function register()
    {
        $this->app->bind('AppHelpersContractsRocketShipContract', function(){

            return new RocketLauncher();

        });
    }

用seed新增一名管理员,顺便新增一个管理员组。新建app/database/seeds/SentrySeeder.php,内容为:

还包括 use 申明:

<?php
class SentrySeeder extends Seeder {
 public function run()
 {
 DB::table('users')->delete();
 DB::table('groups')->delete();
 DB::table('users_groups')->delete();
 Sentry::getUserProvider()->create(array(
 'email' => 'oo@xx.com',
 'password' => "ooxx",
 'first_name' => 'OO',
 'last_name' => 'XX',
 'activated' => 1,
 ));
 Sentry::getGroupProvider()->create(array(
 'name' => 'Admin',
 'permissions' => ['admin' => 1],
 ));
 // 将用户加入用户组
 $adminUser = Sentry::getUserProvider()->findByLogin('oo@xx.com');
 $adminGroup = Sentry::getGroupProvider()->findByName('Admin');
 $adminUser->addGroup($adminGroup);
 }
}
use AppHelpersRocketLauncher;

给app/database/seeds/DatabaseSeeder.php 新增一行:

根据上面的简单变动,我们现在有了基于契约约束的不同的实现,所以浏览器里的结果也会产生相应变化。

$this->call('SentrySeeder');

尽管我们为了这个教程只是做了一个超级无聊的例子,你还是可以通过它看到这个架构的好处。通过编写一个契约而不是一个具体类,我们给自己提供了一种更灵活和简单的而方法来管理代码。

然后运行:

这里有一些“陷阱”你需要注意。你没法直接重命名一个Service Provider,而是需要删除它并通过 artisan 新建一个,因为创建时其它地方也会有一些不被注意的改动。这很可能与自动加载(autoload)有关。如果你发生了这类问题,你可以尝试在命令行运行 composer dump-autoload。如果仍不起作用,那就还是删除文件并重新创建吧。

php artisan db:seed

另一件事是务必在最后一步才把Service Provider添加到 config/app.php。如果里面配置了一个并不存在的类估计 artisan 会崩溃。

成功以后,进数据库就会发现,users、groups、users_groups表均新增了一行。但是,articles和pages表也分别新增了10行,对,seed就是这么蠢萌^_^

Laravel 框架拥有完善的文档,你可以在这里阅读更多关于 Laravel Service Provider 的内容。

让我们来尝试登录!如果你得到:

我希望你能享受这个教程并觉得它有价值。点击页面来阅读全部的教程。如果可以,请评论、分享和点赞,谢谢!

Class AppControllersAdminPagesController does not exist

我没有捐款按钮,但如果你愿意支持我的工作或学习更多 Laravel 的知识,你可以通过购买我的书来实现,《Laraboot: laravel 5* For Beginners》,非常感谢。

这说明你成功了!

上一篇:如果你要在一个程序文件中实例化100个对象,如果需要使用一个类 下一篇:没有了

更多阅读

我们来看两种不同的路由,这就是视图文

前端 2020-04-30
本文由码农网 –任琦磊原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划...
查看全文

为了理解速度有多重要

前端 2020-04-30
1. 不要使用 mysql_ 函数 这一天终于来了,从此你不仅仅“不应该”使用mysql_函数。PHP 7已经把它...
查看全文

如果你要在一个程序文件中实例化100个对

前端 2020-04-30
PHP在魔术函数__autoload()方法出现以前,如果你要在一个程序文件中实例化100个对象,那么你必...
查看全文

友情链接: 网站地图

Copyright © 2015-2019 http://www.koi-bumi.com. 韦德体育有限公司 版权所有