excel sheet validation in laravel - laravel

I using laravel excel Maatwebsite.
public function collection(Collection $rows)
{
Validator::make($rows->toArray(), [
'*.price' => 'numeric',
])->validate();
}
I need output
Excel Row No not array number
Row No 1.price must be a number.

as I understand you need to validate uploaded excel rows.
the package excel Maatwebsite provided validation rules feature too.
in your import class you need to add WithValidation, WithHeadingRow interface and rules mothod. in this way uploaded excel will be validated before insert into database:
namespace App\Imports;
use App\User;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Validators\Failure;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\SkipsOnFailure;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\SkipsFailures;
class UsersImport implements ToModel, WithValidation, WithHeadingRow,SkipsOnFailure
{
use Importable,SkipsFailures;
public function model(array $row)
{
return new User([
'name' => $row['name'],
'email' => $row['email'],
'password' => 'secret',
]);
}
public function rules(): array
{
return [
'email' => Rule::in(['patrick#maatwebsite.nl']),
// Above is alias for as it always validates in batches
'*.email' => Rule::in(['patrick#maatwebsite.nl']),
];
}
}
and to gather errors:
$import = new UsersImport();
$import->import('users.xlsx');
$failures= $import->failures() ;
foreach ($failures as $failure) {
$failure->row(); // row that went wrong
$failure->attribute(); // either heading key (if using heading row concern) or column index
$failure->errors(); // Actual error messages from Laravel validator
$failure->values(); // The values of the row that has failed.
}
now $failures contains all validation error in all rows

This example use interface Validator that take different arguments to initialize; (array $data, array $rules, array $messages, array $customAttributes) to validate rows and referenced from web sources, do not implement ToModel concern and is similar to the example showed in the question.
namespace App\Imports;
use App\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Concerns\ToCollection;
class UsersImport implements ToCollection
{
public function collection(Collection $rows)
{
Validator::make($rows->toArray(), [
'*.0' => 'required',
])->validate();
foreach ($rows as $row) {
User::create([
'name' => $row[0],
]);
}
}
}
Another scenario like defined a custom data or Excel file like the following, suppose you want to access specific cells, you can implement the WithMappedCells concern.
index
user
date
subscription
0
user
2022-12-08
true
1
user
2022-12-08
true
2
user
2022-12-08
false
3
user
2022-12-08
true
use App\User;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithMappedCells;
class UsersImport implements WithMappedCells, ToModel
{
public function mapping(): array
{
return [
'index' => 'B1',
'user' => 'B2',
'date' => 'B3',
'subscription' => 'B4',
];
}
public function model(array $row)
{
return new User([
'index' => $row['index'],
'user' => $row['user'],
'date' => $row['date'],
'subscription' => $row['subscription']
]);
}
}

Related

I try to import with skip the line containing already existing email but it doesn't work

I'm having a problem when I try to import the data; in particular, I check if the email is already present; I'm use maatwebsite-excel
If it already exists I want to skip and move on. I tried to do this as the code below shows but once I load the data from an external sheet, where of course there are also clients with new emails, the data is not imported.
I don't get any errors but the data is not saved in the database.
ImportClass
namespace App\Imports;
use App\Models\Client;
use Maatwebsite\Excel\Row;
use Maatwebsite\Excel\Concerns\OnEachRow;
class ClientsImport implements OnEachRow
{
public function rules(): array
{
return [
'email' => 'required | unique:clients'
];
}
public function model(array $row)
{
return new Client([
'name' => $row[1],
'surname' => $row[2],
'email' => $row[3],
]);
}
}
Controller
public function importFile(Request $request)
{
Excel::import(new ClientsImport, $request->file('file')->store('temp'));
return back();
}
This is my code.

Eloquent eager loading specific columns

