I'm working on a system for payments for a school and I need to automatically add a new monthly debt for students, related to the month price. I was thinking about using scheduled events but it hasn't worked when I'm testing.
Please could anyone tell me if this is a good approach or give me any piece of advice for another approach? Thanks in advance.
This is what I was testing but it seems that it's not running since there's no new value on the debts table.
delimiter $$
create definer=`root`#`localhost` event registrar_mensualidad
on schedule every 5 second
on completion preserve
enable
do
begin
insert into debts(auto, amount, student_id, institution_payment_reason_id)
values (1, 100, 1, 1);
end;$$
I checked if the events_scheduler is enabled and it is in fact.
The system is being developed with laravel connected to a mysql database.
You might consider cron jobs.
CRONTAB (at every 1st day of the month)
#: sudo nano /etc/crontab
0 0 1 * * root php /var/www/html/your_php_query.php > output.txt
PHP
$qry = "insert into debts(auto, amount, student_id, institution_payment_reason_id)
values (1, 100, 1, 1);"
return $con->query($qry);
LOG
ouput.txt
1 // if insert is true
Or Laravel
App\Console\Kernel
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*
* #param \Illuminate\Console\Scheduling\Schedule $schedule
* #return void
*/
protected function schedule(Schedule $schedule)
{
.........
// add this
$schedule->call(function () {
DB::insert('insert into debts(auto, amount, student_id, institution_payment_reason_id) values (?, ?, ?, ?)', [1, 100, 1, 1]);
})->monthly();
}
}
To check/validate the schedule, type: php artisan schedule:list
Output:
0 0 1 * * Closure at: app\Console\Kernel.php:22 Next Due: 3 weeks from now
Related
I have a multi tenanted app where each user can generate invoices / credits.
I would like to generate an auto incrementing number by each user invoice or user credit while still keeping the id column used in Laravel relationships.
I want to auto increment the number column based on user_id and type.
My Invoices table:
id number biller_id type ...
1 1 1 invoice
2 2 1 invoice
3 1 1 credit
4 1 2 invoice
So I end up with:
Biller 1 will have invoice numbers 1, 2, 3... and credit numbers 1, 2, 3...
And same for each user.
I have not been successful in locking the table for each creation event so that other transactions do not access their last invoice number, so I end up with repeated invoice / credit numbers for each user. I can see this from running my seed.
I have tried the following without success:
In the boot method of my Invoice class:
/**
* Boot Method of class
*/
protected static function boot()
{
/**
* When creating the invoice, create its "number"
*/
static::creating(function ($obj) {
$lastTransaction = Invoice::where('biller_id', $obj->biller_id)
->where('type', $obj->biller_id)
->orderBy('created_at', 'DESC')
->lockForUpdate()
->first();
$nextId = 1;
if ($lastTransaction) {
$nextId = $lastTransaction->number + 1;
}
$obj->number = $nextId;
});
parent::boot();
}
Also as a static createWithlock method (and I remove the lockForUpdate from the creating method):
public static function createWithLock($invoiceData = null)
{
if (! $invoiceData) {
return [
'type' => 'error',
'value' => 'No invoice data supplied!',
];
}
DB::beginTransaction();
try {
// Lock Invoices table to ensure correct creation of invoice number
DB::select(DB::raw('LOCK TABLES invoices WRITE'));
self::create($invoiceData);
DB::select(DB::raw('UNLOCK TABLES'));
} catch (\Exception $e) {
DB::rollBack();
return [
'type' => 'error',
'value' => $e->getMessage(),
];
}
DB::commit();
return [
'type' => 'success',
'value' => 'Invoice created successfully.',
];
}
I get repeated number values for the combinations. Any suggestions appreciated on how to lock the table during creation process to stop duplicates.
Your "for update" lock must be run inside a transaction. I cannot see from your code, that it is. So both the ->lockForUpdate() and the creation of the new invoice must be inside the same transaction.
I often find that Atomic Locks are a lot easier to use, and will take care of more actions (including if you calculate an ID in your code that is parsed back to the database layer after some small delay).
Atomic Locks are also easily added as a middleware (that can later easily be added to more endpoints, that locks the same object).
How I can create automatically record daily with laravel task scheduling without running artisan command. In my case I just want when the date is 28-02-2020 create record in db, when date is 29-02-2020 create another record and etc...
Just check Laravel docs on Task Scheduling. First add this cron entry:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Then, You may define all of your scheduled tasks in the schedule method of the App\Console\Kernel class:
/**
* Define the application's command schedule.
*
* #param \Illuminate\Console\Scheduling\Schedule $schedule
* #return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('your_table')->insert([
'first_column' => 'first_value',
// List other columns
]);
})->daily();
}
I have an array of integers that need to be inserted as a batch of rows. Each row needs some other data.
$ids = [1,2]
$thing = 1
$now = Carbon::now(); // This is just a timestamp.
$binding_values = trim(str_repeat("({$thing}, ?, '{$now}'),", count($ids)), ',');
The string $binding_values looks like this:
"(1, ?, '2019-01-01 00:00:00'), (1, ?, '2019-01-01 00:00:00')"
Then I prepare my query string and bind the parameters to it. The IGNORE is used because I have a composite unique index on the table. It doesn't seem relevant to the problem though so I've left the details out.
DB::insert("
INSERT IGNORE INTO table (thing, id, created_at)
VALUES {$binding_values}
", $ids);
This works almost all the time but every now and then I get an error SQLSTATE[HY000]: General error: 2031.
Is the way I'm doing this parameter binding some kind of anti-pattern with Laravel? What might the source of this error be?
Because there is no risk of injection in this method and there is no chance that this method would be extended to a use case with a risk of injection, I've modified it to bake in all the parameters and skip parameter binding. I haven't seen any errors so far.
I would still like to know what might cause this behaviour so I can manage it better in the future. I'd be grateful for any insight.
I don't see a big issue with your query other than baking parameters into the query, which is vulnerable to SQL injection.
From what I can see, your problem is that you need INSERT ÌGNORE INTO which is not supported out of the box by Laravel. Have you thought about using a third-party package like staudenmeir/laravel-upsert?
An alternative could be to wrap your query in a transaction and select existing entries first, giving you the chance to not insert them a second time:
$ids = [1, 2];
$thing = 1;
$time = now();
DB::transaction(function () use ($ids, $thing, $time) {
$existing = DB::table('some_table')->whereIn('id', $ids)->pluck('id')->toArray();
$toInsert = array_diff($ids, $existing);
$dataToInsert = array_map(function ($id) use ($thing, $time) {
return [
'id' => $id,
'thing' => $thing,
'created_at' => $time
];
}, $toInsert);
DB::table('some_table')->insert($dataToInsert);
});
This way you will only insert what is not present yet, but you will also stay within the framework capabilities of the query builder. Only downside is that it will be slightly slower due to a second roundtrip to the database.
My Laravel web app uses the schema builder for database portability, so the MySQL-specific YEAR column type is not available.
I want to be able to sort and select rows of data by year (including BETWEEN). What's the best portable column type for storing year values?
What's the best portable column type for storing year values?
smallint is a good choice to represent years, and it's ANSI SQL, so will work in most databases. It will last until the year 32767.
Some databases support create domain which basically lets you create your own types. You could create a year domain for other databases. I prefer the simplicity of smallint for year though.
Could you do something like this:
public function up()
{
Schema::create('mytable', function(Blueprint $table)
{
$table->increments('id');
// columns
$table->timestamps();
});
DB::statement('ALTER mytable ADD year YEAR' );
}
I think this should work:
Schema::table('tablea_name', function(Blueprint $table)
{
DB::statement('ALTER TABLE table_name ADD year YEAR(4);');
});
Portable solution would be if Laravel provides any way to access this method (in /vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php) in upcoming versions:
/**
* Add a new column to the blueprint.
*
* #param string $type
* #param string $name
* #param array $parameters
* #return \Illuminate\Support\Fluent
*/
protected function addColumn($type, $name, array $parameters = array())
{
$attributes = array_merge(compact('type', 'name'), $parameters);
$this->columns[] = $column = new Fluent($attributes);
return $column;
}
As at Laravel 5.8, you can do this:
$table->year('birth_year');
I'm in the process of learning CI for myself and this came up. If I run multiple INSERTs, whether as transaction or not, is it possible to get the insert ID of each (in the proper order) instead of running $this->db->insert_id() one at a time for each INSERT?
For example
-- Get ID of each
INSERT INTO table VALUES (insert1), (insert2), (insert3)
-- What if they're called separately
INSERT INTO table VALUES (insert1)
INSERT INTO table VALUES (insert2)
INSERT INTO table VALUES (insert3)
Is it still possible to get the ID as an array from all this?
I think you really must insert it between each inserts as far as i know, just store it in an array.
var $insert_id = array();
public function insert()
{
INSERT INTO table VALUES (insert1)
$this->insert_id =$this->db->insert_id();
INSERT INTO table VALUES (insert2)
$this->insert_id =$this->db->insert_id();
INSERT INTO table VALUES (insert3)
$this->insert_id =$this->db->insert_id();
}
everytime a database record is inserted you can still use insert_id(); after each insert then store it to an array. then you coudl make a function to return it.
public function get_inserted_keys()
{
return $insert_id;
}
It is simple
Controller
$ids = array();
$data[0] = array(blah,blah);
$data[1] = array(blah,blah);
$data[2] = array(blah,blah);
for($i=0;$i<3;$i++)
{
$ids[] = $this->my_model->insert($data[$i]);
}
echo '<pre>';
print_r($ids);
Model
public function insert($data)
{
$this->db->insert('table_name',$data);
return $this->db->insert_id()
}