正确构建Laravel项目


Correctly structuring Laravel project

我有一个视图,它在一个表中显示了一行10个数字(这是一个统计数据面板)。

它们是今天、明天、后天等的销售信息,按不同的州划分。

目前我有一些代码,比如:

class Transaction extends Eloquent {
    // other methods that aren't relevant
    public function scopeConfirmed($query)
    {
        return $query->where('status', '=', 'Confirmed');
    }
    public function scopeBooked($query)
    {
        return $query->where('status', '<>', 'Cancelled');
    }
    public function scopeDaysAhead($query, $days)
    {
        $start = 'Carbon'Carbon::now()->addDays($days)->toDateString();
        $end = 'Carbon'Carbon::now()->addDays($days+1)->toDateString();
        return $query->where('date', '>=', $start)->where('date', '<', $end);
    }
    // few other similar scopes
}

然后在我看来,我有:

(简化)

<td>
    {{Transaction::->daysAhead(0)
        ->booked()
        ->count()}}
</td>
<td>
    {{Transaction::->daysAhead(0)
        ->confirmed()
        ->count()}}
</td>
<td>
    {{Transaction::->daysAhead(1)
        ->confirmed()
        ->count()}}
</td>
<td>
    {{Transaction::->daysAhead(2)
        ->confirmed()
        ->count()}}
</td>

所以,在我看来,我正在处理Eloquent电话。实际上,有按销售人员和地点划分的范围,因此显示了10-20个值。

我可以通过大量的with语句将其移回控制器中,当然也可以将其放入控制器中的数组中,但这似乎没有太大帮助。

处理这个问题的最佳方法是什么?

尽可能减少视图的逻辑性,也不要用逻辑扰乱控制器。对大多数人来说,这意味着把所有的逻辑都塞进模型中。同样,这并不是一种有助于编写可重用且更重要的是可测试代码的方法。

Laravel的IoC容器和依赖项注入非常强大,应该用来以可测试的方式构建应用程序。

我也能理解为什么要传递查询对象,这是我在模型中经常做的事情。这似乎是有道理的,但很快就会发现,使用强大的查询生成器创建一个严格的查询生成器将创建一些湿代码。

我的建议是让你的模型尽可能精简,用它们来创建关系,设置有说服力的属性,验证规则数组等等。基于接口将所有逻辑抽象到存储库中。为什么?好吧,接口可以通过IoC绑定到它应该解析的类,这意味着它可以很容易地进行依赖注入和交换(例如Mockery),同时在您可能想要构建的任何替换中保持结构完整性(例如Mongo、CouchDB等实现)。

namespace Repositories;
interface TransactionInterface {
     public function findAll();
     public function findById($id);
     public function findByDaysAhead($start = 0, $end = 1)
}

对于存储库

namespace Repositories;
use Transaction;
class TransactionEloquent implements TransactionInterface {
    public function findAll()
    {
        return Transaction::all();
    }
    public function findById($id)
    {
        $transaction = Transaction::find($id);
        if ( ! $transaction )
        {
            throw new Exception("Transaction not found");
        }
        return $transaction;
    }
    public function findByDaysAhead($start = 0 , $end = 1)
    {
        // Create one query to return all the data you need
    }
}

然后,您可以在新的自定义ServiceProvider或routes.php中绑定此存储库。

App::bind('Repositories'TransactionInterface', 'Repositories'TransactionEloquent');

现在,您可以将接口依赖性注入到控制器中,该接口将解析为您的雄辩实现。如果你编写了一个不同的存储库实现,你可以简单地将其重新绑定到接口,这意味着它将在接口注入的任何地方使用(例如Mockery类)

class ApplicationController extends BaseController {
    public function __construct(Repositories'TransactionInterface $interface)
    {
        $this->repo = $interface;
    }
    public function getIndex()
    {
        return View::make('index', array('transactions' => $this->repo->findAll());
    }
}

在您的视图中,您只需要对数据和输出进行简单的循环,不需要任何逻辑。

显然,您可以在存储库中放入任意多的逻辑,并且您可以看到您的控制器、模型和视图仅履行其预期职责(OOPS单一责任原则)

这是一个非常复杂的问题的一个非常简短的答案。我希望这能指导你用Laravel编写可测试和可重用的模块化代码。

一些"轻"读取

  • Laravel 4的IoC容器
  • Laravel 4的服务提供商
  • NetTuts关于构建L4和主干应用程序的文章忽略主干部分,这是一篇关于L4 IoC绑定的精彩演练

放置文件等的位置

遵循PSR-0规范,我最终会得到这样的结构

- app
    - {name of app}
        - Repositories
            * TransactionInterface.php
            * TransactionEloquent.php

这两个文件的名称空间现在将是namespace {name of app}'Repositories

在composer中,您可以将其添加到自动加载对象中:

"psr-0": {
    "{name of app}" : "app/"
}

这将把所有PSR-0兼容的命名空间添加到自动加载器中,当您进行更改时,您可以使用

composer dump-autoload

重建自动加载器并包含新文件(并非总是必需的,但始终比composer update更好、更快)。

有多少个存储库

我通常最终每个模型有1个以上的存储库。例如,我在前端有一个模型和单个模型的骨干集合。

使用Backbone.sync(param, collection)将始终使用将输入处理为模型数组的repo。其中,作为Backbone.sync(param, model),将使用处理正在发送的单个模型的回购。我也会有两个Laravel资源控制器来处理这个问题。

服务提供商

我把这些放在我的应用文件夹路由中,我的应用程序在这个例子中被称为"MFL"

- MFL
    - Repositories
    - MFLServiceProvider.php

我将其添加到config'app.php 中的服务提供商阵列中

namespace MFL;
use Illuminate'Support'ServiceProvider;
class MFLServiceProvider extends ServiceProvider {
    public function register()
    {
        // Register bindings here, don't use other service providers here
        // you can't be sure they are loaded as of yet
        $this->app->bind('MFL'Repositories'TransactionInterface', 'MFL'Repositories'TransactionEloquent');
    }
    public function boot()
    {
        // Do anything else here with assurance all service providers are
        // fully loaded and the application is ready
    }
}

使用这种方法,您不会用IoC绑定污染routes.php,并且您可以从逻辑上将所有代码划分为服务。出于后一个原因,这是我更喜欢的方法。

视图composers中有第三个选项。在最基本的层面上,这些都包含在routes.php中,但我喜欢按视图路径将它们分开,这样composers/transactions.php就会处理视图/事务中的所有视图,比如"transactions.dashboard"

View::composer('transactions.dashboard', function ($view)
{
    $view->day0_count = Transaction::->daysAhead(0)
        ->booked()
        ->count();
}

$day0_count现在在视图中可用。

<td>{{ $day0_count }}</td>