I have two models :Product and category
which are linked by a one-to-many relationship. A category has several products. I would like to select specific columns from each model.
Here is the query I have, but I have all the columns with category_id, but I want the category name instead of id. How can I do that. Thank you in advance.
here is the method in controller
$products = Product::with('categories:id,name')->get();
if ($products) {
$response = ['api_status' => 1, 'api_message' => 'success', 'data' => $products];
return response()->json($response);
} else {
$response = ['api_status' => 0, 'api_message' => 'Error'];
return response()->json($response);
}
Here is category model
class Categorie extends Model
{
use HasFactory, SoftDeletes;
protected $fillable =['name','redirect'];
public function products()
{
return $this->hasMany(product::class);
}
}
and the product model is:
class Product extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'description',
'detail', 'img',
'categorie_id', 'onSale',
'costPrice', 'inStock', 'salePrice'
];
public function categories()
{
return $this->belongsTo(Categorie::class);
}
}
here is the response:
To modify the output of your model I'd suggest using an API resource. This will give you more granular control about how a resource is returned by the API. A resource is also the best point to modify certain values.
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'detail' => $this->detail,
'img' => $this->img,
'category_id' => $this->categorie->name,
'category_name' => $this->categorie->name,
'onSale' => $this->onSale,
'costPrice' => $this->costPrice,
'inStock' => $this->inStock,
'salePrice' => $this->salePrice,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
'categories' => $this->categories ?? null,
];
}
}
This way you can manually specify which values your response should have.
In your controller you can include the populated array in your response by manually filling the toArray method with the current request object or just by using the resolve method which basically does the previous task for you:
$response = [
'api_status' => 1,
'api_message' => 'success',
'data' => ProductResource::collection($products)->resolve()
];
You can select particular fields from the relationship but you always need to select any keys involved in the relationship:
$products = Product::with('categories:id,name')->get();
Now each Product has its 'categories' loaded and those Category models only have the id and name fields.
Importantly:
The relationship categories is named incorrectly, it should be categorie in this case as the foreign key on Product is categorie_id and it is a singular relationship, it does not return multiple results.
Product::with('categorie:id,name')->get()
If you want to keep the name categories you would have to define the foreign key used when defining the belongsTorelationship, the second argument.
If you need to transform the structure of any of this that is a different thing and you will be walking into transformers or an API Resource.
Not sure how you want your data to look but this is the structure you will have by eager loading records, so if you need a different structure then what you get you will have to show an example.

How to convert object return by laravel model factory create method into array containing model fields?

For example, I have a UserFactory.php
<?php
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'role' => 'USER',
'password' => 'sasdcsdf34', // password
'remember_token' => Str::random(10),
];
});
Now, I can create a user as following
$user = factory(User::class)->create();
Now, How can I convert this $user object into array containing user info like name,email etc without initializing new array and manually assigning every $user object property. ??
I DON'T want to manually assign like following as it is tedious if there are many properties in $user object
$userArray=[
'id' => $user->id,
'name' => $user->name,
'email' => $user->email
]
I have tried this but it creates array containing various other properties and actual values needed are nested inside properties
$userArray=array($user)
You should try using the raw method of factory instead of create.
$user = factory(User::class)->raw();
This should give you an array you can work with.
Try to add something like this to your model class:
public function getArr(){
foreach($this->attributes as $key => $val){
$array[$key] = $val;
}
return $array;
}
If you wish to have this function in every model you could create trait with this function and then just attach it in model class or any class extending it.
You can use json_decode.
// Laravel 7
$userArray = json_decode(factory(User::class)->create(), true);
// Laravel 8
$userArray = json_decode(User::factory()->create(), true);
For Laravel 8, instead of make or create method, use:
User::factory()->raw();
This will return an array

Laravel Error: Object of class Torann\GeoIP\Location could not be converted to string

