Astrotomic / laravel-translatable

A Laravel package for multilingual models
https://docs.astrotomic.info/laravel-translatable/
MIT License
1.23k stars 156 forks source link

"Array to string conversion" only at create model in Seeder #240

Closed tsaiyihua closed 3 years ago

tsaiyihua commented 3 years ago

Describe the bug While use seeder to create model with translations, it always return error with "Array to string conversion", but the same code in tinker is ok.

To Reproduce config/translatable.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Application Locales
    |--------------------------------------------------------------------------
    |
    | Contains an array with the applications available locales.
    |
    */
    'locales' => [
        'zh' => [
            'Hant',
            'Hans',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Locale separator
    |--------------------------------------------------------------------------
    |
    | This is a string used to glue the language and the country when defining
    | the available locales. Example: if set to '-', then the locale for
    | colombian spanish will be saved as 'es-CO' into the database.
    |
    */
    'locale_separator' => '-',

    /*
    |--------------------------------------------------------------------------
    | Default locale
    |--------------------------------------------------------------------------
    |
    | As a default locale, Translatable takes the locale of Laravel's
    | translator. If for some reason you want to override this,
    | you can specify what default should be used here.
    | If you set a value here it will only use the current config value
    | and never fallback to the translator one.
    |
    */
    'locale' => null,

    /*
    |--------------------------------------------------------------------------
    | Use fallback
    |--------------------------------------------------------------------------
    |
    | Determine if fallback locales are returned by default or not. To add
    | more flexibility and configure this option per "translatable"
    | instance, this value will be overridden by the property
    | $useTranslationFallback when defined
    |
    */
    'use_fallback' => false,

    /*
    |--------------------------------------------------------------------------
    | Use fallback per property
    |--------------------------------------------------------------------------
    |
    | The property fallback feature will return the translated value of
    | the fallback locale if the property is empty for the selected
    | locale. Note that 'use_fallback' must be enabled.
    |
     */
    'use_property_fallback' => true,

    /*
    |--------------------------------------------------------------------------
    | Fallback Locale
    |--------------------------------------------------------------------------
    |
    | A fallback locale is the locale being used to return a translation
    | when the requested translation is not existing. To disable it
    | set it to false.
    | If set to null it will loop through all configured locales until
    | one existing is found or end of list reached. The locales are looped
    | from top to bottom and for country based locales the simple one
    | is used first. So "es" will be checked before "es_MX".
    |
    */
    'fallback_locale' => 'en',

    /*
    |--------------------------------------------------------------------------
    | Translation Model Namespace
    |--------------------------------------------------------------------------
    |
    | Defines the default 'Translation' class namespace. For example, if
    | you want to use App\Translations\CountryTranslation instead of App\CountryTranslation
    | set this to 'App\Translations'.
    |
    */
    'translation_model_namespace' => null,

    /*
    |--------------------------------------------------------------------------
    | Translation Suffix
    |--------------------------------------------------------------------------
    |
    | Defines the default 'Translation' class suffix. For example, if
    | you want to use CountryTrans instead of CountryTranslation
    | application, set this to 'Trans'.
    |
    */
    'translation_suffix' => 'Translation',

    /*
    |--------------------------------------------------------------------------
    | Locale key
    |--------------------------------------------------------------------------
    |
    | Defines the 'locale' field name, which is used by the
    | translation model.
    |
    */
    'locale_key' => 'locale',

    /*
    |--------------------------------------------------------------------------
    | Always load translations when converting to array
    |--------------------------------------------------------------------------
    | Setting this to false will have a performance improvement but will
    | not return the translations when using toArray(), unless the
    | translations relationship is already loaded.
    |
     */
    'to_array_always_loads_translations' => true,

    /*
    |--------------------------------------------------------------------------
    | Configure the default behavior of the rule factory
    |--------------------------------------------------------------------------
    | The default values used to control the behavior of the RuleFactory.
    | Here you can set your own default format and delimiters for
    | your whole app.
     *
     */
    'rule_factory' => [
        'format' => \Astrotomic\Translatable\Validation\RuleFactory::FORMAT_ARRAY,
        'prefix' => '%',
        'suffix' => '%',
    ],
];
  1. Create Table/Model with translation
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMyTestsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('my_tests', function (Blueprint $table) {
            $table->id();
            $table->boolean('display')->default(true);
            $table->timestamps();
        });
        Schema::create('my_tests_translations', function (Blueprint $table) {
            $table->increments('id');
            $table->foreignId('my_test_id')->references('id')->on('my_tests')->onDelete('cascade');
            $table->string('name', 255)->comment('name');
            $table->string('locale', 255)->index()->comment('locale');
            $table->unique(['my_test_id', 'locale'], 'my_test_id_locale_unique');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('my_tests_translations');
        Schema::dropIfExists('my_tests');
    }
}
  1. make factory for the Model

Model - MyTest.php

<?php

namespace App\Models;

use App\Traits\TranslateTrait;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class MyTest extends Model
{
    use HasFactory;
    use TranslateTrait;

    public $translatedAttributes = ['name'];

    public $translationModel = MyTestTranslation::class;

    protected $fillable = ['display'];
}

Model - MyTestTranslations.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class MyTestTranslation extends Model
{
    protected $fillable = [
        'name',
    ];

    public $timestamps = false;

    public function myTest()
    {
        return $this->belongsTo(MyTest::class);
    }
}
  1. make factory and seeder for the Model

factory - MyTestFactory.php

<?php

namespace Database\Factories;

use App\Models\MyTest;
use Illuminate\Database\Eloquent\Factories\Factory;

class MyTestFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = MyTest::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'display' => $this->faker->boolean,
            'zh-Hant' => [
                'name' => $this->faker->name
            ],
            'zh-Hans' => [
                'name' => $this->faker->name
            ],
        ];
    }
}

