Module – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Sun, 24 May 2026 10:40:56 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 Nwidart\Modules\Exceptions\ModuleNotFoundException : Module [Blog] does not exist!,模块中数据库迁移文件的执行(无需发布) https://www.shuijingwanwq.com/2023/01/11/7341/ https://www.shuijingwanwq.com/2023/01/11/7341/#respond Wed, 11 Jan 2023 01:34:47 +0000 registerMigrations()]]> registerPublishing()]]> https://www.shuijingwanwq.com/?p=7341 Post Views: 125 1、当在 Modules/Blog 下删除了文件 module.json 后,生成新的迁移文件:Nwidart\Modules\Exceptions\ModuleNotFoundException : Module [Blog] does not exist!。如图1
当在 Modules/Blog 下删除了文件 module.json 后,生成新的迁移文件:Nwidart\Modules\Exceptions\ModuleNotFoundException  : Module [Blog] does not exist!

图1



PS E:\wwwroot\laravel6-modules-demo> php artisan module:make-migration delete_wp_is_default_theme_id_from_theme_saas_task_table Blog

   Nwidart\Modules\Exceptions\ModuleNotFoundException  : Module [Blog] does not exist!

  at E:\wwwroot\laravel6-modules-demo\vendor\nwidart\laravel-modules\src\FileRepository.php:396
    392|         if ($module !== null) {
    393|             return $module;
    394|         }
    395|
  > 396|         throw new ModuleNotFoundException("Module [{$name}] does not exist!");
    397|     }
    398|
    399|     /**
    400|      * Get all modules as laravel collection instance.

  Exception trace:

  1   Nwidart\Modules\FileRepository::findOrFail("Blog")
      E:\wwwroot\laravel6-modules-demo\vendor\nwidart\laravel-modules\src\Traits\ModuleCommandTrait.php:16

  2   Nwidart\Modules\Commands\MigrationMakeCommand::getModuleName()
      E:\wwwroot\laravel6-modules-demo\vendor\nwidart\laravel-modules\src\Commands\MigrationMakeCommand.php:115

  Please use the argument -v to see more details.


2、使用 make:migration Artisan 命令来创建迁移,使用 –path 选项以自定义生成迁移文件的个人存放路径。如图2
使用 make:migration Artisan 命令来创建迁移,使用 --path 选项以自定义生成迁移文件的个人存放路径

图2



PS E:\wwwroot\laravel6-modules-demo> php artisan make:migration delete_wp_is_default_theme_id_from_theme_saas_task_table --path=Modules/Blog/Database/Migrations
Created Migration: 2023_01_09_073351_delete_wp_is_default_theme_id_from_theme_saas_task_table
PS E:\wwwroot\laravel6-modules-demo>


3、一般在模块中的迁移是需要发布的,但是由于现在模块 Blog 已经不存在,因此,无法发布


PS E:\wwwroot\laravel6-modules-demo> php artisan module:publish-migration Blog

   Nwidart\Modules\Exceptions\ModuleNotFoundException  : Module [Blog] does not exist!

  at E:\wwwroot\laravel6-modules-demo\vendor\nwidart\laravel-modules\src\FileRepository.php:396
    392|         if ($module !== null) {
    393|             return $module;
    394|         }
    395|
  > 396|         throw new ModuleNotFoundException("Module [{$name}] does not exist!");
    397|     }
    398|
    399|     /**
    400|      * Get all modules as laravel collection instance.

  Exception trace:

  1   Nwidart\Modules\FileRepository::findOrFail("Blog")
      E:\wwwroot\laravel6-modules-demo\vendor\nwidart\laravel-modules\src\Commands\PublishMigrationCommand.php:32

  2   Nwidart\Modules\Commands\PublishMigrationCommand::handle()
      E:\wwwroot\laravel6-modules-demo\vendor\laravel\framework\src\Illuminate\Container\BoundMethod.php:36

  Please use the argument -v to see more details.


4、最终决定修改 Blog 模块的 服务加载类 ServiceProvider


    /**
     *
     * @return void
     */
    public function register()
    {
        // ...
        $this->registerMigrations();
        $this->registerPublishing();
	}


    /**
     * Register the package's migrations.
     *
     * @return void
     */
    private function registerMigrations()
    {
        if ($this->app->runningInConsole()) {
            $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
            $this->loadFactoriesFrom(__DIR__.'/../Database/factories');
        }
    }

    /**
     * Register the package's publishable resources.
     *
     * @return void
     */
    private function registerPublishing()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../Database/Migrations' => database_path('migrations'),
            ], 'theme-store-db-migrations');
        }
    }



5、已经无需发布模块中的数据迁移文件,在执行数据库迁移时,会自动运行模块 Blog 中的数据库迁移文件。


E:\wwwroot\laravel6-modules-demo> php artisan migrate
Migrating: 2023_01_09_154319_delete_wp_is_default_theme_id_from_theme_saas_task_table
Migrated:  2023_01_09_154319_delete_wp_is_default_theme_id_from_theme_saas_task_table (0 seconds)
E:\wwwroot\laravel6-modules-demo>


6、即使待执行的数据库迁移文件,有在目录:/database/migrations,还有在模块目录:/Modules/ThemeStoreDB/Database/Migrations 中的,也会自动按照创建时间顺序执行。


PS E:\wwwroot\object> php artisan migrate
Migrating: 2022_12_20_145928_create_theme_saas_task_table
Migrated:  2022_12_20_145928_create_theme_saas_task_table (0.05 seconds)
Migrating: 2023_01_04_094154_add_from_theme_installation_ids_to_theme_saas_task_table
Migrated:  2023_01_04_094154_add_from_theme_installation_ids_to_theme_saas_task_table (0.04 seconds)
Migrating: 2023_01_09_155849_drop_wp_is_default_theme_id_from_theme_saas_task_table
Migrated:  2023_01_09_155849_drop_wp_is_default_theme_id_from_theme_saas_task_table (0.04 seconds)
PS E:\wwwroot\object>


]]>
https://www.shuijingwanwq.com/2023/01/11/7341/feed/ 0
在 Laravel 6 中的模块(nWidart/laravel-modules:7.3)上定义调度任务 https://www.shuijingwanwq.com/2023/01/07/7330/ https://www.shuijingwanwq.com/2023/01/07/7330/#respond Sat, 07 Jan 2023 02:27:59 +0000 https://www.shuijingwanwq.com/?p=7330 Post Views: 243 1、生成一个新模块:Blog。


