I have upgraded the laravel excel library (Maatswebsite) from 2x to 3.1 (running Laravel 5.6/php 7.1) and trying to make my old data work (download exported file) and cannot work out how to pass my $data (which is an array from a foreach DB query (not eloquent) in controller) to the UsersExport.php class...
If I manually create a test collection (mirroring my $data array) in the class:
return collect([
[
'name' => 'F Name 1',
'surname' => 'Last Name 1',
'email' => 'Email 1'
'date_completed' => 'xx/xx/xx'
],
[
'name' => 'F Name 2',
'surname' => 'Last Name 2',
'email' => 'Email 2',
'date_completed' => 'xx/xx/xx'
]
]);
the above works perfect and the file is created and downloads when I run:
return Excel::download(new UsersExport, 'Test.xlsx');
But I want to pass my array ($data) from the controller to the class and not sure HOW I do this... I am trying to get something like this to work:
return Excel::download(new UsersExport($data), 'Test.xlsx');
From reading the specific posts I could find, I believe I need to create a constructor in the Class to accept my $data - but not sure how, and how to return that data if I succeed in my class accepting the data etc... Is the FromCollection the right option?
private $data;
public function __construct($data)
{
$this->data = $data;
}
Appreciate any assistance.... Thanks in advance.
Your approach is right. then use the collection() function to return that data.
private $data;
public function __construct($data)
{
$this->data = $data;
}
public function collection()
{
return $this->data;
}
if you want passing param data to class you use construct.
Example Controller:
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\CustomerinvoiceExport;
use App\Model\OrderInvoiceList;
use Illuminate\Http\Request;
class CustomerInvoiceController extends Controller
{
public function index(Request $request)
{
if ($request->has('start_date')) {
$start_date = $request->start_date;
} else {
$date_now = Carbon::now();
$start_date = $date_now->toDateString();
}
if ($request->has('end_date')) {
$end_date = $request->end_date;
} else {
$date_now = Carbon::now();
$end_date = $date_now->toDateString();
}
$customer_invs = OrderInvoiceList::customer_invoice($start_date, $end_date);
return Excel::download(new CustomerinvoiceExport($customer_invs), 'Customer_Invoice_Report.xlsx');
}
}
}
Class Export
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
class CustomerinvoiceExport implements FromCollection
{
protected $customer_invs;
/**
* Customer Invoice Report
*/
public function __construct($customer_invs)
{
$this->customer_invs = $customer_invs;
}
/**
* #return invoice_list
*/
public function collection(): array
{
$invoice_list = $this->invoice_list;
...........your logic here....
}
}
Related
While importing/uploading an excel file, if the data is already present in the excel file then update it in the Database or else insert it. This means before inserting should check with the database. So, anyone please help to solve with this issue:
This is the Import class for customers:
<?php
namespace App\Imports;
use App\Customer;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\Importable;
class ImportCustomers implements ToModel, WithHeadingRow, WithValidation
{
use Importable;
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
// Check mobile already exists
/* $count = Customer::where('mobile',$row['mobile'])->count();
dd($count);
if($count > 0){
return null;
} */
return new Customer([
'customer_name' => $row['customer_name'],
'mobile' => $row['mobile'],
'email' => $row['email']
]);
}
public function rules(): array
{
return [
'*.customer_name' => 'required',
'*.mobile' => 'required|unique:customers',
'*.email' => 'required',
];
}
}
/* This is Controller:*/
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\CustomerImportRequest;
use App\Imports\ImportCustomers;
use App\Exports\ExportCustomers;
use Maatwebsite\Excel\Facades\Excel;
use DB;
use App\Customer;
use Illuminate\Support\Arr;
class ImportExportExcelController extends Controller
{
protected $customers;
public function __construct(Customer $customers){
$this->customers = $customers;
}
public function index()
{
$customers = $this->customers->orderBy('id', 'desc')->get();
return view('ImportExportExcel', compact('customers'));
}
public function importExcel(CustomerImportRequest $request)
{
try {
if ($request->hasFile('import_file'))
{
$file = $request->file('import_file');
$columnRead = (new ImportCustomers)->toArray($file);
$customerCheck = $this->customers->where('mobile',$columnRead[0][1]["mobile"])->first(); //**here not getting result, rather shows null**
//dd($customerCheck);
if($customerCheck)
{
$customers = $customerCheck;
/*
**How to update if duplicates are found and display old values updated. How to achieve this?**
*/
}else{
$customers = new $this->customers;
Excel::import(new ImportCustomers, $file);
return redirect()->back()->with('success','Data imported successfully.');
}
}
} catch (\Maatwebsite\Excel\Validators\ValidationException $e) {
$failures = $e->failures();
//dd($failures);
return redirect()->back()->with('import_errors', $failures);
}
}
public function exportExcel()
{
$customers = Customer::select(["customer_name", "mobile", "email"])->get();
return Excel::download(new ExportCustomers($customers), 'customers.xlsx');
}
}
/This is the database migration schema:/
public function up()
{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('customer_name');
$table->string('mobile', 13)->unique();
$table->string('email')->nullable();
$table->timestamps();
});
}
Here "mobile" is unique, So if values like customer_name, and email are with modified values in an excel sheet with the same mobile no. then while importing, values should be updated.
excel sheet
I have used maatwebsite with Laravel 6
Controller :
Excel::import(new ImportCustomers(), $file);
then you could apply your logic at the Import class for customers:
public function model(array $row)
{
try {
$mobile = $row[1]; // referenced by row
$customer_name = $row[0];
$email = $row[1];
$customer = Customer::where('mobile', $mobile)->first();
//apply your logic
if (!$customer) { // you may not need if else, if no customer exists then create a new record and assign mobile
$customer = new Customer();
$customer->mobile = $mobile;
}
$customer->customer_name = $customer_name;
$customer->email = $email;
$customer->save();
return $customer;
} catch (\Exception $ex) {
dd($ex);
return;
}
}
Also please remove the rule about mobile, I think this should work
"*.mobile' => 'required',"
because your logic handles that mobile is unique.
//Check for the existing value in database and if result is found do this.
public function model(array $row)
{
// Check mobile already exists
$count = Customer::where('mobile',$row['mobile'])->first();
if($count){
return;
}
else{
return new Customer([
'customer_name' => $row['customer_name'],
'mobile' => $row['mobile'],
'email' => $row['email']
]);
}
}
I am using excel 3.1 in Laravel for importing excel files. I have included a validation for the name field. So whenever the name field is duplicating it will not import that row data to DB. everything works fine up to here. but the problem is I want to show success message in the controller. But the message is not showing and not showing any other error messages.
This is my import function
namespace App\Imports;
use App\Products;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithStartRow;
use Maatwebsite\Excel\Concerns\SkipsOnError;
use Maatwebsite\Excel\Concerns\SkipsErrors;
use Maatwebsite\Excel\Concerns\Importable;
use App\Categories;
use App\Brand;
use App\Unit;
use Maatwebsite\Excel\Concerns\WithValidation;
// use Illuminate\Validation\Rule;
// use Throwable;
class ExcelImport implements ToModel, WithStartRow, WithValidation, SkipsOnError
{
use Importable, SkipsErrors;
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
$name = $row[0];
$des = $row[1];
$product = new Products([
'name' => $row[0],
'description' => $row[1],
]);
return $product;
}
public function startRow(): int
{
return 2;
}
public function rules(): array{
return[
'0' => 'unique:products,name',
];
}
}
this is my controller
public function uploadfile(Request $request)
{
$this->validate($request, [
'file' => 'required',
]);
if($request->hasfile('file'))
{
foreach($request->file('file') as $file)
{
$import = new ExcelImport;
$import = $import->import($file);
}
//if the import is successful then I want to show some messages here
}
else{
return back()->with('error','File contains invalid data. Please upload a valid file.');
}
}
I am finding it hard to understand the examples from the docs to the scenario I am having. In my project I have an application form which filled up by the user then admin will update that form once the application is approved, canceled etc.
Now I want to notify the user that her/his application has been approved, canceled etc.
in my controller:
public function update(Request $request, $id)
{
$this->validate($request, [
'status' => 'required'
]);
$requestData = $request->all();
$loanapplication = LoanApplication::findOrFail($id);
$loanapplication->update([
"status" => $request->status,
"admin_notes" => $request->admin_notes,
"date_approval" => $request->date_approved
]);
if($request->notifyBorrower = 'on') {
$user_id = $loanapplication->user_id;
$status = $request->status;
$this->notify(new AdminResponseToApplication($user_id));
}
return redirect()->back()->with('flash_message', 'LoanApplication updated!');
}
In my AdminResponseToApplication.php I like to achieve this
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class AdminResponseToApplication extends Notification implements ShouldQueue
{
use Queueable;
public function __construct()
{
//
}
public function via($notifiable)
{
return ['mail','database'];
}
public function toMail($notifiable)
{
return (new MailMessage)
->line(.$user->nameHere. 'your application has been '.$statusHere.'.')
->action('check it out', url('/'))
->subject('Regarding with your loan application')
->line('This is system generated. Do not reply here.');
}
public function toDatabase($notifiable)
{
return [
'user_id' => $user->nameHere,
'status' => $statusHere,
'title' => .$user->nameHere. 'your application has been '.$statusHere.'.',
'url' => '/'
];
}
}
How can I achieve that? Thank you in advance!
Get user object and call function notify() on it. $this->notify() will not work because $this is not an instance of User class.
$user = User::find($user_id);
$user in the $user->notify(new AdminResponseToApplication($data)) function is available in notification class as $notifiable.
You can get any value of that object using $notifiable->name etc.
Remember:
AdminResponseToApplication is a class and you can do anything with it that a php class can.
So you can pass as many variables as you want to AdminResponseToApplication class in constructor and do what you want.
$user->notify(new AdminResponseToApplication($data))
As shown above I am sending a $data object to the class which is available in the constructor.
In the class
class AdminResponseToApplication extends notification implements ShouldQueue{
use Queueable;
public $myData;
public function __construct($data)
{
$this->myData = $data; //now you have a $data copied to $this->myData which
// you can call it using $this->myData in any function of this class.
}
}
My plugin code:
public function boot()
{
**I can:**
$user = new User();
$table = $user->getTable();
**I would like to:**
echo User::getTable();
exit;
$user = $this->user;
\Serviom\Guestpage\Models\Post::extend(function($model) use ($user) {
$model->rules = [
'name' => 'required|between:3,100',
'subject' => 'required|between:3,100',
'desc' => 'required|between:10,1000',
'parent_id' => 'nullable|exists:serviom_guestpage_posts,id',
'user_id' => 'nullable|exists:' . $table . ',id',
Variable in modal $table is protected member so its really nothing we can do about that but we can simply access it by adding public method to class [ extend it ]
In any of your plugin you can add this snippet
use RainLab\User\Models\User as UserModel;
class Plugin extends PluginBase
{
public function boot() {
UserModel::extend(function($model) {
$model->addDynamicMethod('getTableName', function() use ($model) {
return $model->getTable();
});
});
//....
Now you can able to call
echo User::getTableName();
Its like kind of hack but its only if you really badly needed it then you do something like this
if any doubt please comment.
You can add a static method to your Model as a helper :
class MyModel extends Model
{
public static function getTableName()
{
return with(new static)->getTable();
}
}
Usage : MyModel::getTableName()
Source
I've built a simple form model & view, a simple AR model, and a simple controller. The form model assigns the correct values to the AR instance, but when I call save(), none of those values are saved in the DB. Any ideas?
The form model:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
class PromptForm extends Model
{
public $name;
public $intro;
public $prompt;
public $notes;
public $questions;
public function attributeLabels()
{
return [
'name' => 'Prompt title',
'intro' => 'Intro',
'prompt' => 'Prompt body',
'notes' => 'Closing notes',
'questions' => 'Exploration questions',
];
}
/**
* #return array the validation rules.
*/
public function rules()
{
return [
[['name', 'prompt'], 'required'],
['name', 'filter', 'filter' => 'trim'],
['name', 'string', 'max' => 255],
[['intro', 'prompt', 'notes', 'questions'], 'default'],
];
}
public function post()
{
if ($this->validate()) {
$prompt = new Prompt();
$prompt->name = $this->name;
$prompt->intro = $this->intro;
$prompt->prompt = $this->prompt;
$prompt->notes = $this->notes;
$prompt->questions = $this->questions;
$prompt->author = \Yii::$app->user->getId();
//die(print_r($prompt, TRUE));
$prompt->save();
return $prompt;
}
return null;
}
}
The AR model:
<?php
namespace app\models;
use Yii;
use yii\db\ActiveRecord;
/**
* Prompt is the model behind the prompt item.
*/
class Prompt extends ActiveRecord
{
public $name;
public $intro;
public $prompt;
public $notes;
public $questions;
public $status;
public $author;
public $id;
/**
* #return string the name of the table associated with this ActiveRecord class.
*/
public static function tableName()
{
return 'prompt';
}
/**
* #return array the attribute labels.
*/
public function attributeLabels()
{
return [
'name' => 'Prompt title',
'intro' => 'Intro',
'prompt' => 'Prompt body',
'notes' => 'Closing notes',
'questions' => 'Exploration questions',
'status' => 'Status',
'author' => 'Author ID',
];
}
}
The controller:
<?php
namespace app\controllers;
use Yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\filters\VerbFilter;
use app\models\PromptForm;
use app\models\Prompt;
class PromptsController extends Controller
{
public function actionIndex()
{
// Return a list of all prompts:
return $this->render('index');
}
public function actionNew()
{
if (\Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new PromptForm();
if ($model->load(Yii::$app->request->post())) {
if ($prompt = $model->post()) {
Yii::$app->getSession()->setFlash('success', 'Your prompt was created successfully!');
return $this->goHome();
} else {
Yii::$app->getSession()->setFlash('error', 'Error while submitting your prompt.');
}
}
return $this->render('create', [
'model' => $model,
]);
}
}
Okay, I figured it out. Turns out that if you declare public attributes in your ActiveRecord model, they obscure the automatic attributes that are created by AR. Data gets assigned to your obscuring attributes but doesn't get sent into the database.
The correct AR model should have been simply this:
<?php
namespace app\models;
use Yii;
use yii\db\ActiveRecord;
class Prompt extends ActiveRecord
{
/**
* #return string the name of the table associated with this ActiveRecord class.
*/
public static function tableName()
{
return 'prompt';
}
}
Use
$prompt->save(false);
If that works that means that some validation rule fails.
Try
if ($model->load(Yii::$app->request->post())) {
if ($prompt = $model->post()) {
$model->save()
Yii::$app->getSession()->setFlash('success', 'Your prompt was created successfully!');
return $this->goHome();
} else {
Yii::$app->getSession()->setFlash('error', 'Error while submitting your prompt.');
}
}
In controller, change your if condition as follow :
if ($prompt = $model->post() !== null) {
This will validate that the value which is return is not null.
Your current validation condition is only validating where value is get assigned to variable $prompt or not. And that's why it's always returns true.
I ran across the same problem recently, when I combine the Active Record class with The Model class. Cause I know that AR actually extends Model in Yii2. Why not write less code.So I move the code from the Model to the AR.
$model = new User();
$model->load(Yii::$app->request->post())
But the AR's _attribute didn't get the post data in the form. the form data is actually in a Model object.
object(app\models\User)#39 (12) { ["password"]=> string(6) "google"
["newpass"]=> NULL ["name"]=> string(5) "Jane1" ["email"]=> string(16)
"jane#outlook.com" ["_attributes":"yii\db\BaseActiveRecord":private]=>
array(2) { ["password_hash"]=> string(60)
"$2y$13$.vNKpmosLjW/oYAhIezOZOj8rIG6QJvQj8tGHN2x78.75poXVn6Yi"
["auth_key"]=> string(32) "4XggNakVd-oeU28ny7obdw7gOmZJ-Rbu" }
simply delete the public attribute you want mass assign to the AR instance will make it work.
For who is struggling with this problem, I would remember to check the beforeSave method, if present. I mistakenly commented out the return statement.
public function beforeSave($insert)
{
// never toggle comment on this!!!
return parent::beforeSave( $insert);
}
How to Troubleshoot
First thing you should add while developing to your _form.php is errorSummary():
<?php $form = ActiveForm::begin(); ?>
// Some input fields
...
<?= $form->errorSummary($model); ?> // <--- Add this
...
<?php ActiveForm::end(); ?>
Simplify
Why not use scenarios instead if there is some minimal variation form to form:
In your model:
public function rules()
{
return [
[['field_1'], 'required', 'on' => self::SCENARIO_ADD], // only on add
[['field_2'], 'required', 'on' => self::SCENARIO_UPDATE], // only on update
[['field_3', 'field_4'], 'required'], // required all the time
];
}
In your controller:
public function actionAdd()
{
$model = new Model();
$model->scenario = Model::SCENARIO_ADD;
if ($model->load(Yii::$app->request->post())) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('add', ['model' => $model]);
}
Behaviors
Alternatively, rather than assign the user directly in your model, you could use a behavior like so:
https://www.yiiframework.com/doc/api/2.0/yii-behaviors-blameablebehavior
/**
* {#inheritdoc}
*/
public function behaviors()
{
return [
[
'class' => \yii\behaviors\BlameableBehavior::className(),
'value' => Yii::$app->user->identity->username,
],
[
'class' => \yii\behaviors\TimestampBehavior::className(),
'value' => new \yii\db\Expression('NOW()'),
],
[
'class' => 'sammaye\audittrail\LoggableBehavior',
'userAttribute' => 'updated_by', //blameable attribute of the current model.
'ignored' => ['updated_by', 'updated_at'], // This ignores fields from a selection of all fields, not needed with allowed
],
];
}