Project in Laravel (9), and PHP (8.1).
I want to import an excel file and use maatwebsite/excel (3.1) package.
I can import a file, and save the file into the model, like this:
import class:
class BankTransfersHistoryImport implements ToModel, WithHeadingRow, WithValidation, WithBatchInserts
{
use Importable;
private $rows;
public function __construct()
{
$this->rows = collect();
}
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
$bankTransferHistory = new BankTransfersHistory([
'loanId' => $row['loanId'],
'actionDate' => transformDate($row['actionDate']),
'worth' => $row['worth'],
.
.
]);
$this->rows->push($bankTransferHistory);
return $bankTransferHistory;
}
/**
* Returns Imported Data
*
* #return \Illuminate\Support\Collection
*/
public function getImportedData(): \Illuminate\Support\Collection
{
return $this->rows;
}
public function headingRow(): int
{
return 2;
}
public function rules(): array
{
return [
'*.loanId' => ['required', 'numeric'],
... some roles ...
];
}
}
controller:
public function store(Request $request)
{
$request->validate([
'file' => 'required|mimes:xls,xlsx',
]);
$file = $request->file('file');
$import = new BankTransfersHistoryImport;
try {
// date validation
$collection = $import->toCollection($file);
... some validation about the date ...
$import->import($file);
$getImportedData = import->getImportedData();
... check and update rows ...
return [
"message" => some message,
"data" => [
some data
],
];
} catch (\Maatwebsite\Excel\Validators\ValidationException$e) {
$failures = $e->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.
}
return $failures;
}
My question is:
If I can get the response of the file after saving the data, that will give me the data with the id of the row that was saved.
In some cases, I will have to update a row. That's why I would like to get the ID.
Now, in the check and update rows section, I update row by loanId + actionDate. I want it to be done by only ID.
something like this:
code:
$getImportedData = import->getImportedData();
data will be like:
[
{
"id": 1,
"loanId": 21001,
"actionDate": "2020-01-02T00:00:00.000000Z",
"worth": 2997.09,
"offerId": 1,
},
{
"id": 2,
"loanId": 21002,
"actionDate": "2020-01-02T00:00:00.000000Z",
"worth": 3000,
"offerId": 10,
},
]
The solution to my problem.
To save the information and get the ID of each saved row, I did a few things.
I changed my import class.
First I changed the create from ToModel to ToCollection
I deleted WithBatchInserts because this method does not work with ToCollection.
Next, I called the getImportedData function.
That's how I got all the rows I create in the DB with their ID.
This solved the problem for me to get the information saved with the ID of each line, and perform validation + update if necessary.
The code is below.
A small note:
I changed the word rows to data in the `getImportedData' function.
I save all the files in the system.
import class:
class BankTransfersHistoryImport implements ToCollection, WithHeadingRow, WithValidation
{
use Importable;
private $data;
public function __construct()
{
$this->data = collect();
}
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function collection(Collection $rows)
{
foreach ($rows as $row) {
$bankTransferHistory = BankTransfersHistory::create([
'loanId' => $row['loanId'],
'actionDate' => transformDate($row['actionDate']),
'worth' => $row['worth'],
.
.
]);
$this->data->push($bankTransferHistory);
}
}
/**
* Returns Imported Data
*
* #return \Illuminate\Support\Collection
*/
public function getImportedData(): \Illuminate\Support\Collection
{
return $this->data;
}
public function headingRow(): int
{
return 2;
}
public function rules(): array
{
return [
'*.loanId' => ['required', 'numeric'],
... some roles ...
];
}
}
store controller:
public function store(Request $request)
{
$request->validate([
'file' => 'required|mimes:xls,xlsx',
]);
$file = $request->file('file');
$import = new BankTransfersHistoryImport;
try {
// date validation
$collection = $import->toCollection($file);
... some validation about the date ...
// save the file in the system
$fileName = time() . '-' . $file->getClientOriginalName();
$file->storeAs('import bank transfers history', $fileName);
$import->import($file);
$importedData = $import->getImportedData(); // data after save in DB
... check and update rows ...
return [
"message" => some message,
"data" => [
some data
],
];
} catch (\Maatwebsite\Excel\Validators\ValidationException$e) {
$failures = $e->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.
}
return $failures;
}
}
Related
Project in Laravel (9), and PHP (8.1).
I want to import an excel file and use maatwebsite/excel (3.1) package.
I can import a file, and save the file into the model, like this:
import class:
class BankTransfersHistoryImport implements ToModel, WithHeadingRow, WithValidation
{
use Importable;
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new BankTransfersHistory([
'loanId' => $row['loanId'],
'actionDate' => transformDate($row['actionDate']),
'worth' => $row['worth'],
.
.
]);
}
public function headingRow(): int
{
return 2;
}
public function rules(): array
{
return [
'*.loanId' => ['required', 'numeric'],
... some roles ...
];
}
}
controller:
$import = new BankTransfersHistoryImport;
try {
// date validation
$collection = $import->toCollection($file);
... some validation about the date ...
$import->import($file);
... check and update rows ...
return [
"message" => some message,
"data" => [
some data
],
];
} catch (\Maatwebsite\Excel\Validators\ValidationException$e) {
$failures = $e->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.
}
return $failures;
}
My question is:
If I can get the response of the file after saving the data, and that will give me the data with the id of the row that was saved.
In some cases, I will have to update a row. That's why I would like to get the ID.
Now in the check and update rows section, I update by loanId + actionDate. I want it to be done by ID.
something like this:
code:
$data = $import->import($file);
data will be like:
[
{
"id": 1,
"loanId": 21001,
"actionDate": "2020-01-02T00:00:00.000000Z",
"worth": 2997.09,
"offerId": 1,
},
{
"id": 2,
"loanId": 21002,
"actionDate": "2020-01-02T00:00:00.000000Z",
"worth": 3000,
"offerId": 10,
},
]
You can create a function on import class which will return the imported data, adding a sample for your reference.
UsersImport.php
<?php
namespace App\Imports;
use App\Models\User;
use Maatwebsite\Excel\Concerns\ToModel;
class UsersImport implements ToModel
{
private $rows;
public function __construct() {
$this->rows = collect();
}
/**
* #param array $row
*
* #return User|null
*/
public function model(array $row)
{
$user = new User([
'name' => $row[0],
'email' => $row[1],
'password' => bcrypt(12345678),
]);
$this->rows->push($user);
return $user;
}
/**
* Returns Imported Data
*
* #return \Illuminate\Support\Collection
*/
public function getImportedData(): \Illuminate\Support\Collection
{
return $this->rows;
}
}
Your Import Function in Controller
public function import(UsersImport $usersImport)
{
Excel::import($usersImport, public_path('users.xlsx'));
$usersImport->getImportedData();
}
I have a problem to skip the row about importing excel laravel with the Maatwebsite / Laravel-Excel package.
I tried many ways from the internet but it still didn't work.
this is my controller code
if ($request->hasFile('file')) {
$import = new HsatuImport();
$file = $request->file('file'); //GET FILE
// config(['excel.import.startRow' => 2]);
Excel::import($import, $file)->limit(false, 2); //IMPORT FILE
return redirect()->route('admin.hsatus.upload')->withFlashSuccess('Upload Berhasil '.$import->getRowCount().' Data');
} else {
return redirect()->route('admin.hsatus.upload')->withFlashSuccess('Upload Gagal');
}
and this is my import code
<?php
namespace App\Imports;
use App\Models\Hsatu;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\ToModel;
class HsatuImport implements ToModel
{
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
private $rows = 0;
public function model(array $row)
{
++$this->rows;
return new Hsatu([
'region' => #$row[0],
'nomor_faktur' => #$row[1],
'nomor_rangka' => #$row[2],
'kode_mesin' => #$row[3]
]);
}
public function headingRow(): int
{
return 1;
}
public function getRowCount(): int
{
return $this->rows;
}
}
I had the problem with skipping when there's already existing Eloquent model. So below my code:
public function model(array $row)
{
$name = $row['name'];
if (!Category::where('name', $name)->exists())
return new Category([
'name' => $name
]);
}
I'm returning Model instance only if given statement is true.
When I run the code I get no error but the data I am trying to display is not displaying it's just blank.. can someone tell me what I'm doing wrong?
My controller:
public function openingPage($id) {
$this->getGames();
$games = $this->getGames();
return view('caseopener')->with('games',$games);
}
private function getGames() {
$games = array();
foreach ($this->data->items as $item) {
$game = new Game($item);
$games[] = array(
'id' => $game['id'],
'name' => $game['name'],
'price' => $game['price'],
'image' => $game['image'],
);
}
return $games;
}
The 'Game' Model that is used in 'getGames function':
class Game extends Model
{
private $id;
public $data;
public function __construct($id) {
parent::__construct();
$this->id = $id;
$this->data = $this->getData();
}
private function getData() {
$game = DB::table('products')->where('id', 1)->first();
if(empty($game)) return array();
return $game;
}
}
The view:
#foreach ($games as $game)
<div class="gold">$ {{ $game['price'] }}</div>
#endforeach
I think you are over-complicating things. You could simplify your flow like this:
Given your provided code, it seems like you are using a custom table name ('products') in your Game model. So we'll address this first:
Game.php
class Game extends Model
{
protected $table = 'products'; //
}
Now, it seems like you're searching an array of Game ids ($this->data->items). If so, you could make use of Eloquent for your query, specially the whereIn() method:
YourController.php
public function openingPage($id)
{
$games = Game::whereIn('id', $this->data->items)->get();
return view('caseopener')->with('games', $games);
}
Optionally, if you want to make sure of just returning the id, name, price and image of each Game/product, you could format the response with API Resources:
php artisan make:resource GameResource
Then in your newly created class:
app/Http/Resources/GameResource.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GameResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'price' => $this->price,
'image' => $this->image,
];
}
}
So now just update your controller:
YourController.php
use App\Http\Resources\GameResource;
public function openingPage($id)
{
$games = Game::whereIn('id', $this->data->items)->get();
return view('caseopener')->with('games', GameResource::collection($games));
} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I've been trying for countless hours now, but still having issues updating a models relationship, the closest I've got to is a 'Method fill does not exist.' error.
Listing model:
class Listing extends Model
{
protected $fillable = [
'uid', 'start_date',...........
];
public function locations()
{
return $this->hasMany('App\ListingLocation');
}
}
Location (relationship to listing - hasMany):
class ListingLocation extends Model
{
protected $fillable = [
'listing_id', 'location',
];
public function listing()
{
return $this->belongsTo('App\Listing');
}
}
This returns my model and relationship, which I can view with dd($listing)
$listing = Listing::with('locations')->findOrFail($id);
This will update my listing model, which I can see the changes after calling dd($listing) again
$listing->fill($array);
However when I attempt to fill the relationship as per below, I get 'Method fill does not exist.'
$listing->locations->fill($array['locations']);
How can I update the relationship successfully before calling $listing->push();?
Change your location to a single record, not a collection
For example:
$listings->locations->first()->fill($array['locations']);
to fill every record use foreach
#foreach($listings->locations as $location)
$location->fill(do_something);
#endforeach
I ended up creating a new class to extend hasMany which allowed me to use sync as per alexweissman at https://laracasts.com/discuss/channels/general-discussion/syncing-one-to-many-relationships.
Extract from forum:
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* #link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/HasMany.php
*/
class HasManySyncable extends HasMany
{
public function sync($data, $deleting = true)
{
$changes = [
'created' => [], 'deleted' => [], 'updated' => [],
];
$relatedKeyName = $this->related->getKeyName();
// First we need to attach any of the associated models that are not currently
// in the child entity table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->newQuery()->pluck(
$relatedKeyName
)->all();
// Separate the submitted data into "update" and "new"
$updateRows = [];
$newRows = [];
foreach ($data as $row) {
// We determine "updateable" rows as those whose $relatedKeyName (usually 'id') is set, not empty, and
// match a related row in the database.
if (isset($row[$relatedKeyName]) && !empty($row[$relatedKeyName]) && in_array($row[$relatedKeyName], $current)) {
$id = $row[$relatedKeyName];
$updateRows[$id] = $row;
} else {
$newRows[] = $row;
}
}
// Next, we'll determine the rows in the database that aren't in the "update" list.
// These rows will be scheduled for deletion. Again, we determine based on the relatedKeyName (typically 'id').
$updateIds = array_keys($updateRows);
$deleteIds = [];
foreach ($current as $currentId) {
if (!in_array($currentId, $updateIds)) {
$deleteIds[] = $currentId;
}
}
// Delete any non-matching rows
if ($deleting && count($deleteIds) > 0) {
$this->getRelated()->destroy($deleteIds);
$changes['deleted'] = $this->castKeys($deleteIds);
}
// Update the updatable rows
foreach ($updateRows as $id => $row) {
$this->getRelated()->where($relatedKeyName, $id)
->update($row);
}
$changes['updated'] = $this->castKeys($updateIds);
// Insert the new rows
$newIds = [];
foreach ($newRows as $row) {
$newModel = $this->create($row);
$newIds[] = $newModel->$relatedKeyName;
}
$changes['created'][] = $this->castKeys($newIds);
return $changes;
}
/**
* Cast the given keys to integers if they are numeric and string otherwise.
*
* #param array $keys
* #return array
*/
protected function castKeys(array $keys)
{
return (array) array_map(function ($v) {
return $this->castKey($v);
}, $keys);
}
/**
* Cast the given key to an integer if it is numeric.
*
* #param mixed $key
* #return mixed
*/
protected function castKey($key)
{
return is_numeric($key) ? (int) $key : (string) $key;
}
}
You can then override Eloquent's hasMany method in your model class:
/**
* Overrides the default Eloquent hasMany relationship to return a HasManySyncable.
*
* {#inheritDoc}
*/
public function hasMany($related, $foreignKey = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
$foreignKey = $foreignKey ?: $this->getForeignKey();
$localKey = $localKey ?: $this->getKeyName();
return new HasManySyncable(
$instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
);
}
/**
* Get all of a user's phone numbers.
*/
public function phones()
{
return $this->hasMany('App\Phone');
}
A sync method will now be available to any hasMany relationships you have on this model:
$user->phones()->sync([
[
'id' => 21,
'label' => "primary",
'number' => "5555551212"
],
[
'id' => null,
'label' => "mobile",
'number' => "1112223333"
]
]);
I am trying to get a record from my MongoDB. To get the record I use the following code;
public function getEventsForAggregate($identifier)
{
$events = $this->MongoCollection()->findOne([
'_id' => $identifier
], [
'events',
]);
var_dump($events);
}
Unfortunately it dies on the findOne.
Do I change the code so the findOne is in a protected function it does work.
So the following code does work.
public function getEventsForAggregate($identifier)
{
$events = $this->getEvents($identifier);
var_dump($events);
}
protected function getEvents($identifier)
{
return $this->MongoCollection()->findOne([
'_id' => $identifier
], [
'events',
]);
}
Can some one explain how it is possible that the first code breaks, but the second one works?
Extra code;
/** #var MongoCollection */
protected $mongoCollection;
public function __construct(MongoCollection $MongoCollection)
{
$this->mongoCollection = $MongoCollection;
}
/**
* #return MongoCollection
*/
protected function MongoCollection()
{
return $this->mongoCollection;
}