PS E:\wwwroot\laravel6-modules-demo> php artisan module:make Blog
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/module.json
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Routes/web.php
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Routes/api.php
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Resources/views/index.blade.php
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Resources/views/layouts/master.blade.php
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Config/config.php
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/composer.json
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Resources/assets/js/app.js
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/Resources/assets/sass/app.scss
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/webpack.mix.js
Created : E:\wwwroot\laravel6-modules-demo\Modules/Blog/package.json
Created : E:/wwwroot/laravel6-modules-demo/Modules/Blog/Database/Seeders/BlogDatabaseSeeder.php
Created : E:/wwwroot/laravel6-modules-demo/Modules/Blog/Providers/BlogServiceProvider.php
Created : E:/wwwroot/laravel6-modules-demo/Modules/Blog/Providers/RouteServiceProvider.php
Created : E:/wwwroot/laravel6-modules-demo/Modules/Blog/Http/Controllers/BlogController.php
Module [Blog] created successfully.


2、默认情况下,模块类不会自动加载。 你可以使用 psr-4 自动加载你的模块。编辑 composer.json。如图1
默认情况下,模块类不会自动加载。 你可以使用 psr-4 自动加载你的模块。编辑 composer.json

图1



    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Modules\\": "Modules/"
        }
    },


3、提示:不要忘记之后运行 composer dump-autoload。如图2
提示:不要忘记之后运行 composer dump-autoload

图2



PS E:\wwwroot\laravel6-modules-demo> composer dump-autoload
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Discovered Package: nwidart/laravel-modules
Package manifest generated successfully.
Generated optimized autoload files containing 4360 classes


4、为指定模块生成给定的控制台命令。


PS E:\wwwroot\laravel6-modules-demo> php artisan module:make-command CreatePostCommand Blog
Created : E:/wwwroot/laravel6-modules-demo/Modules/Blog/Console/CreatePostCommand.php


5、编辑 CreatePostCommand.php,主要修改 $name 属性与 handle 方法
<pre class="wp-block-syntaxhighlighter-code">

<?php

namespace Modules\Blog\Console;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;

class CreatePostCommand extends Command
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'blog:create-post';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        file_put_contents(storage_path() . '/logs/Modules-Blog-Console-CreatePostCommand-handle-' . microtime(true) . '-' . mt_rand()  . '.txt', print_r([], true), FILE_APPEND | LOCK_EX);
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['example', InputArgument::REQUIRED, 'An example argument.'],
        ];
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
            ['example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null],
        ];
    }
}


</pre>
6、注册命令,使用服务提供者类中可用的名为 commands 的 laravel 方法注册该命令。


    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->register(RouteServiceProvider::class);
        $this->commands([
            \Modules\Blog\Console\CreatePostCommand::class,
        ]);
    }


7、要查看所有可用的 Artisan 命令的列表,可以使用 list 命令:确认 blog:create-post 已存在。如图3
要查看所有可用的 Artisan 命令的列表,可以使用 list 命令:确认 blog:create-post 已存在

图3

8、执行:php artisan blog:create-post 时报错:Not enough arguments (missing: “example”).


PS E:\wwwroot\laravel6-modules-demo> php artisan blog:create-post


  Not enough arguments (missing: "example").




9、编辑 CreatePostCommand.php ,删除掉方法 getArguments 10、再次执行:php artisan blog:create-post,不再报错。查看执行结果,有输出日志文件:Modules-Blog-Console-CreatePostCommand-handle-1672371310.181-805184791.txt 。如图4
再次执行:php artisan blog:create-post,不再报错。查看执行结果,有输出日志文件:Modules-Blog-Console-CreatePostCommand-handle-1672371310.181-805184791.txt

图4

11、执行:php artisan schedule:run,响应:没有计划的命令准备好运行。


PS E:\wwwroot\laravel6-modules-demo> php artisan schedule:run
No scheduled commands are ready to run.


12、参考任务调度 – Artisan 命令调度,在 App\Console\Kernel 类的 schedule 方法中定义新的调度任务


    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // $schedule->command('inspire')
        //          ->hourly();
        $schedule->command('blog:create-post')->everyMinute(); // 每分钟执行一次任务
    }


13、执行 schedule:run 命令 ,执行成功,且成功生成对应的日志文件。如图5
执行 schedule:run 命令 ,执行成功,且成功生成对应的日志文件

图5



PS E:\wwwroot\laravel6-modules-demo> php artisan schedule:run
Running scheduled command: "C:\php-7.4.27\php.exe" "artisan" blog:create-post > "NUL" 2>&1


14、但是,既然 command blog:create-post 是在模块中编写的,那么调度此 command 的 schedule 也应该在对应的模块中编写才是更为合适的实现。 15、参考:https://stackoverflow.com/questions/30456737/how-to-schedule-artisan-commands-in-a-package ,两种方案皆是可行的。还原第 12 步骤的实现。


    /**
     * Boot the application events.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerTranslations();
        $this->registerConfig();
        $this->registerViews();
        $this->registerFactories();
        $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
        /*
        $this->app->booted(function () {
            $schedule = $this->app->make(Schedule::class);
            $schedule->command('blog:create-post')->everyMinute();
        });
        */
        // 在 Laravel 6.10 及以上版本中:
        $this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
            $schedule->command('blog:create-post')->everyMinute();
        });
    }




PS E:\wwwroot\laravel6-modules-demo> php artisan schedule:run
Running scheduled command: "C:\php-7.4.27\php.exe" "artisan" blog:create-post > "NUL" 2>&1
PS E:\wwwroot\laravel6-modules-demo> php artisan schedule:run
Running scheduled command: "C:\php-7.4.27\php.exe" "artisan" blog:create-post > "NUL" 2>&1


]]>
https://www.shuijingwanwq.com/2023/01/07/7330/feed/ 0
Uncaught SyntaxError: Cannot use import statement outside a module (at statement.js:1:1) https://www.shuijingwanwq.com/2022/12/30/7317/ https://www.shuijingwanwq.com/2022/12/30/7317/#respond Fri, 30 Dec 2022 13:17:18 +0000 https://www.shuijingwanwq.com/?p=7317 Post Views: 117

1、Uncaught SyntaxError: Cannot use import statement outside a module (at statement.js:1:1)。参考:重构:改善既有代码的设计 (第2版)。如图1

Uncaught SyntaxError: Cannot use import statement outside a module (at statement.js:1:1)。参考:重构:改善既有代码的设计 (第2版)

图1


Uncaught SyntaxError: Cannot use import statement outside a module (at statement.js:1:1)
statement.html:37 Uncaught ReferenceError: statement is not defined
    at statement.html:37:13


2、代码实现如下,如图2

代码实现如下

图2

statement.html

<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="utf-8">
    <title>重构,第一个示例</title>
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%20%20%20%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<style>" title="<style>" />
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22statement.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%3E%0A%20%20%20%20%20%20%20%20let%20invoice%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22customer%22%3A%20%22BigCo%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22performances%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22playID%22%3A%20%22hamlet%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22audience%22%3A%2055%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22playID%22%3A%20%22as-like%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22audience%22%3A%2035%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22playID%22%3A%20%22othello%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22audience%22%3A%2040%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20let%20plays%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22hamlet%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20%22Hamlet%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22tragedy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22as-like%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20%22As%20You%20Like%20It%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22comedy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22othello%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20%22Othello%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22tragedy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20console.log(statement(invoice%2C%20plays))%3B%0A%20%20%20%20%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
</head>
<body>

