I have the following event handler in my model:
<?php
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//...
public static function boot()
{
parent::boot();
static::saving(function ($user) {
// die('inside');
if (empty($user->username)) {
$base = strtolower($user->first_name . '.' . $user->last_name);
do {
$username = $base . #$suffix;
$duplicate = User::where('username', '=', $username)->first();
} while($duplicate and $suffix = rand(1000, 9999));
// return the original/ generated username
$user->username = $username;
}
});
}
}
Basically when username is not set, the model will automatically generate a unique username from the first/last name. This works fine in the browser. But not in the CLI when I'm running my tests - username is not set, so it attempts to insert without username which my mysql table doesn't accept.
Below is how I'm setting up Eloquent console:
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'database' => 'sso_dev',
'username' => 'root',
'password' => 'vagrant1',
]);
$capsule->setEventDispatcher( new \Illuminate\Events\Dispatcher( new \Illuminate\Container\Container ));
$capsule->bootEloquent();
$capsule->setAsGlobal();
Both the web environment and testing use this same code. If I comment the line setEventDispatcher then the browser environment throws an error as the event handler doesn't fire. So I know the event dispatcher is doing it's job there. Just not the testing CLI environment. Any reason why this might be?
Btw I'm using Eloquent 5.3.
You will need to make a call to the setEventDispatcher again when it boots.
public static function boot(){
parent::boot();
static::setEventDispatcher(new \Illuminate\Events\Dispatcher());
static::saving(function ($model){
// now you can reference your model
}
}
Related
I try to use setcookie in Laravel listeners with queue, setcookie return true but the problem is in chrome (Or Firefox) DevTools> Application> Cookies cookies are not set and not working.
My Class:
class FlarumEventSubscriber implements ShouldQueue
{
public function onUserLogin($event)
{
$user = $event->user;
$response = $this->authenticate($user->mobile, $user->password);
$token = $response['token'] ?: '';
setcookie('flarum_session', $token, time() + 99999999 , '/', 'localhost'); // ======> The problem
}
public function subscribe($events)
{
$events->listen(
'Illuminate\Auth\Events\Login',
'App\Listeners\FlarumEventSubscriber#onUserLogin'
);
}
private function authenticate($id, $password)
{
$endpoint = '/api/token';
$method = 'POST';
$data = [
'identification' => $id,
'password' => $password,
'lifetime' => 99999999
];
return $this->sendRequest($endpoint, $method, $data);
}
}
EventServiceProvider :
class EventServiceProvider extends ServiceProvider
{
/**flarum subscriber */
protected $subscribe = [
'App\Listeners\FlarumEventSubscriber',
];
}
In FlarumEventSubscriber class, I send a request after the user login to Laravel, and after that, some information needs to be stored in the cookie.
My efforts:
Use Cookie::queue() instead of setcookie() => Not working
Test on other domains => Not working
Info:
PHP v7.4.26
Laravel 8.0
Wampserver v3.2.6 64bit
I am new to Laravel tests, and i am currently building my tests so they use certain data in my database to check if an HTTP request did the appropriate job.
I am trying to figure out how i can "seed" data into my database before i run my tests but also delete this data after all the tests are complete (either succeed of failed, should get deleted anyway).
I tried to understand how to do it correctly reading some articles in the internet but just couldn't find the right solution for me.
I know that in node.js the Mocha tests has a "beforeEach" to manipulate data before every test, is there a similar option in PHP Laravel?
With laravel version greater than 5.5 you can use the RefreshDatabase trait within your test which will reset your database after test have been run. All you will have to do is to add it at the top of your test like bellow
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
class YourModelNameTest extends TestCase
{
use RefreshDatabase;
public function setUp()
{
parent::setUp();
// This will run all your migration
Artisan::call('migrate');
// This will seed your database
Artisan::call('db:seed');
// If you wan to seed a specific table
Artisan::call('db:seed', ['--class' => 'TableNameSeeder ', '--database' => 'testing']);
}
}
The RefreshDatabase trait have define refreshDatabase which migrate the database before and after each test. you can see the code here
RefreshDatabse refreshDatabase
While Yves answer should work (separate database), i have found a set of methods that can help achieving what i needed:
"setUp" and "tearDown".
the "setUp" method will do something before the set of tests will run (the test class) and "tearDown" will do something after all of those tests were executed.
I'm attaching my test class here for an example of how i used those:
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserTest extends TestCase
{
private $token;
public function setUp(){
parent::setUp();
$userData = [
'email' => 'mytestemail#email.com',
'password' => '123456'
];
$response = json_decode($this->post('/register', $userData)->baseResponse->getContent());
$this->token = $response->token;
}
/**
* A basic test example.
*
* #return void
*/
public function testRegister()
{
$validData = [
'email' => 'testrerere#email.com',
'password' => '123456'
];
$invalidEmail = [
'email' => 'not_a_real_email',
'password' => '123456'
];
$invalidPassword = [
'email' => 'test2#email.com',
'password' => '12'
];
$emptyData = [];
$goodResponse = $this->post('/register', $validData);
$goodResponse->assertStatus(201);
$goodResponse->assertJsonStructure(['token']);
User::where('email', 'testrerere#email.com')->delete();
$invalidEmailResponse = $this->post('/register', $invalidEmail);
$invalidEmailResponse->assertStatus(400);
$invalidPasswordResponse = $this->post('/register', $invalidPassword);
$invalidPasswordResponse->assertStatus(400);
$emptyDataResponse = $this->post('/register', $emptyData);
$emptyDataResponse->assertStatus(400);
}
public function testToken()
{
$validData = [
'email' => 'mytestemail#email.com',
'password' => '123456'
];
$invalidData = [
'email' => 'nonexistingemail#test.com',
'password' => '123456'
];
$validDataResponse = $this->post('/token', $validData);
$validDataResponse->assertStatus(200);
$validDataResponse->assertJsonStructure(['token']);
$invalidDataResponse = $this->post('/token', $invalidData);
$invalidDataResponse->assertStatus(400);
}
//get an account object based on a token
public function testAccount()
{
$goodResponse = $this->withHeaders([
'Authorization' => 'Bearer ' . $this->token,
])->json('GET', '/account');
$goodResponse
->assertStatus(200)
->assertJsonStructure([
'user',
]);
//altering the token to get an invalid token error
$badResponse = $this->withHeaders([
'Authorization' => 'Bearer L' . $this->token,
])->json('GET', '/account');
// print_r($badResponse->baseResponse->getContent());
$badResponse->assertJson(['status' => 'Token is Invalid']);
}
public function tearDown()
{
User::where('email', 'mytestemail#email.com')->delete();
User::where('email', 'testrerere#email.com')->delete();
parent::tearDown();
}
}
I'm trying to follow a tutorial here which is just a simple helloworld that pulls data from a database.
I'm using CI 3.0.6.
I have never used codeigniter before, but I wanted to try a framework.
Anyways, I think I followed the tutorial correctly (I do think the tutorial uses a slightly older version of codeigniter as some of the paths are a bit different) but I cannot get this to work, it gives me the error The page you requested was not found.
I have a few questions on this.
The tutorial tells me to use a model, but what is a model?
How can I get this working? If I can get this working properly I have no doubt I can use it as a learning experience to expand my project.
My code:
helloworld.php (Controller # BASEDIR/application/controllers)_:
<?php
class Helloworld extends Controller{
function index()
{
$this->load->model('helloworld_model');
$data['result'] = $this->helloworld_model-><span class="sql">getData</span>();
$data['page_title'] = "CI Hello World App!";
$this->load->view('helloworld_view',$data);
}
}
?>
helloworld_model.php (Model # BASEDIR/application/models):
<?php
class Helloworld_model extends Model {
function Helloworld_model()
{
// Call the Model constructor
parent::Model();
}
function getData()
{
//Query the data table for every record and row
$query = $this->db->get('data');
if ($query->num_rows() > 0)
{
//show_error('Database is empty!');
}else{
return $query->result();
}
}
}
?>
helloworld_view (view # BASEDIR/application/views):
<html>
<head>
<title><?=$page_title?></title>
</head>
<body>
<?php foreach($result as $row):?>
<h3><?=$row->title?></h3>
<p><?=$row->text?></p>
<br />
<?php endforeach;?>
</body>
</html>
my db info:
$db['default'] = array(
'dsn' => '',
'hostname' => 'private',
'username' => 'private',
'password' => 'private',
'database' => 'database',
'dbdriver' => 'mysql';
'dbprefix' => 'va',
'pconnect' => FALSE,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci',
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
& my autoload:
$autoload['libraries'] = array('database');
I'm trying to access the page by going to http://myurl.com/dev/index.php/helloworld/
/dev/ is my installation directory.
Again, I've never used a framework before so this question is probably really simple to fix.
Well if you followed that tutorial completely then you need to access that page without index.php in url.
Also use PHP5 constructors so change:
function Helloworld_model()
{
// Call the Model constructor
parent::Model();
}
To:
function __construct()
{
// Call the Model constructor
parent::__construct();
}
This also makes no sense:
if ($query->num_rows() > 0)
{
//show_error('Database is empty!');
}
else
{
return $query->result();
}
The code inside if/else should be switched. If number of rows returned is greater than 0 then return result else show error that database is empty.
And last thing in controller:
$data['result'] = $this->helloworld_model-><span class="sql">getData</span>();
Store only database result to data variable, pass it to view and wrap whatever elements of the data in the view.
$data['result'] = $this->helloworld_model->getData();
If you read a documentation controller
enter link description here
you can see that in application/controller/Name.php the name of the controller needs to be capitalize, inside the class you need to have (remember extends CI_Controller)
<?php
class Controller_name extends CI_Controller {
public function view($page = 'home')
{
}
}
the call to the model is ok $this->load->model('helloworld_model');m but remember that in applicatoin/models/Model_name.php needs to be capitalized too, and insede you need to have (remember extends CI_Model)
<?php
class Blog_model extends CI_Model {
public function __construct()
{
// Call the CI_Model constructor
parent::__construct();
}
Yes, you needs to autoload database, but in you database config (application/config/database.php) change dbdriver to mysqli
This is a similar situation:
Multi tenancy in Laravel Eloquent ORM
I would like to modify eloquent model to have a variable database connection name. I have the name available as a string. I will have hundreds of models connecting to a tenant database, so I want a one-liner...
I have tried several approaches. The one that works is this:
$posUser = new posUser();
$posUser->setConnection($this->system->getDBC());
$posUser->create($posUserData);
with the classes set up like this:
class posUser extends myModel
{
}
class myModel extends Model
{
public function setConnection($dbc)
{
$this->connection = $dbc;
}
}
However this is a bit verbose.
What I want is a one liner:
posUser::create($posUserData);
Which, I do have this on liner working by creating a new database connection and setting that connection to the default, which happens when the registered user visits the site. I would feel more comfortable with a command like (which errors...)
posUser::On($connection_name)->create($posUserData);
And require all models using the tenant databases to specify the connection. I am simply terrified that one tenant will somehow write to another tenant database. Any ideas?
The Answer for me so far is to set a connection as the default, then operate as normal.
1) get the system connection information from a database using a "main" connection
2) create the tenant connection
3) set it to default
Then all the tenant models/queries you will not supply a connection to. The main connection for me is set as 'main'.
Tenant Connection class
class TenantDatabaseConnector
{
public static function GetDBCPrefix()
{
return strtolower(env('DB_PREFIX')) .'_'.env('MAIN_DB_NAME') . '_';
}
public static function createTenantConnection(System $system)
{
if(! self::checkDB($system->dbc()))
{
dd('You sure there is a database named ' . $system->dbc());
}
$env = strtoupper(env('DB_PREFIX'));
$connections = Config::get('database.connections');
$tenant_connection = [
'driver' => 'mysql',
'host' => env($env . '_DB_HOST'),
'database' => $system->dbc(),
'username' => env($env. '_DB_USERNAME'),
'password' => env($env .'_DB_PASSWORD'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
'port' => '3306'
];
$connections[$system->dbc()] = $tenant_connection;
Config::set('database.connections', $connections);
self::setDefaultDBC($system);
return true;
}
public static function setDefaultDBC($system)
{
Config::set('database.default', $system->dbc());
}
public static function checkDB($dbc)
{
$sql = "SELECT SCHEMA_NAME FROM
INFORMATION_SCHEMA.SCHEMATA
WHERE SCHEMA_NAME = '". $dbc ."'";
$dbexists = DB::connection('main')->select($sql);
if(sizeof($dbexists) > 0)
{
return true;
}
return false;
}
}
You see a $env variable which I am using to set local_, alpha_, beta_, testing_ prefixes to my databases.
I want to switch DB based on domain, selecting credentials from another DB, but I can't switch..
AppController.php
// Select username, password and database based on domain
$this->Company->find('first', [...]);
if ($company) {
// Connect to second database, droping connection from first.
$dataSource = ConnectionManager::getDataSource('default');
$dataSource->config['login'] = $company['Company']['dbuser'];
$dataSource->config['password'] = $company['Company']['dbpass'];
$dataSource->config['database'] = $company['Company']['dbname'];
/**
* PROBLEM START HERE:
* Here, need to use new database settings, and, this case
* Company table does not exists, but I always get it, so,
* I think I am connected with the first and not second connection.
*/
print_r($this->Company->find('first'));
}
How I can correct this?
EDIT
I have tried without success:
ConnectionManager::drop('default');
ConnectionManager::create('default', $settings);
A print_r(ConnectionManager::create('default', $settings)) return:
[... lots of things ... ]
[config] => Array
(
[persistent] =>
[host] => localhost
[login] => login
[password] => password
[database] => database
[port] => 3306
[datasource] => Database/Mysql
[prefix] =>
)
[... more things ... ]
EDIT 2
Now, I can switch database, but, Company Model always get the old database settings.
FooController.php
<?php
App::uses('AppController', 'Controller');
class FooController extends AppController {
var $uses = array('Foo', 'Company');
public function index() {
echo'<pre>';
print_r($this->Company->find('first')); // Here I get from old settings
print_r($this->Foo->find('first')); // Here I gete from new settings
echo'</pre>';
$this->layout = false;
$this->render(false);
}
}
AppController.php
public function beforeFilter() {
$company = ClassRegistry::init('Company')->find('first');
$settings = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => 'localhost',
'login' => $company['Company']['dbuser'],
'password' => $company['Company']['dbpass'],
'database' => $company['Company']['dbname'],
'prefix' => ''
);
ConnectionManager::getDataSource('default')->disconnect();
ConnectionManager::drop('default');
ConnectionManager::create('default', $settings);
ConnectionManager::getDataSource('default')->connect();
}
It looks like you need to dynamically create a config and change configs on-the-fly. I don't know if this will work, but it may lead you to the answer.
First we need to create a config for the datasource to use. For this we can use ConnectionManager::create
Next, we need to change configs on-the-fly and for that we can use Model::setDataSource
I haven't tried, but it looks like it should do. If there are issues, leave a comment so I can update the answer.
Edit: This may not work since every model would need to change the to the new datasource. There is a ConnectionManager::drop() method that you could use to drop the default config and then ConnectionManager::create('default', array(...)); to use a new default perhaps.
You can switch databases in your database config file.
Configure your Config/database.php file as follows:
<?php
class DATABASE_CONFIG {
// config for e.g. localhost
public $default = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => 'xxxxx',
'database' => 'cake',
'encoding' => 'utf8'
);
// DB config specifically for your domain
public $domain_x = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'username_at_hosting_provider',
'password' => '087bJ#ytvh&^YU#T',
'database' => 'blabla_cake',
'encoding' => 'utf8'
);
public function __construct(){
// switch config depending on domain / env('HTTP_HOST')
if(env('HTTP_HOST')=='domain_x.com'){
$this->default = $this->domain_x;
}
// otherwise keep $this->default
}
}
Edit:
It seems a was a bit too fast answering your question: it does not really cover your question. Sorry!
Okay, so I managed to fix this problem creating the __construct function whithin AppModel and put all the code to drop the default datasource and create another with the new config. Something like this:
AppModel.php
public function __construct($id = false, $table = null, $ds = null)
{
App::uses('CakeSession', 'Model/Datasource');
parent::__construct($id, $table, $ds);
if ($this->useDbConfig == 'default') {
// Change the datasource config
$user = CakeSession::read('Auth.User');
$this->changeDataSource($user);
}
}
public function changeDataSource($config)
{
...
...
...
$dados = array();
$dados['database'] = $config['titulo_bd'];
$dados['host'] = $config['host_bd'];
$dados['login'] = $config['login_bd'];
$dados['password'] = $config['senha_bd'];
$dados['datasource'] = 'Database/Mysql';
$dados['persistent'] = false;
$dados['prefix'] = '';
$dados['encoding'] = 'utf8';
ConnectionManager::create('default', $dados);
ConnectionManager::getDataSource('default')->connect();
}
My case is very similar. In my case, I have two types of connections. One is the default core connection which uses the Config/database.php file for connection configurations. Second one is a dynamic configuration which uses db details saved for logged in user. So, for a few specific actions I have to switch between the default db connection and user based db connection. Here's how it came up.
To switch to user based db connection from default file
try {
ConnectionManager::getDataSource('default')->disconnect();
ConnectionManager::drop('default');
ConnectionManager::create('default', $this->config);
ConnectionManager::getDataSource('default')->connect();
$db = ConnectionManager::getDataSource('default');
} catch (MissingDatabaseException $e) {
$this->Session->setFlash($e->getMessage());
}
where $this->config is user based connection.
To switch back to default file based connection I do:
try {
ConnectionManager::getDataSource('default')->disconnect();
ConnectionManager::drop('default');
$dbconfig = new DATABASE_CONFIG();
ConnectionManager::create('default', $dbconfig->default);
ConnectionManager::getDataSource('default')->connect();
$db = ConnectionManager::getDataSource('default');
}
where $dbconfig->default i.e. $default contains my default connection configuration in Config/database.php