seeder - MyTestSeeder.php

<?php

namespace Database\Seeders;

use App\Models\MyTest;
use Illuminate\Database\Seeder;

class MyTestSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        MyTest::factory()->create();
    }
}
  1. run "php artisan d:s --class={ModelSeeder}
    $ php artisan d:s --class=MyTestSeeder

Expected behavior create data successfully

Screenshots none

Versions (please complete the following information)

Additional context This bug only happened in seeder,

Exception The thrown exception message.


   ErrorException 

  Array to string conversion

  at vendor/laravel/framework/src/Illuminate/Support/Str.php:560
    556▕ 
    557▕         $result = array_shift($segments);
    558▕ 
    559▕         foreach ($segments as $segment) {
  ➜ 560▕             $result .= (array_shift($replace) ?? $search).$segment;
    561▕         }
    562▕ 
    563▕         return $result;
    564▕     }

      +17 vendor frames 
  18  database/seeders/MyTestSeeder.php:17
      Illuminate\Database\Eloquent\Factories\Factory::create()

      +22 vendor frames 
  41  artisan:37
      Illuminate\Foundation\Console\Kernel::handle()

Stack Trace

[2021-08-19 16:58:36] local.ERROR: Array to string conversion {"exception":"[object] (ErrorException(code: 0): Array to string conversion at /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Support/Str.php:560)
[stacktrace]
#0 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Support/Str.php(560): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError()
#1 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/QueryException.php(57): Illuminate\\Support\\Str::replaceArray()
#2 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/QueryException.php(40): Illuminate\\Database\\QueryException->formatMessage()
#3 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Connection.php(693): Illuminate\\Database\\QueryException->__construct()
#4 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Connection.php(652): Illuminate\\Database\\Connection->runQueryCallback()
#5 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Connection.php(486): Illuminate\\Database\\Connection->run()
#6 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Connection.php(438): Illuminate\\Database\\Connection->statement()
#7 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php(32): Illuminate\\Database\\Connection->insert()
#8 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2964): Illuminate\\Database\\Query\\Processors\\Processor->processInsertGetId()
#9 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(1617): Illuminate\\Database\\Query\\Builder->insertGetId()
#10 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1123): Illuminate\\Database\\Eloquent\\Builder->__call()
#11 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1088): Illuminate\\Database\\Eloquent\\Model->insertAndSetId()
#12 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(929): Illuminate\\Database\\Eloquent\\Model->performInsert()
#13 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php(275): Illuminate\\Database\\Eloquent\\Model->save()
#14 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php(234): Illuminate\\Database\\Eloquent\\Factories\\Factory->Illuminate\\Database\\Eloquent\\Factories\\{closure}()
#15 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php(278): Illuminate\\Support\\Collection->each()
#16 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/Factory.php(236): Illuminate\\Database\\Eloquent\\Factories\\Factory->store()
#17 /data/tyh/projects/php8.0/api-v2.mirrorfiction/database/seeders/MyTestSeeder.php(17): Illuminate\\Database\\Eloquent\\Factories\\Factory->create()
#18 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Database\\Seeders\\MyTestSeeder->run()
#19 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#20 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
#21 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
#22 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/Container.php(651): Illuminate\\Container\\BoundMethod::call()
#23 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Seeder.php(149): Illuminate\\Container\\Container->call()
#24 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php(66): Illuminate\\Database\\Seeder->__invoke()
#25 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php(157): Illuminate\\Database\\Console\\Seeds\\SeedCommand->Illuminate\\Database\\Console\\Seeds\\{closure}()
#26 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php(67): Illuminate\\Database\\Eloquent\\Model::unguarded()
#27 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Illuminate\\Database\\Console\\Seeds\\SeedCommand->handle()
#28 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
#29 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure()
#30 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod()
#31 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Container/Container.php(651): Illuminate\\Container\\BoundMethod::call()
#32 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Console/Command.php(136): Illuminate\\Container\\Container->call()
#33 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/symfony/console/Command/Command.php(299): Illuminate\\Console\\Command->execute()
#34 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\\Component\\Console\\Command\\Command->run()
#35 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/symfony/console/Application.php(978): Illuminate\\Console\\Command->run()
#36 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/symfony/console/Application.php(295): Symfony\\Component\\Console\\Application->doRunCommand()
#37 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/symfony/console/Application.php(167): Symfony\\Component\\Console\\Application->doRun()
#38 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Console/Application.php(92): Symfony\\Component\\Console\\Application->run()
#39 /data/tyh/projects/php8.0/api-v2.mirrorfiction/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(129): Illuminate\\Console\\Application->run()
#40 /data/tyh/projects/php8.0/api-v2.mirrorfiction/artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle()
#41 {main}
"} 
tsaiyihua commented 3 years ago

my fault. sorry

olivermbs commented 2 years ago

my fault. sorry

Could you kindly share how you fixed this? I have exactly the same issue. Thanks!

tsaiyihua commented 2 years ago

Could you kindly share how you fixed this? I have exactly the same issue. Thanks!

In my Reproduce case.

I forget to implements Translatable in my model and use the wrong trait, the trait must be \Astrotomic\Translatable\Translatable;

The correct code for the model in this case, should be

<?php

namespace App\Models;

use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
use Astrotomic\Translatable\Translatable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class MyTest extends Model implements TranslatableContract
{
    use HasFactory;
    use Translatable;

    public $translatedAttributes = ['name'];

    public $translationModel = MyTestTranslation::class;

    protected $fillable = ['display'];
}