</body>
</html>

statement.js


import createStatementData from './createStatementData.js';

function renderPlainText(data, plays) {
    let result = `Statement for ${data.customer}\n`;
    for (let perf of data.performances) {
        result += ` ${perf.play.name}: ${usd(perf.amount)} (${perf.audience} seats)\n`;
    }
    result += `Amount owed is ${usd(data.totalAmount)}\n`;
    result += `You earned ${data.totalVolumeCredits} credits\n`;
    return result;
}

function usd(aNumber) {
    return new Intl.NumberFormat("en-US",
        {
            style: "currency", currency: "USD",
            minimumFractionDigits: 2
        }).format(aNumber / 100);
}

function statement(invoice, plays) {
    return renderPlainText(createStatementData(invoice, plays));
}


createStatementData.js


export default function createStatementData(invoice, plays) {
    const result = {};
    result.customer = invoice.customer;
    result.performances = invoice.performances.map(enrichPerformance);
    result.totalAmount = totalAmount(result);
    result.totalVolumeCredits = totalVolumeCredits(result);
    return result;

    function enrichPerformance(aPerformance) {
        const result = Object.assign({}, aPerformance);
        result.play = playFor(result);
        result.amount = amountFor(result);
        result.volumeCredits = volumeCreditsFor(result);
        return result;
    }

    function playFor(aPerformance) {
        return plays[aPerformance.playID];
    }

    function amountFor(aPerformance) {
        let result = 0;
        switch (aPerformance.play.type) {
            case "tragedy":
                result = 40000;
                if (aPerformance.audience > 30) {
                    result += 1000 * (aPerformance.audience - 30);
                }
                break;
            case "comedy":
                result = 30000;
                if (aPerformance.audience > 20) {
                    result += 10000 + 500 * (aPerformance.audience - 20);
                }
                result += 300 * aPerformance.audience;
                break;
            default:
                throw new Error(`unknown type: ${aPerformance.play.type}`);
        }
        return result;
    }

    function volumeCreditsFor(aPerformance) {
        let result = 0;
        result += Math.max(aPerformance.audience - 30, 0);
        if ("comedy" === aPerformance.play.type) result += Math.floor(aPerformance.audience / 5);
        return result;
    }

    function totalAmount(data) {
        return data.performances
            .reduce((total, p) => total + p.amount, 0);
    }

    function totalVolumeCredits(data) {
        return data.performances
            .reduce((total, p) => total + p.volumeCredits, 0);
    }
}


3、参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules 。JavaScript 模块。需要把 type=”module” 放到 script 标签中,来声明这个脚本是一个模块。

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22module%22%20src%3D%22statement.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" /> 

4、刷新页面:file:///E:/wwwroot/refactoring/1/statement.html。报错:Access to script at ‘file:///E:/wwwroot/refactoring/1/statement.js’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.。如图3

刷新页面:file:///E:/wwwroot/refactoring/1/statement.html。报错:Access to script at 'file:///E:/wwwroot/refactoring/1/statement.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.

图3


Uncaught ReferenceError: statement is not defined
    at statement.html:43:17
(匿名) @ statement.html:43
statement.html:1 Access to script at 'file:///E:/wwwroot/refactoring/1/statement.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
statement.html:8          GET file:///E:/wwwroot/refactoring/1/statement.js net::ERR_FAILED


5、需要注意本地测试 — 如果你通过本地加载 HTML 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为 JavaScript 模块安全性需要。你需要通过一个服务器来测试。

6、打开:http://localhost/refactoring/1/statement.html,仅剩下报错:Uncaught ReferenceError: statement is not defined。但是已经有正常的输出结果。如图4

打开:http://localhost/refactoring/1/statement.html,仅剩下报错:Uncaught ReferenceError: statement is not defined。但是已经有正常的输出结果

图4


Uncaught ReferenceError: statement is not defined
    at statement.html:43:17
(匿名) @ statement.html:43



7、将 html 中的 js 代码剪切至 statement.js 中,实现如下。不再报错。如图5

将 html 中的 js 代码剪切至 statement.js 中,实现如下。不再报错

图5

statement.html

<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="utf-8">
    <title>重构,第一个示例</title>
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%20%20%20%20%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<style>" title="<style>" />
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22module%22%20src%3D%22statement.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
</head>
<body>
 
</body>
</html>

statement.js


import createStatementData from './createStatementData.js';

function renderPlainText(data, plays) {
    let result = `Statement for ${data.customer}\n`;
    for (let perf of data.performances) {
        result += ` ${perf.play.name}: ${usd(perf.amount)} (${perf.audience} seats)\n`;
    }
    result += `Amount owed is ${usd(data.totalAmount)}\n`;
    result += `You earned ${data.totalVolumeCredits} credits\n`;
    return result;
}

function usd(aNumber) {
    return new Intl.NumberFormat("en-US",
        {
            style: "currency", currency: "USD",
            minimumFractionDigits: 2
        }).format(aNumber / 100);
}

function statement(invoice, plays) {
    return renderPlainText(createStatementData(invoice, plays));
}

let invoice = {
    "customer": "BigCo",
    "performances": [
        {
            "playID": "hamlet",
            "audience": 55
        },
        {
            "playID": "as-like",
            "audience": 35
        },
        {
            "playID": "othello",
            "audience": 40
        }
    ]
};

let plays = {
    "hamlet": {
        "name": "Hamlet",
        "type": "tragedy"
    },
    "as-like": {
        "name": "As You Like It",
        "type": "comedy"
    },
    "othello": {
        "name": "Othello",
        "type": "tragedy"
    }
};

console.log(statement(invoice, plays));


]]>
https://www.shuijingwanwq.com/2022/12/30/7317/feed/ 0
在 Laravel 6、Module 中的本地化文件从 php 转换为 json (由于 Module 不支持,决定放弃) https://www.shuijingwanwq.com/2022/08/05/6852/ https://www.shuijingwanwq.com/2022/08/05/6852/#respond Fri, 05 Aug 2022 01:14:46 +0000 https://www.shuijingwanwq.com/?p=6852 Post Views: 78

1、现在翻译字符串都存放在 resources/lang 目录下的文件里。在此目录中,但凡应用支持的每种语言都应该有一个对应的子目录:


/resources
    /lang
        /en
            validation.php
        /en-US
            validation.php


2、之前的调用方法:return trans(‘online_store_theme_graphql::validation.custom.theme_editor_session.theme_editor_session_id_exists_rule’);

