如何在Laravel中创建自定义辅助函数

603
我想创建一些辅助函数来避免在Laravel视图之间重复编写代码。例如:

view.blade.php

<p>Foo Formated text: {{ fooFormatText($text) }}</p>

它们基本上是文本格式化函数。我应该如何定义全局可用的辅助函数,例如fooFormatText()

23个回答

763
在您的应用程序文件夹中创建一个 helpers.php 文件,并使用composer加载它:
"autoload": {
    "classmap": [
        ...
    ],
    "psr-4": {
        "App\\": "app/"
    },
    "files": [
        "app/helpers.php" // <---- ADD THIS
    ]
},

在你的composer.json文件中添加后,运行以下命令:
composer dump-autoload

如果你不想将helpers.php文件放在app目录下(因为它不是PSR-4命名空间类文件),你可以像laravel.com网站一样将其存储在bootstrap目录中。记得在composer.json文件中设置它:in the bootstrap directory
"files": [
    "bootstrap/helpers.php"
]

122
新手提示:在修改composer.json后,使用这个命令。composer dump-autoload - Allfarid Morales García
20
"也许只是一个有用的提示,因为答案没有明确说明你需要在此之后这样做。" - Matt McDonald
11
我赞成使用辅助函数使编写视图更加容易,但我讨厌其他答案中引用这篇答案的频率。别误解我的意思,这是一个好的、正确的答案,但我担心人们会滥用它,并重新开始编写大量低质量、组织混乱的 PHP 函数。 - andrewtweber
50
我不理解这种方法。Composer 应该是一个用于包含库的工具:Laravel 也可以完全不使用它,而 Composer 也可以脱离 Laravel 使用。这个建议告诉我们在我们的应用程序中创建一个文件,然后离开我们的应用程序,去到 Composer,告诉 Composer 返回我们的应用程序并包含这个文件。Laravel 显然处理了文件的包含,对吧?为什么我们要放弃 Laravel 的本地实现,使用这个外部工具来帮助我们包含一个文件,从而更多地将我们的应用与 Composer 耦合起来?这是懒惰,还是我有什么地方理解有误? - dKen
10
Laravel使用Composer的自动加载程序来确定包括其依赖的所有库和文件的位置。这在bootstrap/autoload.php中引用。请阅读该文件中的注释。方法是将文件的引用添加到composer.json中,然后“转储自动加载程序”,这会重新生成Composer的自动加载程序,以便Laravel可以找到它。使用Composer的“files”集合是添加库或杂项函数文件的好方法,这些文件没有整齐地打包在Composer包中。有一个地方来处理所有的“顺便提一下我必须包含这个奇怪的文件”的情况是很好的。 - Phillip Harrington
显示剩余22条评论

486

在 Laravel 5 中轻松创建自定义类

此答案适用于 Laravel 中的一般自定义类。有关更 Blade 特定的答案,请参见Laravel 5中的自定义 Blade 指令

步骤1:创建您的 Helpers(或其他自定义类)文件,并为其提供相匹配的命名空间。编写您的类和方法:

<?php // Code within app\Helpers\Helper.php

namespace App\Helpers;

class Helper
{
    public static function shout(string $string)
    {
        return strtoupper($string);
    }
}

第二步: 创建别名:

<?php // Code within config/app.php

    'aliases' => [
     ...
        'Helper' => App\Helpers\Helper::class,
     ...

第三步:在项目根目录下运行composer dump-autoload

第四步:在您的Blade模板中使用它:

<!-- Code within resources/views/template.blade.php -->

{!! Helper::shout('this is how to use autoloading correctly!!') !!}

额外加分项:可以在你的 Laravel 应用程序中的任何地方使用这个类:

<?php // Code within app/Http/Controllers/SomeController.php

namespace App\Http\Controllers;

use Helper;

class SomeController extends Controller
{

    public function __construct()
    {
        Helper::shout('now i\'m using my helper class in a controller!!');
    }
    ...

来源: http://www.php-fig.org/psr/psr-4/

原因: https://github.com/laravel/framework/blob/master/src/Illuminate/Support/ClassLoader.php

自动加载的起源: http://php.net/manual/en/language.oop5.autoload.php


53
要明确的是,这个答案实际上并没有涉及到助手函数,它们是全局命名空间函数。相反,它鼓励将助手函数转换为类方法。这通常是最好的方法,但实际上并没有回答在这里提出的问题,这就是为什么其他答案相比如此复杂的原因。 - Dan Hunsaker
1
函数助手意味着它在 Blade 中也可用。你如何使这个函数在 Blade 中可用?你不能在 Blade 中调用 Helper::prettyJason(parameters)。 - MaXi32
2
是的,我曾经深入研究过框架,并找到了他们引入辅助程序的位置。而且,我完全同意,命名空间静态类的方法比大多数情况下被请求或推荐的方法要干净得多。事实上,辅助程序并不是 Laravel 的方式,而是 CodeIgniter 2.x 的遗留物,仍未被淘汰。因此,我对这种方法不能完全回答 OP 的问题的吹毛求疵更多地是为了强调您不会得到辅助程序,而是得到了更好的东西。 - Dan Hunsaker
1
也许一个全面的答案会说,嘿,这就是你可以创建全局助手的方法(带有代码示例),但实际上有一种更“友好”的做法,这才是我推荐的。 - heisian
2
对于 Laravel 8(我在这个版本上进行了测试),在 composer dump-autoload 命令之前,您需要运行:php artisan config:cache 来清除文件 config/app.php 的缓存。然后它就会起作用。 - Axel Paris
显示剩余12条评论

353

我的最初想法也是使用composer自动加载,但这对我来说并不像Laravel 5。Laravel 5大量使用服务提供者(Service Providers),它可以引导您的应用程序。

首先,在我的app目录中创建了一个名为Helpers的文件夹。然后在Helpers文件夹中添加了我想要添加的函数文件。拥有多个文件的文件夹可以避免一个过长且难以管理的大文件。

接下来,我通过运行artisan命令创建了一个HelperServiceProvider.php文件:

php artisan make:provider HelperServiceProvider

register方法中,我添加了这段代码片段。
public function register()
{
    foreach (glob(app_path().'/Helpers/*.php') as $filename){
        require_once($filename);
    }
}

最后,在提供程序数组中将服务提供者注册到您的config/app.php中。
'providers' => [
    'App\Providers\HelperServiceProvider',
]

现在你的Helpers目录中的任何文件都已加载并准备好使用。

更新于2016-02-22

这里有很多好的选择,但如果我的答案适合您,我已经为包含这种方式的帮助程序制作了一个软件包。您可以使用该软件包进行灵感,也可以随意使用Composer下载它。它具有一些我经常使用的内置助手(默认情况下全部未激活),并允许您使用简单的Artisan生成器创建自己的自定义助手。它还解决了一个回答者提出的使用映射器的建议,并允许您明确定义要加载的自定义助手,或者默认情况下自动加载helper目录中的所有PHP文件。非常感谢您的反馈和PR!

composer require browner12/helpers

Github: browner12/helpers

GitHub: browner12/helpers


32
对于只需要添加少量功能的人来说,使用composer自动加载已经足够了;但对于那些可能有很多帮助函数和多个文件进行组织的人来说,则必须采用多文件组织的解决方案。这个解决方案基本上就是我在L4中所做的,只不过我是在start.php文件中注册文件(这并不是很好,但暂时起到了作用)。您是否有关于加载多个文件的其他建议? - Andrew Brown
8
如果您有多个文件,请将它们全部添加到composer.json文件中。即使在那里添加5-10行,也比你这里写的要更有意义 - Joseph Silber
26
我认为这项技术有很大的优点。它既精巧又高效,因为每次创建辅助文件时不需要记得去修改composer.json文件。 - impeto
9
非常好的解决方案。唯一我不同意的是您添加文件的方式,我认为应该使用一个映射器,在其中添加要加载的文件名。考虑错误!如果一个文件中只有一个辅助程序失败了,那么您就必须删除所有辅助程序,或者让网站在解决问题之前处于损坏状态。 - Pablo Ezequiel Leone
4
你是否使用App\Providers命名空间?我如何从控制器和视图中调用该辅助函数。抱歉,这是个新手问题。 - Cengkaruk
显示剩余14条评论

93

这是 JeffreyWay 在这个Laracasts讨论中提出的建议。

  1. 在你的 app/Http 目录下创建一个 helpers.php 文件并添加你的函数。
  2. composer.jsonautoload 块中添加 "files": ["app/Http/helpers.php"]
  3. 运行 composer dump-autoload

23
助手可能不仅限于HTTP。将其放在app/helpers.phpapp/Helpers/似乎更合适。 - sepehr
2
如果我们正在使用共享服务器,并且没有使用composer dump-autoload的选项,那该怎么办呢? - user3201500
@user3201500 这是另一个问题,如果您想遵循上面的答案,可能需要手动完成。或者您可以从其他答案中选择。要手动反映 composer dump-autoload,您可以按照此操作:http://developed.be/2014/08/29/composer-dump-autoload-laravel/ - itsazzad

67

在SO和Google上筛选了各种答案后,我仍然找不到最佳方法。大多数答案建议我们离开应用程序,依赖于第三方工具Composer来完成这项工作,但我不相信为了包含一个文件而将应用程序与工具进行耦合是明智的。

Andrew Brown的回答 最接近我认为应该采取的方法,但是(至少在5.1中),服务提供商步骤是不必要的。Heisian的回答 强调了使用PSR-4,让我们更近了一步。这是我在视图中实现帮助器的最终方式:

首先,在应用目录中的任何位置创建一个帮助器文件,并指定名称空间:

namespace App\Helpers;

class BobFinder
{
    static function bob()
    {
        return '<strong>Bob?! Is that you?!</strong>';
    }
}

接下来,在config\app.php文件中的aliases数组中为你的类取别名:

Next, alias your class in config\app.php, in the aliases array:


'aliases' => [
    // Other aliases
    'BobFinder' => App\Helpers\BobFinder::class
]

这应该就是你需要做的全部内容了。 PSR-4 和别名会将 Helper 暴露给你的视图,因此在视图中,如果你输入:

{!! BobFinder::bob() !!}

它应该输出:

<strong>Bob?! Is that you?!</strong>

感谢您发布这篇文章。正如@Dan-Hunsaker在我的解决方案中指出的那样,我们仍然没有得到一个全局命名空间函数,即能够简单地编写{!! bob() !!}。我将继续搜索并查看是否可能实现。 - heisian
2
我再次考虑了一下,试图使bob()成为真正的全局函数并不明智。命名空间是有其存在的意义的,我们不应该将bob()与基本的PHP函数混在一起调用。我会将你的别名代码添加到我的代码中 - 谢谢! - heisian
1
我认为这是最好的。 - Jimmy Obonyo Abor
1
@AndrewBrown 很不幸,Laravel文档中几乎每个示例都使用了facade,而没有任何警告或建议说明何时使用它们以及何时不使用它们。这使得Laravel新手很难知道他们有选择不使用它们的选项,以及这样做的影响是什么。 - dKen
1
@AndrewBrown 这不是一个外观。这是 PSR-4 自动加载。http://www.php-fig.org/psr/psr-4/ - heisian
显示剩余5条评论

43

Laravel 5中的自定义Blade指令

是的,还有另一种方法可以实现这个功能!

步骤1: 注册一个自定义的Blade指令:

<?php // code in app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

use Blade; // <-- This is important! Without it you'll get an exception.

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
     public function boot()
     {
         // Make a custom blade directive:
         Blade::directive('shout', function ($string) {
             return trim(strtoupper($string), '(\'\')');
         });

         // And another one for good measure:
         Blade::directive('customLink', function () {
             return '<a href="#">Custom Link</a>';
         });
     }
    ...

第二步:使用您的自定义 Blade 指令:

<!-- // code in resources/views/view.blade.php -->

@shout('this is my custom blade directive!!')
<br />
@customLink

输出:

这是我的自定义 Blade 指令!
自定义链接


来源:https://laravel.com/docs/5.1/blade#extending-blade

更多阅读:https://mattstauffer.co/blog/custom-conditionals-with-laravels-blade-directives


如果您想学习如何最好地制作可以在任何地方使用的自定义类,请参见Laravel 5 中的自定义类,简单易行的方法


这应该被标记为最佳答案,因为问题是“避免在某些视图之间重复代码”。关键词是VIEWS。 :) - Aleksandrs
@heisian,你如何将变量传递到自定义指令中? - Mark

31

这是我的HelpersProvider.php文件:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class HelperServiceProvider extends ServiceProvider
{
    protected $helpers = [
        // Add your helpers in here
    ];

    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     */
    public function register()
    {
        foreach ($this->helpers as $helper) {
            $helper_path = app_path().'/Helpers/'.$helper.'.php';

            if (\File::isFile($helper_path)) {
                require_once $helper_path;
            }
        }
    }
}

您应该在app文件夹下创建一个名为Helpers的文件夹,然后在其中创建一个名为whatever.php的文件,并将字符串whatever添加到$helpers数组中。

完成!

编辑

我不再使用此选项,我目前正在使用composer来加载静态文件,例如帮助程序。

您可以直接添加帮助程序:

...
"autoload": {
    "files": [
        "app/helpers/my_helper.php",
        ...
    ]
},
...

创建映射器的原因除了性能外还有其他原因吗? 正如Andrew Brown所写的那样,可以直接使用glob()加载目录中的所有文件,为什么要创建映射器呢? 如果您想指定要包含的文件,为什么不像Joseph Silber所写的那样在composer.json中指定文件以进行自动加载? 为什么您更喜欢这种解决方案? 我不是说这是一个糟糕的解决方案,我只是很好奇。 - Pelmered
3
采用映射方法更容易地进行选择性地启用/禁用帮助程序,例如如果其中一个帮助文件包含破坏性错误。然而,在服务提供者中映射文件与在 composer.json 文件中映射文件并没有太大区别,除了两点-首先,它将映射保留在应用程序本身内,而不是元数据文件;其次,它不需要每次更改要加载的文件列表时重新运行 composer dump-autoload - Dan Hunsaker
无需使用 includerequire,Laravel 已经内置了 PSR-4 自动加载功能:http://www.php-fig.org/psr/psr-4/ - heisian
1
使用PSR-4和Composer无法让您开启/关闭帮助程序。 - Pablo Ezequiel Leone
@PabloEzequielLeone 那我该如何在控制器或 Blade 文件中使用它呢?如果你担心每次加载所有控制器的帮助程序,那么这似乎是最好的选择,但对于 Laravel 的初学者(比如我自己)来说并不是很好。 - VinGarcia

20

由于OP要求最佳实践,我认为我们还缺少一些好的建议。

一个单独的helpers.php文件远非良好实践。首先,因为您混合了许多不同种类的函数,所以您违反了良好的编码原则。此外,这可能不仅会损害代码文档,还会损害代码指标,如Cyclomatic复杂性、可维护性指数和Halstead体积。函数越多,情况就越糟。

使用像phpDocumentor这样的工具,代码文档将是可以理解的,但是使用Sami时,它无法呈现过程性文件。 Laravel API文档就是这种情况-没有辅助函数文档:https://laravel.com/api/5.4

可以使用PhpMetrics等工具分析代码指标。使用PhpMetrics版本1.x来分析Laravel 5.4框架代码,将会得到非常糟糕的CC/MI/HV指标,适用于src/Illuminate/Foundation/helpers.phpsrc/Illuminate/Support/helpers.php文件。

多个上下文帮助文件(例如string_helpers.phparray_helpers.php等)肯定会改善这些糟糕的指标,从而使代码更易于维护。根据所使用的代码文档生成器,这已经足够好了。

通过使用具有静态方法的帮助器类,可以进一步改进它们,以便可以使用命名空间进行上下文化。就像 Laravel 已经在 Illuminate\Support\StrIlluminate\Support\Arr 类中所做的一样。这将改善代码指标/组织和文档。可以使用类别名使它们更易于使用。

使用类结构使代码组织和文档更好,但另一方面,我们最终失去了那些优秀的简短易记的全局函数。我们可以通过为这些静态类方法创建函数别名来进一步改进这种方法。这可以手动或动态地完成。

Laravel 内部使用第一种方法,通过在过程性帮助器文件中声明函数来映射到静态类方法。这可能不是理想的事情,因为您需要重新声明所有内容(文档块/参数)。
我个人使用动态方法,在执行时创建这些函数的 HelperServiceProvider 类:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class HelperServiceProvider extends ServiceProvider
{
    /**
     * The helper mappings for the application.
     *
     * @var array
     */
    protected $helpers = [
        'uppercase' => 'App\Support\Helpers\StringHelper::uppercase',
        'lowercase' => 'App\Support\Helpers\StringHelper::lowercase',
    ];

    /**
     * Bootstrap the application helpers.
     *
     * @return void
     */
    public function boot()
    {
        foreach ($this->helpers as $alias => $method) {
            if (!function_exists($alias)) {
                eval("function {$alias}(...\$args) { return {$method}(...\$args); }");
            }
        }
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

可以说这是过度工程,但我并不这么认为。它运行得非常好,与预期的相反。至少在使用PHP 7.x时,它不会产生太多执行时间成本。

17

对于我 Laravel 项目中的自定义助手库,我已经在 Laravel/App 目录下创建了一个名为 Libraries 的文件夹,并在 Libraries 文件夹中为不同的助手库创建了各种文件。

创建完我的助手文件后,我只需要像这样在我的 composer.json 文件中包含所有这些文件:

...
"autoload": {
        "classmap": [
            "database"
        ],
        "files": [
            "app/Libraries/commonFunctions.php"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    },
...

并执行

composer dump-autoload

1
composer dump-autoloadcomposer dumpautoload 都可以使用,实际上 composer du 也可以。 - Akshay Khale
你提到在你的app/Libraries文件夹中添加了不同的辅助库,但是只在你的composer.json文件中添加了一个文件。你是否在commonFunction.php文件中包含了这些辅助文件? - Kolawole Emmanuel Izzy
1
这个方法太棒了!我为此浪费了将近一天的时间,然后我找到了这个答案。非常感谢! - Tharindu Thisarasinghe

14
不需要包含您的自定义帮助类,您实际上可以在别名下将其添加到您的config/app.php文件中。 应该是这样的。
 'aliases' => [ 
    ...
    ...
    'Helper' => App\Http\Services\Helper::class,
 ]

然后在你的控制器中,使用方法 'use Helper' 包含 Helper类,这样你就可以简单地调用 Helper 类中的某些方法。

eg. Helper::some_function();

或者在资源视图中,您可以直接调用 Helper 类。

eg. {{Helper::foo()}}

但这仍然是开发者编码风格的方法,我们可能有不同的解决问题方式,我只是想与初学者分享我的经验。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接