Laravel-scout : ElasticSearch with Translatable Entities (astrotomic/laravel-translatable) - laravel

I'm trying to use "babenkoivan/scout-elasticsearch-driver" with "astrotomic/laravel-translatable", but i don't understand how I could index the translated words.
My Model looks like :
namespace App\Models;
use Astrotomic\Translatable\Translatable;
use App\Models\Search\ShowIndexConfigurator;
use ScoutElastic\Searchable;
...
class Show extends BaseModel
{
...
use Translatable;
use Searchable;
protected $indexConfigurator = ShowIndexConfigurator::class;
protected $searchRules = [
//
];
protected $mapping = [
'properties' => [
// How to index localized translations ???
'title' => [
'type' => 'string'
],
]
];
....
public $translatedAttributes = [
...,
'title'
...
];
Best regards

I found a solution with the override of the method
public function toSearchableArray() with something like:
public function toSearchableArray(): array
{
$document = [];
if ($this->published) {
$document = [
//...
];
foreach ($this->translations()->get() as $translation)
{
if (!$translation->active) {
continue;
}
$locale = $translation->locale;
$document['title_' . $locale] = $translation->title;
$document['url_' . $locale] = $this->getLink($locale);
$document['sub_title_' . $locale] = $translation->sub_title;
$document['keywords_' . $locale] = "";
}
}
return $document;
}
The purpose of $mapping=[] is only to define the structure of data. Something like that is expected:
protected $mapping = [
'properties' => [
'title_en' => [
'type' => 'string'
],
'title_fr' => [
'type' => 'string'
],
...
]
];

Related

How to test singleton(a class only new once) on laravel

This is Residence Facade:
class Residence extends Facade
{
protected static function getFacadeAccessor()
{
return 'residence.manager';
}
}
Here is the provider:
class ResidenceServiceProvider extends ServiceProvider implements DeferrableProvider
{
public $singletons = [
'residence.manager' => ResidenceManager::class,
'residence.japan' => ResidenceJapan::class,
'residence.us' => ResidenceUS::class,
'residence.eu' => ResidenceEU::class,
];
public function provides()
{
return [
'residence.manager',
'residence.japan',
'residence.us',
'residence.eu',
];
}
The ResidenceManager Class:
class ResidenceManager
{
protected $app;
public function __construct()
{
$this->app = app();
}
public function make($data)
{
$residenceService = match ($data['location']) {
'japan' => $this->app['residence.japan'],
'us' => $this->app['residence.us'],
'eu' => $this->app['residence.eu'],
default => false
};
if (!$residenceService ) {
return false;
}
return $residenceService->make($data);
}
}
I try to test if ResidenceJapan::class, ResidenceUS::class, and ResidenceEU::class only new once.
I use spy, bu it give me
Mockery\Exception\InvalidCountException
Method make(<Any Arguments>) from Mockery_0_App_Services_Residence_ResidenceJapan should be called
exactly 1 times but called 3 times.
/**
* #dataProvider residenceSeedProvider
*/
public function test_residence_class_only_new_once($data, $expected)
{
$residence= match ($data['location']) {
'japan' =>
[
'bind' => 'residence.japan',
'class' => ResidenceJapan::class
],
'us' =>
[
'bind' => 'residence.us',
'class' => ResidenceUS::class
],
'eu' => [
'bind' => 'residence.eu',
'class' => ResidenceEU::class
],
default => 'unknown',
};
$spy=$this->spy($residence['class']);
$this->app->instance(
$residence['bind'],
$spy
);
$i = 3;
while ($i > 0) {
//$this->assertNotNull(Residence::make($data));
Residence::make($data);// don't know why the Residence::make($data) return null
$i--;
}
$spy->shouldHaveReceived('make')->once();
}
So I use mock, but it give me
Error
Call to undefined method App\Services\Residence\ResidenceJapan::shouldHaveReceived()
/**
* #dataProvider residenceSeedProvider
*/
public function test_residence_class_only_new_once($data, $expected)
{
$residence= match ($data['location']) {
'japan' =>
[
'bind' => 'residence.japan',
'class' => ResidenceJapan::class
],
'us' =>
[
'bind' => 'residence.us',
'class' => ResidenceUS::class
],
'eu' => [
'bind' => 'residence.eu',
'class' => ResidenceEU::class
],
default => 'unknown',
};
$mock = Mockery::mock($residence['class'], function (MockInterface $mock) use ($expected) {
$mock->shouldReceive('make')->once()->andReturn($expected);
});
$this->app->instance(
$residence['bind'],
$mock
);
$i = 3;
while ($i > 0) {
$this->assertNotNull(Residence::make($data)); // the Residence::make($data) return what I am expected
$i--;
}
$residence['class']::shouldHaveReceived('make')->once();
}
How do I test if ResidenceJapan::class, ResidenceUS::class, and ResidenceEU::class only new once, no matter how many times the Facade Residence::make($data) calls?

Laravel 8: Passing Factory properties into children relationships

we are currently working on a laravel 8 application. We are trying to create factories to create some dummy data for manual / developer based application testing.
The current code of my main Database-Seeder is below:
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call([
UserTableSeeder::class,
]);
\App\Models\User::factory(10)->create();
\App\Models\Activity::factory(5)->create();
/* 1. try
$tenFact = \App\Models\Tenant::factory(2)->has(
\App\Models\Project::factory(2)->state(
function (array $attributes, \App\Models\Tenant $tenant) {
return ['tenant_id' => $attributes['id']];
}
)->hasTasks(5)->hasLocation()
)->hasContracts(3)->create();
*/
/* Currently being used: */
\App\Models\Tenant::factory(10)->has(
\App\Models\Project::factory(5)->hasTasks(5)->hasLocation()
)->hasContracts(3)->create();
}
ProjectFactory.php:
class ProjectFactory extends Factory
{
protected $model = Project::class;
public function definition()
{
return [
'name' => 'Projekt: '. $this->faker->name,
'budget' => $this->faker->randomDigitNotNull*1000,
'progress' => $this->faker->randomDigitNotNull*10,
'budget_used' => $this->faker->randomDigitNotNull*50,
//'tenant_id' => Tenant::factory(),
'location_id' => Location::factory()->hasTenant(1),
];
}
}
LocationFactory.php:
class LocationFactory extends Factory
{
protected $model = Location::class;
public function definition()
{
return [
'name' => 'Standort: ' . $this->faker->company,
'street' => $this->faker->streetName,
'house_number' => $this->faker->buildingNumber,
'house_addition' => $this->faker->secondaryAddress,
'zip' => $this->faker->postcode,
'city' => $this->faker->city,
'tenant_id' => Tenant::factory(),
];
}
}
Our relationships look like this:
Tenant
|-- Project (has: tenant_id, but also has location_id)
| | -- Task (has: project_id)
|-- Locations (has: tenant_id)
|-- Contracts (has: tenant_id)
When creating datasets with the above named Tenant-Factory the following happens:
Tenant->id is being passed to Project(tenant_id)
but: Tenant->id is not being passend to Location (which depends on the tenants id but is also used for Project).
How can we pass the id of \App\Models\Tenant::factory(10) to Project::factory(5)->hasTasks(5)->hasLocation()?
Additionally we do have the problem, that even though we request 10 tenants, we will get around 60, because Location/Project create new objects when they should be using existing ones.
I gave up using the chained usage of the Tenant-Factory - I finally used some for-Loop that connected the related objects to each user by using laravels for() and state() methods:
for ($i=0; $i < 10 ; $i++) {
$tenant = \App\Models\Tenant::factory()->hasContracts(3)->create();
for ($j=0; $j < 5; $j++) {
$location = \App\Models\Location::factory(1)->for($tenant)->create();
$project = \App\Models\Project::factory(1)->state([
'location_id' => $location->first()['id'],
'tenant_id' => $tenant['id']])->hasTasks(5)->create();
}
}
class ProjectFactory extends Factory
{
$location_ids = App\Models\Location::pluck('id')->toArray();
protected $model = Project::class;
public function definition()
{
return [
'name' => 'Projekt: '. $this->faker->name,
'budget' => $this->faker->randomDigitNotNull*1000,
'progress' => $this->faker->randomDigitNotNull*10,
'budget_used' => $this->faker->randomDigitNotNull*50,
//'tenant_id' => Tenant::factory(),
'location_id'=> $faker->randomElement($location_ids),
];
}
}
class LocationFactory extends Factory
{
$tenant_ids = App\Models\Tenant::pluck('id')->toArray();
protected $model = Location::class;
public function definition()
{
return [
'name' => 'Standort: ' . $this->faker->company,
'street' => $this->faker->streetName,
'house_number' => $this->faker->buildingNumber,
'house_addition' => $this->faker->secondaryAddress,
'zip' => $this->faker->postcode,
'city' => $this->faker->city,
'tenant_id'=> $faker->randomElement($tenant_ids),
];
}
}

Laravel array key validation

I have custom request data:
{
"data": {
"checkThisKeyForExists": [
{
"value": "Array key Validation"
}
]
}
}
And this validation rules:
$rules = [
'data' => ['required','array'],
'data.*' => ['exists:table,id']
];
How I can validate array key using Laravel?
maybe it will helpful for you
$rules = ([
'name' => 'required|string', //your field
'children.*.name' => 'required|string', //your 1st nested field
'children.*.children.*.name => 'required|string' //your 2nd nested field
]);
The right way
This isn't possible in Laravel out of the box, but you can add a new validation rule to validate array keys:
php artisan make:rule KeysIn
The rule should look roughly like the following:
class KeysIn implements Rule
{
public function __construct(protected array $values)
{
}
public function message(): string
{
return ':attribute contains invalid fields';
}
public function passes($attribute, $value): bool
{
// Swap keys with their values in our field list, so we
// get ['foo' => 0, 'bar' => 1] instead of ['foo', 'bar']
$allowedKeys = array_flip($this->values);
// Compare the value's array *keys* with the flipped fields
$unknownKeys = array_diff_key($value, $allowedKeys);
// The validation only passes if there are no unknown keys
return count($unknownKeys) === 0;
}
}
You can use this rule like so:
$rules = [
'data' => ['required','array', new KeysIn(['foo', 'bar'])],
'data.*' => ['exists:table,id']
];
The quick way
If you only need to do this once, you can do it the quick-and-dirty way, too:
$rules = [
'data' => [
'required',
'array',
fn(attribute, $value, $fail) => count(array_diff_key($value, $array_flip([
'foo',
'bar'
]))) > 0 ? $fail("{$attribute} contains invalid fields") : null
],
'data.*' => ['exists:table,id']
];
I think this is what you are looking:
$rules = [
'data.checkThisKeyForExists.value' => ['exists:table,id']
];

How to post data in from back-end testcase to controller

I am new to php and I am currently trying to make a testcase for an add function I wrote for adding records in the table "project_point" of my database. In this testcase I want to post some test data to that add function and check if the data is set correctly.
Project Point Add function
public function addProjectPoint (Request $request) {
$point = new ProjectPoint();
$location = new Point($request->markerLat, $request->markerLong);
$point->project_id = $request->project_id;
$point->location = $location;
$point->area = $request->area;
$point->name = $request->name;
$point->information = $request->information;
$point->category = $request->category;
$point->save();
}
My test case
public function testCreateProjectPoint()
{
$this->post('admin/projectpoint/create', [
'project_id' => 1,
'markerLat' => 5.287020206451416,
'markerLong' => 51.68828138589033,
'area' => null,
'name' => 'TestCaseProjectPoint',
'information' => 'This is a automated test ProjectPoint, please delete this point!',
'category' => 'bezienswaardigheid'
]);
$this->assertDatabaseHas('interest_point', [
'project_id' => 1,
'location' => new Point(5.287020206451416, 51.68828138589033),
'area' => null,
'name' => 'TestCaseProjectPoint',
'information' => 'This is a automated test ProjectPoint, please delete this point!',
'category' => 'bezienswaardigheid'
]);
/*
$test = factory(ProjectPoint::class)->create();
$this->post('admin/projectpoint/create', $test);
$this->assertDatabaseHas('project_point', $test);
*/
}
ProjectPoint model
class ProjectPoint extends Model
{
use SpatialTrait;
protected $table = 'interest_point';
protected $fillable = ['project_id', 'name', 'information', 'category' ];
protected $spatialFields = [
'location',
'area'
];
public $timestamps = false;
public function project()
{
return $this->belongsTo('App\Models\Project', 'project_id');
}
}
The output of the test is:
Failed asserting that a row in the table [interest_point] matches the attributes {
"project_id": 1,
"location": {
"type": "Point",
"coordinates": [
51.68828138589033,
5.287020206451416
]
},
"area": null,
"name": "TestCaseProjectPoint",
"information": "This is a automated test ProjectPoint, please delete this point!",
"category": "bezienswaardigheid"
}.
But I expect to see the test case succeed and when checking the database no records have been added to the database
Have you tried still putting location and area in your fillables?

how to download PDF in laravel 5.4?

I'm trying to fetch data from database and pass this data to pdf view and download it.
I tried this code but its not working:
Download PDF
Route
Route::get('download-PDF/{id}', 'PDFController#pdf');
Controller
class PDFController extends Controller
{
public function pdf($id){
$getEvent=Event::find($id);
$eventId=$getEvent->id;
if(isset($eventId)) {
$eventData = Event::where('id', $eventId)->first();
$getDays = Day::where('event_id', $eventId)->get();
for ($i = 0; $i < count($getDays); $i++) {
$dayId = $getDays[$i]->id;
$schedule[$i] = DaySchedule::where('day_id', $dayId)->get();
}
}
$pdf=PDF::loadView('pdf',['eventData' => $eventData, 'schedule' => $schedule]);
return $pdf->download('event.pdf');
}
}
Config
'providers' => [
Barryvdh\DomPDF\ServiceProvider::class,
]
'aliases' => [
'PDF' => Barryvdh\DomPDF\Facade::class,
]
Are you getting any exception?
Here is a code I use for generating the pdfs:
Controller:
public function generateReport(TimetableRequest $request, $id) {
$reportData = $this->prepareReportData($id, $request->startDate, $request->endDate);
$pdf = App::make('dompdf.wrapper');
$pdf->loadView('pdf.report', $reportData);
return $pdf->download('report.pdf');
}
Config
'providers' => [
...
Barryvdh\DomPDF\ServiceProvider::class,
],
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
...
'PDF' => Barryvdh\DomPDF\Facade::class,
]
Route
Route::get('projects/{id}/report', 'Controller#generateReport');

Resources