3、计划翻译文件以 JSON 格式存储在 resources/lang 目录中


/resources
    /lang
        /en
            validation.php
        /en-US
            validation.php
		en.json
		en-US.json


3、特意将 /en/validation.php 与 en.json 进行区分,以确认优先级的顺序。确认 .php 的优先级高于 .json。如图1

特意将 /en/validation.php 与 en.json 进行区分,以确认优先级的顺序。确认 .php 的优先级高于 .json

图1

<?php
 
return [
    'custom' => [
        'theme_editor_session' => [
            'theme_editor_session_id_exists_rule' => 'The selected :attribute is invalid.',
        ],
    ],
 
    'attributes' => [],
];

{
  "validation.custom.theme_editor_session.theme_editor_session_id_exists_rule": "The selected :attribute is invalid1."
}


4、删除掉 php 相关的目录与文件


/resources
    /lang
		en.json
		en-US.json


5、发现翻译未正常工作。如图2

发现翻译未正常工作

图2

6、使用 App Facade 的 getLocale 和 isLocale 方法确定当前的区域设置


        echo App::getLocale();
        exit;



{
  "error": {},
  "text": "en"
}


7、参考 How to use json translation files using modules? (Translation Strings As Keys):https://github.com/nWidart/laravel-modules/issues/954 。调整服务提供者中的 registerTranslations(),删除掉对于 PHP 文件的引用,采用对于 JSON 文件的引用。


    /**
     * Register translations.
     *
     * @return void
     */
    public function registerTranslations()
    {
        $langPath = resource_path('lang/modules/' . $this->moduleNameLower);

        if (is_dir($langPath)) {
            $this->loadTranslationsFrom($langPath, $this->moduleNameLower);
            $this->loadJsonTranslationsFrom($langPath);
        } else {
            $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', $this->moduleNameLower);
            $this->loadJsonTranslationsFrom(__DIR__ . '/../Resources/lang');
        }
    }



    /**
     * Register translations.
     *
     * @return void
     */
    public function registerTranslations()
    {
        $langPath = resource_path('lang/modules/' . $this->moduleNameLower);

        if (is_dir($langPath)) {
            $this->loadJsonTranslationsFrom($langPath);
        } else {
            $this->loadJsonTranslationsFrom(__DIR__ . '/../Resources/lang');
        }
    }


8、翻译仍然不能够正常工作。最终决定暂且放弃此实现。

]]>
https://www.shuijingwanwq.com/2022/08/05/6852/feed/ 0
在 Laravel 6、Module 中,自定义验证规则的 message 中翻译不可用的排查(延迟提供者的使用导致) https://www.shuijingwanwq.com/2022/08/04/6845/ https://www.shuijingwanwq.com/2022/08/04/6845/#respond Thu, 04 Aug 2022 01:12:48 +0000 https://www.shuijingwanwq.com/?p=6845 Post Views: 73 1、参考:https://www.shuijingwanwq.com/2022/04/26/6341/ 2、在一段时间后,突然发现自定义验证规则的 message 中翻译不可用。验证失败时响应的 message 未被翻译。如图1
在一段时间后,突然发现自定义验证规则的 message 中翻译不可用。验证失败时响应的 message 未被翻译

图1



{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorSessionDelete].",
      "extensions": {
        "validation": {
          "sessionId": [
            "online_store_theme_graphql::validation.custom.theme_editor_session.theme_editor_session_id_exists_rule"
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 3,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorSessionDelete"
      ],
      "trace": ...
    }
  ],
  "data": {
    "onlineStoreThemeEditorSessionDelete": null
  }
}


3、参考:https://www.shuijingwanwq.com/2022/06/29/6707/ ,当删除 implements DeferrableProvider 后,再清空目录:/bootstrap/cache 后,翻译正常工作。如图2
参考:https://www.shuijingwanwq.com/2022/06/29/6707/ ,当删除 implements DeferrableProvider 后,再清空目录:/bootstrap/cache 后,翻译正常工作

图2



{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorSessionDelete].",
      "extensions": {
        "validation": {
          "sessionId": [
            "The selected session id is invalid."
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 3,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorSessionDelete"
      ],
      "trace": ...
    }
  ],
  "data": {
    "onlineStoreThemeEditorSessionDelete": null
  }
}


4、在模块的服务提供者中的现有实现如下


class GraphQlResolverServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * @return void
     */
    public function register()
    {
        $this->registerTranslations();

        // ...
    }
}


5、参考:/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php ,代码调整如下


class GraphQlResolverServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * @return void
     */
    public function register()
    {
        $this->registerLoader();

        $this->app->singleton('translator', function ($app) {
            $loader = $app['translation.loader'];

            // When registering the translator component, we'll need to set the default
            // locale as well as the fallback locale. So, we'll grab the application
            // configuration so we can easily get both of these values from there.
            $locale = $app['config']['app.locale'];

            $trans = new Translator($loader, $locale);

            $trans->setFallback($app['config']['app.fallback_locale']);

            return $trans;
        });

        $this->registerTranslations();

        // ...
    }

    /**
     * Register the translation line loader.
     *
     * @return void
     */
    protected function registerLoader()
    {
        $this->app->singleton('translation.loader', function ($app) {
            return new FileLoader($app['files'], $app['path.lang']);
        });
    }

    /**
     * Register translations.
     *
     * @return void
     */
    public function registerTranslations()
    {
        $langPath = resource_path('lang/modules/' . $this->moduleNameLower);

        if (is_dir($langPath)) {
            $this->loadTranslationsFrom($langPath, $this->moduleNameLower);
        } else {
            $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', $this->moduleNameLower);
        }
    }

    /**
     * 获取由提供者提供的服务。
     *
     * @return array
     */
    public function provides()
    {
        return ['translator', 'translation.loader'];
    }
}


6、翻译正常运行。但是这样的话,感觉代码冗余得厉害。决定再次调整一下,将延迟加载的服务单独剥离出一个类。


class OnlineStoreThemeGraphQLServiceProvider extends ServiceProvider
{
    private $moduleNameLower = 'online_store_theme_graphql';

    /**
     * @return void
     */
    public function register()
    {
        $this->app->register(ResolverServiceProvider::class);

        $this->registerTranslations();
    }

    /**
     * Register translations.
     *
     * @return void
     */
    public function registerTranslations()
    {
        $langPath = resource_path('lang/modules/' . $this->moduleNameLower);

        if (is_dir($langPath)) {
            $this->loadTranslationsFrom($langPath, $this->moduleNameLower);
        } else {
            $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', $this->moduleNameLower);
        }
    }

    /**
     * 获取由提供者提供的服务。
     *
     * @return array
     */
    public function provides()
    {
        return [];
    }
}




class ResolverServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(SettingPersistenceInterface::class, function($app){
            return new DbSettingPersister();
        });

        $this->app->singleton(ThemeAssetRepositoryInterface::class, function($app){
            return new ThemeAssetRepository();
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [SettingPersistenceInterface::class, ThemeAssetRepositoryInterface::class];
    }
}


7、翻译正常工作。如图3
翻译正常工作

图3

]]>
https://www.shuijingwanwq.com/2022/08/04/6845/feed/ 0
在 Laravel 6 中发布模块的数据库迁移文件 https://www.shuijingwanwq.com/2022/06/09/6587/ https://www.shuijingwanwq.com/2022/06/09/6587/#respond Thu, 09 Jun 2022 01:07:45 +0000 https://www.shuijingwanwq.com/?p=6587 Post Views: 102 1、在 Docker 容器的启动脚本中,如果要强制迁移命令在没有提示的情况下运行,请使用 –force 参数


php artisan migrate --force


2、现阶段存在基于 LARAVEL MODULES 开发的模块,其中存在迁移文件,例:/Modules/ThemeStoreDB/Database/Migrations 。如图1
现阶段存在基于 LARAVEL MODULES 开发的模块,其中存在迁移文件,例:/Modules/ThemeStoreDB/Database/Migrations

图1

3、在本地开发环境中,执行模块下的迁移文件,一般是运行命令:php artisan module:migrate ThemeStoreDB 。如图2
在本地开发环境中,执行模块下的迁移文件,一般是运行命令:php artisan module:migrate ThemeStoreDB

图2



PS E:\wwwroot\object> php artisan module:migrate ThemeStoreDB
Migrating: 2021_12_06_133918_create_theme_asset_table
Migrated:  2021_12_06_133918_create_theme_asset_table (0.11 seconds)
Migrating: 2021_12_13_145303_create_theme_asset_version_table
Migrated:  2021_12_13_145303_create_theme_asset_version_table (0.1 seconds)
Migrating: 2022_05_16_105847_create_theme_installation_table
Migrated:  2022_05_16_105847_create_theme_installation_table (0.07 seconds)
Migrating: 2022_05_16_134353_create_theme_installation_version_preset_table
Migrated:  2022_05_16_134353_create_theme_installation_version_preset_table (0.21 seconds)
Migrating: 2022_05_16_172815_create_theme_installation_task_table
Migrated:  2022_05_16_172815_create_theme_installation_task_table (0.12 seconds)


4、但是,模块数量过多,也不可能频繁地在启动脚本中增加对应模块的迁移命令。参考:module:publish-migration,发布模块的数据库迁移文件。如图3
但是,模块数量过多,也不可能频繁地在启动脚本中增加对应模块的迁移命令。参考:module:publish-migration,发布模块的数据库迁移文件。

图3



PS E:\wwwroot\object> php artisan module:publish-migration ThemeStoreDB


5、目录:/Modules/ThemeStoreDB/Database/Migrations 下的文件已经复制至目录:/database/migrations。如图4
目录:/Modules/ThemeStoreDB/Database/Migrations 下的文件已经复制至目录:/database/migrations

图4

6、这样的话,在启动脚本中的执行迁移的命令就仅需要:php artisan migrate –force,而无需要任何变更。在本地环境中执行,无迁移可执行,符合预期。


PS E:\wwwroot\object> php artisan migrate
Nothing to migrate.
PS E:\wwwroot\object>


 ]]>
https://www.shuijingwanwq.com/2022/06/09/6587/feed/ 0
在 Laravel 6 的模块中使用队列 https://www.shuijingwanwq.com/2022/05/30/6468/ https://www.shuijingwanwq.com/2022/05/30/6468/#respond Mon, 30 May 2022 01:23:05 +0000 https://www.shuijingwanwq.com/?p=6468 Post Views: 76

1、使用以下 Artisan 命令来为指定的模块生成一个新的队列任务。如图1

使用以下 Artisan 命令来为指定的模块生成一个新的队列任务

图1


PS E:\wwwroot\object> php artisan module:make-job ThemeInstallation ThemeStoreDB
Created : E:/wwwroot/object/Modules/ThemeStoreDB/Jobs/ThemeInstallation.php
PS E:\wwwroot\object>


2、任务分发,推送任务到队列


use Modules\ThemeStoreDB\Jobs\ThemeInstallation as ThemeInstallationJob;

// 推送任务到队列
ThemeInstallationJob::dispatch($themeInstallationTask);


3、编辑 /Modules/ThemeStoreDB/Jobs/ThemeInstallation.php 。队列中的处理示例,主要是将表 theme_installation_task 的字段 step 的值修改为 2。

<?php
 
namespace Modules\ThemeStoreDB\Jobs;
 
use Modules\ThemeStoreDB\Entities\ThemeInstallationTask;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
 
class ThemeInstallation implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    protected $themeInstallationTask;
 
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(ThemeInstallationTask $themeInstallationTask)
    {
        // 队列任务构造器中接收了 Eloquent 模型,将会只序列化模型的 ID
        $this->themeInstallationTask = $themeInstallationTask;
    }
 
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $this->themeInstallationTask->step = ThemeInstallationTask::STEP_DOWNLOAD_ZIP;
        $this->themeInstallationTask->save();
    }
}
 
 

4、在命令行启动队列系统,队列在启动完成后会进入监听状态:


PS E:\wwwroot\object> php artisan queue:listen


5、调用 API ,任务分发至队列后,可以在命令行中看到监听的状态。如图2

调用 API ,任务分发至队列后,可以在命令行中看到监听的状态

图2


PS E:\wwwroot\object> php artisan queue:listen
[2022-05-18 14:12:39][0brUqtQG1q1J8njW3uyUnrXE16ubcCXs] Processing: Modules\ThemeStoreDB\Jobs\ThemeInstallation
[2022-05-18 14:12:39][0brUqtQG1q1J8njW3uyUnrXE16ubcCXs] Processed:  Modules\ThemeStoreDB\Jobs\ThemeInstallation



6、查看数据表 theme_installation_task 的字段 step 的值已经修改为 2。且 created_at 与 updated_at 分别为:2022-05-18 14:12:37、2022-05-18 14:12:39。修改时间较之创建时间晚了 2 秒钟。符合预期。如图3

查看数据表 theme_installation_task 的字段 step 的值已经修改为 2。且 created_at 与 updated_at 分别为:2022-05-18 14:12:37、2022-05-18 14:12:39。修改时间较之创建时间晚了 2 秒钟。符合预期

图3

7、当 handle 方法抛出异常时,队列任务执行失败,日志信息插入至表 failed_jobs 中。期望将异常的 message 同步插入至表 theme_installation_task 的字段 message 中。如图4

当 handle 方法抛出异常时,队列任务执行失败,日志信息插入至表 failed_jobs 中。期望将异常的 message 同步插入至表 theme_installation_task 的字段 message 中