I am getting error on send Location Data To Database Using Laravel GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'),
Error:
Object of class Torann\GeoIP\Location could not be converted to string
This Is My Auth LoginController. How to Insert GeoIP Loacation data into database. Please Help me
If i remove this code 'current_location' => GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'), i am no longer getting this error, every data inserted into database but i add this code i am getting this error
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Jenssegers\Agent\Agent;
use Carbon\Carbon;
use App\User;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Closure;
use GeoIP;
use Location;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
function authenticated(Request $request, $user)
{
// Chrome, IE, Safari, Firefox, ...
$agent = new Agent();
$browser = $agent->browser();
// Ubuntu, Windows, OS X, ...
$platform = $agent->platform();
$user->update([
'last_signin' => Carbon::now()->toDateTimeString(),
'ip_address' => $request->getClientIp(),
'browser_login' => $agent->browser(),
'browser_version' => $agent->version($browser),
'device_login' => $agent->platform(),
'device_version' => $agent->version($platform),
'current_location' => GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31'),
'language' => $agent->languages(),
'root' => $agent->robot(),
'https' => $request->server('HTTP_USER_AGENT'),
]);
}
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest',['except'=>['logout', 'userLogout', 'profile']]);
}
public function userLogout()
{
Auth::guard('web')->logout();
return redirect('/');
}
}
Auth Route :
//User Auth Route Function
Auth::routes();
This is happending because GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31') returns an instance of Torann\GeoIP\Location and you are trying to save it as a String.
Checking the documentation of this object it has this shape:
\Torann\GeoIP\Location {
#attributes:array [
'ip' => '232.223.11.11',
'iso_code' => 'US',
'country' => 'United States',
'city' => 'New Haven',
'state' => 'CT',
'state_name' => 'Connecticut',
'postal_code' => '06510',
'lat' => 41.28,
'lon' => -72.88,
'timezone' => 'America/New_York',
'continent' => 'NA',
'currency' => 'USD',
'default' => false,
]
}
You have to choose a way to represent this location as a String, a possible way can be to save the latitude and the longitude separately.
If you need to use only one column at the DB, you can check some GeoHashing implementations skthon/geogash.
You Might be trying to use getLocation method from wrong instance.
1.) Try as below way :
"use Torann\GeoIP\GeoIPFacade as GeoIP"
$location = GeoIP::getLocation();
2.) Or try as Geoip package documentation suggest here (http://lyften.com/projects/laravel-geoip/doc/methods.html)
from this instance \Torann\GeoIP\GeoIP and then use geoip()->getLocation('27.974.399.65');
This seems to be an issue in the current_location field and how it is typed in your database. From what I read, I guess your field is defined a string, and when trying to save your record to the database, it fails since the data you're trying to save is an Location object.
I would recommend changing your current_location column in your database to make it a json type.
Then you'd be able to insert your data as:
$user->update([
'last_signin' => Carbon::now()->toDateTimeString(),
'ip_address' => $request->getClientIp(),
'browser_login' => $agent->browser(),
'browser_version' => $agent->version($browser),
'device_login' => $agent->platform(),
'device_version' => $agent->version($platform),
'current_location' => json_encode(GeoIP::getLocation('2405:204:970a:d9b3:10a3:5280:9064:3f31')->toArray()),
'language' => $agent->languages(),
'root' => $agent->robot(),
'https' => $request->server('HTTP_USER_AGENT'),
]);

Add custom values in drop down in laravel backpack

I am using laravel back and and I have two tables called bundles and study. I added a drop down fields in the form from bundleCrudController. But I just want to add only those values in the drop down list which studies are created by the logged in user not all the data from studies table.
Here is my code to add data in drop down list -
$this->crud->addField([
'name' => 'studies',
'label' => 'Studies',
'type' => 'select2_from_array',
'options' => $this->Study->getUnallocatedStudies($entryId),
'allows_null' => false,
'hint' => 'Search for the studies you would like to add to this bundle',
'tab' => 'Info',
'allows_multiple' => true
]);
$this->crud->addColumn([
'label' => 'Studies',
'type' => "select_multiple",
'name' => 'bundle_id',
'entity' => 'studies',
'attribute' => 'name',
'model' => "App\Models\Study",
]);
So pls help me to resolve the problem to add only those records in the dropdownlist created by the logged in user not all records.. Thanx
I think the best way would be to create an additional model, UserStudy, that:
extends Study;
has a global scope for filtering for what the current user can see;
It should look something like this:
<?php
namespace App\Models;
use App\Models\Study;
use Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class UserStudy extends Study
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
// filter out the studies that don't belong to this user
if (Auth::check()) {
$user = Auth::user();
static::addGlobalScope('user_id', function (Builder $builder) use ($user) {
$builder->where('user_id', $user->id);
});
}
}
}
You'll then be able to use this UserStudy model in your field definition, instead of Study. Just replace App\Models\Study with App\Models\UserStudy.
Hope it helps. Cheers!

Resources