支持Laravel.io的持续发展 →

管理Laravel服务

23 Dec, 2021 7 min read

我上一篇文章关于测试API和服务的测试留下了悬念

在测试中切换实现方式还有另一种方法:管理器。但这将是另一篇博客文章的内容。

哦,这不是很烦人吗?在此提前道歉。不过,不用担心,在这篇文章中,我将揭示有关Laravel中的管理器的一切,包括它们的作用以及如何让Laravel开发者的生活变得更简单。

什么是管理器?

如果您参考我之前的文章关于测试API和服务的测试,您会注意到我们必须在TestCase中放置以下代码。

<?php

namespace Tests;

use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
use Tests\Doubles\FakeTwitterClient;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
    use LazilyRefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        $this->swap(Twitter::class, new FakeTwitterClient());
    }
}

这虽然是可行的,但对于初学者来说可能不太明显,因为它的实现并不遵循Laravel的常规约定。那么,在这种情况下,Laravel的标准约定是什么?看看config/cache.php文件。您会看到这样一些内容。

<?php

use Illuminate\Support\Str;

return [

    /*
    |--------------------------------------------------------------------------
    | Default Cache Store
    |--------------------------------------------------------------------------
    |
    | This option controls the default cache connection that gets used while
    | using this caching library. This connection is used when another is
    | not explicitly specified when executing a given caching function.
    |
    */

    'default' => env('CACHE_DRIVER', 'file'),

这在Laravel中很常见;我们可以在配置文件中指定应用于特定服务的实现或驱动程序。但在测试中使用假实现进行替换在哪里?检查您的项目根目录中的phpunit.xmlphpunit.dist.xml文件。在那里,您可能会看到类似以下内容。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

这里发生了什么?当我们运行测试套件时,Pest 或 PHPUnit 会加载这个 XML 文件。它会读取这些 <server/> 节点,并使用 .env 文件中的值替换这些值。因此,在这种情况下,我们的缓存驱动器在测试中始终是 array,即使我们在 .env 文件中定义了 redis。多么优雅的方法!

但是,“file”、“redis”或“array”这几个词是如何变为我们合约的实际实现的呢?答案是 Manager。让我们为我们的 Twitter 服务创建一个。

创建一个 Manager

我喜欢在实现所在的目录下放置我的 Manager 类。所以,让我们在 app/Services/Twitter/TwitterManager.php 创建一个新文件。

<?php

namespace App\Services\Twitter;

use Illuminate\Support\Manager;

final class TwitterManager extends Manager
{
    public function getDefaultDriver(): string
    {
    }
}

请注意,我们的类扩展了 Illuminate\Support\Manager。该类有一个抽象方法,我们需要实现它:getDefaultDriver。这个方法的目的是由应用程序使用我们的 Twitter 合约的实现来决定。注意它返回一个字符串。可能是像 fileredisarray 这样的字符串。你们觉得我们会怎么进行下去呢?

让我们更新 config/services.php 中的 twitter 配置,以添加对我们的所需实现的支持。

<?php

return [
    'mailgun' => [
        'domain' => env('MAILGUN_DOMAIN'),
        'secret' => env('MAILGUN_SECRET'),
        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    ],

    'postmark' => [
        'token' => env('POSTMARK_TOKEN'),
    ],

    'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],

    'twitter' => [
        'default' => env('TWITTER_DRIVER', 'oauth'),
        'consumer_key' => env('TWITTER_CONSUMER_KEY'),
        'consumer_secret' => env('TWITTER_CONSUMER_SECRET'),
        'access_token' => env('TWITTER_ACCESS_TOKEN'),
        'access_token_secret' => env('TWITTER_ACCESS_TOKEN_SECRET'),
    ],
];

现在,回到我们的 TwitterManager 中,我们可以访问并返回这个配置值。

<?php

namespace App\Services\Twitter;

use Illuminate\Support\Manager;

final class TwitterManager extends Manager
{
    public function getDefaultDriver(): string
    {
        return $this->config->get('services.twitter.default') ?? 'null';
    }
}

请注意,manager 给我们提供了一个 config 属性,这是非常有用的。同时,注意我们使用 ?? 来捕获 null 值,并返回字符串 null。这很重要。

因此,我们为我们的实现定义了两个可能的字符串值:oauthnull。我们如何将这些选项翻译成相应的类?Laravel 的 Manager 类将查找在您的 manager 上定义的一个遵循以下签名的方法:createXDriver,其中 X 是我们想要实现字符串值。让我们利用这个知识来构建我们的类。

<?php

namespace App\Services\Twitter;

use Illuminate\Support\Manager;
use Tests\Doubles\FakeTwitterClient; 
use Abraham\TwitterOAuth\TwitterOAuth;

final class TwitterManager extends Manager
{
    public function getDefaultDriver(): string
    {
        return $this->config->get('services.twitter.default') ?? 'null';
    }

    public function createOauthDriver(): OauthClient
    {
        return new OauthClient();
    }

    public function createNullDriver(): FakeTwitterClient
    {
        return new FakeTwitterClient();
    }
}

很简单!如果您的实现需要服务容器中的项目,Manager 给您提供了一个 container 属性,您可以用来加载这些依赖项并将它们传递给实现。

现在,我们的 manager 不会自动被调用。我们需要更新在 AppServiceProvider 中创建的绑定,以使用此 manager。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Contracts\Services\Twitter;
use Illuminate\Contracts\Container\Container; 
use App\Services\Twitter\TwitterManager; 

class AppServiceProvider extends ServiceProvider
{

    public function register(): void
    {
        $this->app->bind(Twitter::class, function (Container $container) {
            return (new TwitterManager($container))->driver();
        }); 
    }

}

所以,现在让我们分解我们的应用程序在请求服务容器中的 Twitter 合约实现时将经历的流程。

  1. Laravel 将创建一个 TwitterManager 实例,并在其上调用 driver 方法。
  2. driver 方法(这是基础 Manager 类的一部分)将调用我们先前在 TwitterManager 中必须实现的 getDefaultDriver 方法。
  3. getDefaultDriver 方法将查看我们的 config/services.php 文件中的 twitter.default 键。
  4. 该键将被用来在 manager 上调用一个方法:create[Key]Driver
  5. create[Key]Driver 方法将返回所请求的驱动程序的实现。

明白了吗?完成这些操作后,我们现在可以更新 phpunit.xml 文件,用我们的模拟驱动程序覆盖 .env 文件中的内容。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/> 
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
        <server name="TWITTER_DRIVER" value="null"/>
    </php>
</phpunit>

当然,你现在可以移除我们在 TestCase 中添加的手动交换。

结论

所以,这就是 Laravel 中的 managers!整合起来非常简单,并且功能强大。如果你将来添加了第三个实现,只需在我们 manager 中添加一个名为 createThirdImplementation 的新方法,然后在几秒钟内就可以上线。

就像往常一样,我希望你学到了一些新东西。

诚挚问候,Luke

最后更新时间:1 年前。

driesvints, akhmatovalexander 喜欢这篇文章

2
喜欢这篇文章? 让作者知道,并给他们一些支持!

你可能喜欢的其他文章

2024年3月11日

如何使用Larastan将你的Laravel应用程序从0提升到9

在Laravel应用程序执行前发现错误是可能的,这要归功于Larastan,它...

阅读文章
2024年7月19日

在不使用特性标签的情况下标准化API响应

我发现大多数用于API响应的库都是使用特性标签实现的...

阅读文章
2024年7月17日

在Laravel项目中通过Discord通知收集反馈

如何在Laravel项目中创建反馈模块,并在接收到消息时收到Discord通知...

阅读文章

我们感谢这些 令人惊叹的公司 支持我们

您的标志在这里?

Laravel.io

Laravel的解决问题、知识共享和社区建设平台。

© 2024 Laravel.io - 版权所有。