图4


PS E:\wwwroot\object> php artisan queue:listen
[2022-05-18 15:49:45][XFUmrUWBn2muST8AXmRXzjVLUr72UUde] Processing: Modules\ThemeStoreDB\Jobs\ThemeInstallation
[2022-05-18 15:49:45][XFUmrUWBn2muST8AXmRXzjVLUr72UUde] Failed:     Modules\ThemeStoreDB\Jobs\ThemeInstallation



8、在任务类中定义 failed 方法。导致任务失败的 Exception 将被传递给 failed 方法。

<?php
 
namespace Modules\ThemeStoreDB\Jobs;
 
use Modules\ThemeStoreDB\Entities\ThemeInstallationTask;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
 
class ThemeInstallation implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    /**
     * 任务可以尝试的最大次数。
     *
     * @var int
     */
    public $tries = 1;
 
    /**
     * 任务可以执行的最大秒数 (超时时间)。
     *
     * @var int
     */
    public $timeout = 600;
 
    protected $themeInstallationTask;
 
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(ThemeInstallationTask $themeInstallationTask)
    {
        // 队列任务构造器中接收了 Eloquent 模型,将会只序列化模型的 ID
        $this->themeInstallationTask = $themeInstallationTask;
    }
 
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        checkhere();
        $this->themeInstallationTask->step = ThemeInstallationTask::STEP_DOWNLOAD_ZIP;
        $this->themeInstallationTask->save();
    }
 
    /**
     * 任务失败的处理过程
     *
     * @param  Exception  $exception
     * @return void
     */
    public function failed(Exception $exception)
    {
        $this->themeInstallationTask->message = $exception->getMessage();
        $this->themeInstallationTask->save();
    }
}
 
 

9、异常的 message 同步插入至表 theme_installation_task 的字段 message 中。符合预期。如图5

异常的 message 同步插入至表 theme_installation_task 的字段 message 中。符合预期

图5

]]>
https://www.shuijingwanwq.com/2022/05/30/6468/feed/ 0
Illuminate\Contracts\Container\BindingResolutionException : Target [Modules\ThemeStore\View\ViewStorageInterface] is not instantiable while building [Modules\ThemeStore\Resolver\OnlineStoreThemeResolver]. https://www.shuijingwanwq.com/2022/05/13/6399/ https://www.shuijingwanwq.com/2022/05/13/6399/#respond Fri, 13 May 2022 01:29:58 +0000 https://www.shuijingwanwq.com/?p=6399 Post Views: 104 1、Lighthouse 大量使用 SDL 并利用模式指令。为了改善您的编辑体验,您可以使用 artisan 命令生成定义文件。报错:Illuminate\Contracts\Container\BindingResolutionException : Target [Modules\ThemeStore\View\ViewStorageInterface] is not instantiable while building [Modules\ThemeStore\Resolver\OnlineStoreThemeResolver].如图1
Lighthouse 大量使用 SDL 并利用模式指令。为了改善您的编辑体验,您可以使用 artisan 命令生成定义文件。报错:Illuminate\Contracts\Container\BindingResolutionException  : Target [Modules\ThemeStore\View\ViewStorageInterface] is not instantiable while building [Modules\ThemeStore\Resolver\OnlineStoreThemeResolver].

图1



PS E:\wwwroot\object> php artisan lighthouse:ide-helper
Wrote schema directive definitions to E:\wwwroot\object/schema-directives.graphql.

   Illuminate\Contracts\Container\BindingResolutionException  : Target [Modules\ThemeStore\View\ViewStorageInterface] is not instantiable while building [Modules\ThemeStore\Resolver\OnlineStoreThemeResolver].

  at E:\wwwroot\object\vendor\laravel\framework\src\Illuminate\Container\Container.php:978
    974|         } else {
    975|             $message = "Target [$concrete] is not instantiable.";
    976|         }
    977|
  > 978|         throw new BindingResolutionException($message);
    979|     }
    980|
    981|     /**
    982|      * Throw an exception for an unresolvable primitive.

  Exception trace:


2、执行命令:php artisan module:list,显示所有模块的列表。确认模块 ThemeStore 已禁用。如图2
执行命令:php artisan module:list,显示所有模块的列表。确认模块 ThemeStore 已禁用

图2



PS E:\wwwroot\object> php artisan module:list
+-----------------------+----------+-------+---------------------------------------------------------+
| Name                  | Status   | Order | Path                                                    |
+-----------------------+----------+-------+---------------------------------------------------------+
| ThemeStore            | Disabled | 0     | E:\wwwroot\object\Modules/ThemeStore            |


3、启用模块 ThemeStore 后,仍然报同样的错误


PS E:\wwwroot\object> php artisan module:enable ThemeStore
Module [ThemeStore] enabled successful.
PS E:\wwwroot\object> php artisan lighthouse:ide-helper
Wrote schema directive definitions to E:\wwwroot\object/schema-directives.graphql.

   Illuminate\Contracts\Container\BindingResolutionException  : Target [Modules\ThemeStore\View\ViewStorageInterface] is not instantiable while building [Modules\ThemeStore\Resolver\OnlineStoreThemeResolver].

  at E:\wwwroot\object\vendor\laravel\framework\src\Illuminate\Container\Container.php:978
    974|         } else {
    975|             $message = "Target [$concrete] is not instantiable.";
    976|         }
    977|
  > 978|         throw new BindingResolutionException($message);
    979|     }
    980|
    981|     /**
    982|      * Throw an exception for an unresolvable primitive.

  Exception trace:


4、最后发现原因在于 config/app.php 的 providers 中 Modules\ThemeStore\Providers\ThemeStoreServiceProvider::class 已经被注释所导致。缺少服务提供者的类。取消注释,不再报错。


PS E:\wwwroot\object> php artisan lighthouse:ide-helper
Wrote schema directive definitions to E:\wwwroot\object/schema-directives.graphql.
Wrote definitions for programmatically registered types to E:\wwwroot\object/programmatic-types.graphql.
Wrote PHP definitions to E:\wwwroot\object/_lighthouse_ide_helper.php.

It is recommended to add them to your .gitignore file.


 ]]>
https://www.shuijingwanwq.com/2022/05/13/6399/feed/ 0
在 Laravel 6、LightHouse 5、Module 中使用 @rules 指令时,应用 exists 规则时的本地化实现 https://www.shuijingwanwq.com/2022/04/29/6354/ https://www.shuijingwanwq.com/2022/04/29/6354/#respond Fri, 29 Apr 2022 01:12:38 +0000 https://www.shuijingwanwq.com/?p=6354 Post Views: 109 1、当语言区域为 en 时,当验证的参数不存在时,响应:The selected theme id is invalid.。如图1
当语言区域为 en 时,当验证的参数不存在时,响应:The selected theme id is invalid.

图1



{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorSessionCreate].",
      "extensions": {
        "validation": {
          "themeId": [
            "The selected theme id is invalid."
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorSessionCreate"
      ],
      "trace": ...
    }
  ],
  "data": {
    "onlineStoreThemeEditorSessionCreate": null
  }
}


2、当语言区域为 zh_CN 时,当验证的参数不存在时,响应:所选的theme id无效。预期为:所选的主题ID无效。如图2
当语言区域为 zh_CN 时,当验证的参数不存在时,响应:所选的theme id无效。预期为:所选的主题ID无效。

图2



{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorSessionCreate].",
      "extensions": {
        "validation": {
          "themeId": [
            "所选的theme id无效。"
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorSessionCreate"
      ],
      "trace": ...
    }
  ],
  "data": {
    "onlineStoreThemeEditorSessionCreate": null
  }
}


3、查看 GraphQL 代码实现,/Modules/ThemeStore/Resources/graphql/theme_editor.graphql


extend type Mutation {
    "创建主题编辑会话"
    onlineStoreThemeEditorSessionCreate(themeId: ID! @rules(apply: ["exists:theme_asset,theme_id"])): ThemeEditorSessionCreatePayload @field(resolver: "Modules\\ThemeStore\\Resolver\\ThemeEditor\\CreateThemeEditorSessionResolver")
}

type ThemeEditorSessionCreatePayload
{
    "会话"
    session: Session!
}




4、查看 语言文件 ,/Modules/ThemeStore/Resources/lang/zh_CN/validation.php,已经指定了 theme_id 的自定义属性名称,但是,并未生效
<pre class="wp-block-syntaxhighlighter-code">

<?php

return [

    'attributes' => [
        'theme_id' => '主题ID',
    ],
];


</pre>
5、即使将此配置放至语言文件 /resources/lang/zh_CN/validation.php 中,仍然未生效,响应:所选的theme_id无效。但是,其默认是使用的此语言文件的配置。将验证规则消息放在任何语言的 validation.php 中名为 attributes 的键中,验证消息的 :attribute 部分不会被翻译(模块上下文)。
<pre class="wp-block-syntaxhighlighter-code">

<?php

return [

	'exists' => '所选的:attribute无效。',

    'attributes' => [
        'theme_id' => '主题ID',
    ],
];


</pre>
6、在 Laravel v8.7.90、nwidart/laravel-modules 8.2.0 中,编辑 /resources/lang/zh_CN/validation.php ,attributes 会生效。


    /**
     * 显示创建博客文章的表单
     *
     * @return Response
     */
    public function create(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'theme_id' => 'required|exists:users,id',
        ]);

        if ($validator->fails()) {
            dd($validator->errors());
        }

        // 保存博客文章…

        return view('blog::post.create');
    }


<pre class="wp-block-syntaxhighlighter-code">

<?php
return [
    'exists' => '所选的:attribute无效。resources',

    'attributes' => [
        'theme_id' => '主题ID',
    ],
];


</pre>


Illuminate\Support\MessageBag {#361 ▼
  #messages: array:1 [▼
    "theme_id" => array:1 [▼
      0 => "所选的主题ID无效。resources"
    ]
  ]
  #format: ":message"
}


7、决定在当前版本:Laravel v6.20.43、nwidart/laravel-modules 7.3.0 中,模拟步骤6的实现。以确定是否与框架、模块包的版本有关,进而导致在应用的根目录中,attributes 不生效的问题。确定是无关的。那么原因在于 LightHouse 5。
<pre class="wp-block-syntaxhighlighter-code">

<?php
return [
    'array' => ':attribute必须是一个数组。',

    'attributes' => [
        'filter' => '过滤器'
    ],
];


</pre>


{
  "message": "The given data was invalid.",
  "errors": {
    "filter": [
      "过滤器必须是一个数组。"
    ]
  },
  "status_code": 422,
  "debug": {
    "line": 385,
    "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Validation\\Validator.php",
    "class": "Illuminate\\Validation\\ValidationException",
    "trace": ...
  }
}


8、参考:https://github.com/nuwave/lighthouse/issues/1773 ,虽然已经在 @rules 指令上实现了翻译功能,但是实现得不够彻底。即验证属性未支持。开发者建议:https://lighthouse-php.com/master/security/validation.html#validator-classes 。支持通过任意 PHP 代码定义规则。 9、暂时不准备编写一个自定义规则。决定在非英语环境下,属性名未翻译为对应的语言,暂且接受。且语言文件在根目录下,不在对应的模块目录下。  ]]>
https://www.shuijingwanwq.com/2022/04/29/6354/feed/ 0
在 Laravel 6、LightHouse 5、Module 中,自定义验证规则的使用 https://www.shuijingwanwq.com/2022/04/26/6341/ https://www.shuijingwanwq.com/2022/04/26/6341/#respond Tue, 26 Apr 2022 01:21:13 +0000 https://www.shuijingwanwq.com/?p=6341 Post Views: 96 1、现阶段需要在 GraphQL API 中实现一个删除缓存标识的接口。参考 Shopify 的示例。当缓存标识不存在时,响应失败。如图1
现阶段需要在 GraphQL API 中实现一个删除缓存标识的接口。参考 Shopify 的示例。当缓存标识不存在时,响应失败

图1



mutation DeleteThemeSessionId($sessionId: String!) {
  onlineStoreEditorSessionDelete(sessionId: $sessionId) {
    deletedSessionId
    userErrors {
      field
      message
      __typename
    }
    __typename
  }
}





{
  "sessionId": "H9Mc8FwH5Eu5LR37XAzsehh4"
}




{
  "data": {
    "onlineStoreEditorSessionDelete": {
      "deletedSessionId": null,
      "userErrors": [
        {
          "field": [
            "sessionId"
          ],
          "message": "在线商店编辑器访问不存在",
          "__typename": "UserError"
        }
      ],
      "__typename": "OnlineStoreEditorSessionDeletePayload"
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 990,
        "restoreRate": 50
      }
    }
  }
}


2、由于验证此缓存标识是否存在的规则,会用于大量的 API 中。最张决定自定义验证规则,以复用此验证规则。 3、由于是在模块中编写验证规则。module:make-rule Create a new validation rule for the specified module.
<pre class="wp-block-syntaxhighlighter-code">

PS E:\wwwroot\object> php artisan help module:make-rule
Description:
  Create a new validation rule for the specified module.

Usage:
  module:make-rule <name> [<module>]

Arguments:
  name                  The name of the rule class.
  module                The name of module will be used.

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
PS E:\wwwroot\object> php artisan module:make-rule ThemeEditor/ThemeEditorCodeExistsRule ThemeStore
Created : E:/wwwroot/object/Modules/ThemeStore/Rules/ThemeEditor/ThemeEditorCodeExistsRule.php
PS E:\wwwroot\object>

</pre>
4、编辑规则类,判断缓存标识是否存在。/Modules/ThemeStore/Rules/ThemeEditor/ThemeEditorCodeExistsRule.php
<pre class="wp-block-syntaxhighlighter-code">

<?php

namespace Modules\ThemeStore\Rules\ThemeEditor;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Cache;
use Modules\ThemeStore\Resolver\ThemeEditor\ThemeEditorResolver;

class ThemeEditorCodeExistsRule implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return Cache::tags([ThemeEditorResolver::TAG_THEME_EDITOR, ThemeEditorResolver::TAG_THEME_EDITOR_CODE])->has($value);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The validation error message.';
    }
}


</pre>
5、编辑 /ThemeStore/Resources/graphql/theme_editor.graphql 。使用 @rules 指令,通过完全限定的类名引用自定义验证规则:ThemeEditorCodeExistsRule


extend type Mutation {
    ...
    "删除主题编辑标识"
    onlineStoreThemeEditorCodeDelete(themeEditorCode: String! @rules(apply: ["Modules\\ThemeStore\\Rules\\ThemeEditor\\ThemeEditorCodeExistsRule"])): ThemeEditorCodeDeletePayload @field(resolver: "Modules\\ThemeStore\\Resolver\\ThemeEditor\\DeleteThemeEditorCodeResolver")
}

type ThemeEditorCodeDeletePayload
{
    deletedThemeEditorCode: String
}



6、新生成了一个缓存标识:vRYbn7NQiEWMbH2G6sXB8AE4aoBIk4JQOw2p,先测试验证失败的情况。


mutation {
  onlineStoreThemeEditorCodeDelete(themeEditorCode: "vRYbn7NQiEWMbH2G6sXB8AE4aoBIk4JQOw2p0") {
    deletedThemeEditorCode
  }
}





{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorCodeDelete].",
      "extensions": {
        "validation": {
          "themeEditorCode": [
            "The validation error message."
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorCodeDelete"
      ],
      "trace": [
		...
	  ]
    }
  ],
  "data": {
    "onlineStoreThemeEditorCodeDelete": null
  }
}


7、新建模块下的 /Modules/ThemeStore/Resources/lang/en/validation.php 语言文件。
<pre class="wp-block-syntaxhighlighter-code">

<?php

return [
    'custom' => [
        'theme_editor_code' => [
            'theme_editor_code_exists_rule' => 'The selected :attribute is invalid.',
        ],
    ],

    'attributes' => [],
];


</pre>
8、编辑规则类,/Modules/ThemeStore/Rules/ThemeEditor/ThemeEditorCodeExistsRule.php。从翻译文件中返回一个错误消息,从 message 方法中调用辅助函数 trans。
<pre class="wp-block-syntaxhighlighter-code">

<?php

namespace Modules\ThemeStore\Rules\ThemeEditor;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Cache;
use Modules\ThemeStore\Resolver\ThemeEditor\ThemeEditorResolver;

class ThemeEditorCodeExistsRule implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return Cache::tags([ThemeEditorResolver::TAG_THEME_EDITOR, ThemeEditorResolver::TAG_THEME_EDITOR_CODE])->has($value);
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return trans('theme_store::validation.custom.theme_editor_code.theme_editor_code_exists_rule');
    }
}


</pre>
9、再次测试验证失败的情况。The validation error message. 已经被替换为:The selected theme editor code is invalid.。如图2
再次测试验证失败的情况。The validation error message. 已经被替换为:The selected theme editor code is invalid.

图2



mutation {
  onlineStoreThemeEditorCodeDelete(themeEditorCode: "vRYbn7NQiEWMbH2G6sXB8AE4aoBIk4JQOw2p0") {
    deletedThemeEditorCode
  }
}





{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorCodeDelete].",
      "extensions": {
        "validation": {
          "themeEditorCode": [
            "The selected theme editor code is invalid."
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorCodeDelete"
      ],
      "trace": [
		...
	  ]
    }
  ],
  "data": {
    "onlineStoreThemeEditorCodeDelete": null
  }
}


10、新建模块下的 /Modules/ThemeStore/Resources/lang/zh_CN/validation.php 语言文件。
<pre class="wp-block-syntaxhighlighter-code">

<?php

return [
    'custom' => [
        'theme_editor_code' => [
            'theme_editor_code_exists_rule' => '所选的主题编辑标识无效。',
        ],
    ],

    'attributes' => [],
];


</pre>
11、当前语言区域被设置为:zh_CN 时,测试失败的响应,响应信息为中文。符合预期。如图3
当前语言区域被设置为:zh_CN 时,测试失败的响应,响应信息为中文。符合预期

图3



{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorCodeDelete].",
      "extensions": {
        "validation": {
          "themeEditorCode": [
            "所选的主题编辑标识无效。"
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorCodeDelete"
      ],
      "trace": [
		...
	  ]
    }
  ],
  "data": {
    "onlineStoreThemeEditorCodeDelete": null
  }
}


12、可将 /Modules/ThemeStore/Resources/lang/en/validation.php 复制为 /Modules/ThemeStore/Resources/lang/en_US/validation.php。当语言区域设置为 en_US 时,/en_US/validation.php 的优先级高于 /en/validation.php。如果在 /en_US/validation.php 中查找不到,则会从 /en/validation.php 查找。测试失败的响应。
<pre class="wp-block-syntaxhighlighter-code">

<?php

return [
    'custom' => [],

    'attributes' => [],
];


</pre>


{
  "errors": [
    {
      "message": "Validation failed for the field [onlineStoreThemeEditorCodeDelete].",
      "extensions": {
        "validation": {
          "themeEditorCode": [
            "The selected theme editor code is invalid."
          ]
        },
        "category": "validation"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "onlineStoreThemeEditorCodeDelete"
      ],
      "trace": ...
    }
  ],
  "data": {
    "onlineStoreThemeEditorCodeDelete": null
  }
}


13、注:theme_store 的名称源于 /ThemeStore/Providers/ThemeStoreServiceProvider.php


private $moduleNameLower = 'theme_store';


14、测试验证通过的情况,符合预期。如图4
测试验证通过的情况,符合预期

图4



mutation {
  onlineStoreThemeEditorCodeDelete(themeEditorCode: "vRYbn7NQiEWMbH2G6sXB8AE4aoBIk4JQOw2p") {
    deletedThemeEditorCode
  }
}





{
  "data": {
    "onlineStoreThemeEditorCodeDelete": {
      "deletedThemeEditorCode": "vRYbn7NQiEWMbH2G6sXB8AE4aoBIk4JQOw2p"
    }
  }
}


 ]]>
https://www.shuijingwanwq.com/2022/04/26/6341/feed/ 0