I have an yii2 project with the advanced template and I want to implement notifications using a mosquitto broker.
I already have the publish part done and working, now I'd like to have some help on subscribing to a topic on my frontend app. I already tried, but the page seems to stop working when I subscribe to any topic.
Is there any easy way or tutorial that I can use? If any more information is needed, please ask.
P.S: My idea was: When I open any page on frontend, I check for messages, save them in a array, set them as view param and then render my page.
EDIT: So far i've tried the following
Class
<?php
namespace common\models;
use Yii;
class Notificacoes
{
private $listaNotificacoes;
public function __construct($id, $name)
{
$this->listaNotificacoes = array();
$server = "127.0.0.1";
$port = 1883;
$username = "";
$password = "";
$client_id = $id;
$mqtt = new \common\mosquitto\phpMQTT($server, $port, $client_id);
if(!$mqtt->connect(true, NULL, $username, $password)) {
exit(1);
}
$topics[$name] = array("qos" => 0, "function" => "procmsg");
$mqtt->subscribe($topics, 0);
while($mqtt->proc()){
}
$mqtt->close();
}
function procmsg($topic, $msg)
{
\array_push($this->listaNotificacoes, $msg);
}
public function getAll()
{
return $this->listaNotificacoes;
}
}
SiteController: I tried to get the messages on the beforeAction method
public function beforeAction($action)
{
if (!parent::beforeAction($action)) {
return false;
}
$notifications = array();
if (!Yii::$app->user->isGuest)
{
$notifs = new Notificacoes(Yii::$app->user->identity->getId(), Yii::$app->user->identity->username);
$notifications = $notifs->getAll();
}
$this->view->params['notifications'] = $notifications;
return true;
}
This model is unlikely to work because normally messages are only delivered by MQTT at the instant they are published.
So unless you are using retained messages or persistent subscriptions for a given client id to queue messages. This means your while loop will never have any messages to process
Related
I'm trying to insert record into my audit table upon update of record in any other table. For example, if a user update his profile I want to store the old record and the newly updated record in my audit table. For this in my user model I'm trying to use beforeSave() and pass the value to my audit controller
public function beforeSave($insert)
{
if((parent::beforeSave($insert))){
// Place your custom code here
$query = DepCustomer::findOne($this->customer_id);
Yii::$app->runAction('audit-trial/createaudit', ['query' => $query]);
return true;
}
}
And the action code in audit controller for now
public function actionCreateaudit($query)
{
$model = new Audit();
$model->old = '';
foreach($query as $name => $value){
//$temp = $name .': '. $value.', ';
//$contentBefore[] = $temp;
$audit->old = $audit->old.$name .': '. $value. ', ';
}
// I've not yet any other code for now I'm trying to get the old value
$model->save();
}
I'm getting 404 not found error. What do I need to change in my code to make it work? Thank you!
instead of runAction() . If you want to perform operation on another model, prefer to create a static function in that model (in your case Audit model) to save the data
public function beforeSave($insert)
{
if((parent::beforeSave($insert))){
// Place your custom code here
$query = DepCustomer::findOne($this->customer_id);
Audit::saveOldDetails($query);
return true;
}
}
and write saveOldDetails function in Audit Model
public static saveOldDetails($query){
// your business logic here
}
Refer this link
http://www.yiiframework.com/doc-2.0/yii-base-controller.html#runAction()-detail
http://www.yiiframework.com/doc-2.0/guide-security-authorization.html#role-based-access-control-rbac
In the documentation, it says that you can assign the role to the user in the advanced template by using this code:
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->save(false);
// the following three lines were added:
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $user->getId());
return $user;
}
return null;
}
The problem is that I am using the basic template. Is there a way of doing this inside the basic template?
I thought about using the afterSave method; however, I am not sure how to do this.
public function afterSave($insert)
{
}
Any idea on how it can be done?
public function afterSave($insert)
{
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $this->Id());
}
I am thinking this could work, but I am not totally sure.
It does not depend on used template.
Your example is correct, except few things.
$this->Id() should be replaced with $this->id (assuming primary key of users table is named id).
Note that you need also call parent implementation of afterSave() method and you missed $changedAttributes parameter:
/**
* #inheritdoc
*/
public function afterSave($insert, $changedAttributes)
{
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $this->id);
parent::afterSave($insert, $changedAttributes);
}
For further improvements, you can wrap saving in transaction, so if something is failed in afterSave(), model is not saved (afterSave() event handler is executed after model is saved in database).
Also you can move assigning role logic to separate method.
Note that with this logic every registered user will have that role. You can wrap it with some condition, however it's better to assign role through admin interface.
You can see how it's implemented for example in this extension. For example you can create separate form, action and extend GridView ActionColumn with additional icon for assigning role.
I'm having a problem involving a database transaction in one class that is timing out due to a secondary database connection being opened within the transaction; the problem started occurring when I added a foreign key constraint. And, testing using:
SET foreign_key_checks = 0;
I've been able to confirm this.
My database class looks like this (I've left off all of the methods):
class Db {
function __construct($config) {
$this->config = $config;
}
private function connect($config) {$dsn = 'mysql:host=' . $config['host'] . ';dbname=' . $config['dbname'] . ';charset=utf8';
$options = array(
// PDO::ATTR_PERSISTENT => true,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$dbh = new PDO($dsn, $config['username'], $config['password'], $options);
$dbh->exec("SET NAMES utf8;");
return $dbh;
}
}
My model looks like this:
class Model {
function __construct() {
$this->db = new Db(array('host'=>DB_HOST,'dbname'=>DB_NAME,'username'=>DB_USERNAME,'password'=>DB_PASSWORD));
}
}
The code below then performs a little bit of logic, then an insert into the question_orders table: question_orders has a column question_id, with a foreign key index, which references the parent table questions; I think that the problem is that Assessment_Question_Orders extends the Model and creates a new database connection? Any thoughts on how to maintain both the transaction and foreign key aspects would be appreciated.
class This_Is_A_Problem extends Model() {
public function __construct() {
parent::construct();
}
public function problemFunction() {
/*variable init code left out*/
$this->db->beginTransaction();
$db_result = false;
try {
$db_result = $this->db->insert('questions', $questions_data);
$new_insert_id = $this->db->lastInsertId();
$assessment_question_orders = new Assessment_Question_Orders();
$question_number = $assessment_question_orders->insertSingleQuestionOrder($module_id, $new_insert_id);
$db_result = $this->db->commit();
}
} catch (PDOException $e) {
$this->db->rollBack();
}}}
One thread should (usually) have only one connection to the database. So I recommend one of these patterns:
Plan A: a single $db passed into all classes:
$db = new PDO(...);
$my_obj = new My_Class($db); -- $db is saved in $this->db for use within the methods of My_Class.
Plan B: a singleton Db class with a getter method:
// Singleton (of sorts)
class Db
{
private static $db;
function __construct()
{
self::$db = new PDO(...);
// A variant would include "lazy" instantiation of self::$Db.
}
function Get_Db() { return self::$db; } // All calls get the same `db`
}
class My_class
{
function My_Method()
{
$db = Db::Get_Db();
$db->...
}
}
new Db(); // one time call at start of program
There is only rarely a need to have two db connections in a single program. Plan A easily allows for such. (But see if you can avoid it -- you are in trouble now because of it.)
While running PHPUnit tests, a lot of connections are created but not closed.
I can see this in
mysql> show processlist;
In my database class I create a db connection by implementing PHPUnit_Extensions_Database_TestCase#getConnection().
I make sure that in the teardown the connection gets closed. See snippet:
<?php
abstract class My_Tests_DatabaseTestCase extends \PHPUnit_Extensions_Database_TestCase
{
static private $pdo = null;
private $conn = null;
/**
* #throws RuntimeException
* #return PHPUnit_Extensions_Database_DB_IDatabaseConnection
*/
final public function getConnection()
{
$iniFilePath = __DIR__ . '/../../../db-config.ini';
$iniFile = parse_ini_file($iniFilePath, true);
$dsn = "mysql:dbname=".$iniFile['phpunit']['dbname'].";host=".$iniFile['phpunit']['host'];
if ( $this->conn === null ) {
if ( self::$pdo == null ) {
self::$pdo = new \PDO($dsn, $iniFile['phpunit']['user'], $iniFile['phpunit']['password']);
}
$this->conn = $this->createDefaultDBConnection(self::$pdo, $iniFile['phpunit']['dbname']);
}
return $this->conn;
}
protected function getSetUpOperation()
{
return new \PHPUnit_Extensions_Database_Operation_Composite(array(
new \TestsExtensions\TruncateDatabaseOperation(),
\PHPUnit_Extensions_Database_Operation_Factory::INSERT()
));
}
protected function getTearDownOperation() {
return \PHPUnit_Extensions_Database_Operation_Factory::TRUNCATE();
}
protected function setUp()
{
parent::setUp();
$em = \ORM\Provider::getInstance()->getEntityManager(\ORM\Provider::DEFAULT_ID);
$em->clear();
}
protected function tearDown()
{
parent::tearDown();
$em = \ORM\Provider::getInstance()->getEntityManager(\ORM\Provider::DEFAULT_ID);
$em->getConnection()->close();
if ($this->conn) {
$this->conn->close();
}
}
}
Debugging showed that the connections were closed, but the processlist showed them with status "sleep".
The amount of connections rises until I get the "too many connections" error.
I do not want to increase the number of connections. I want to close the connection.
What can I modify to make this happen?
Snippet from PDO Connection management:
Upon successful connection to the database, an instance of the PDO
class is returned to your script. The connection remains active for
the lifetime of that PDO object. To close the connection, you need to
destroy the object by ensuring that all remaining references to it are
deleted--you do this by assigning NULL to the variable that holds the
object. If you don't do this explicitly, PHP will automatically close
the connection when your script ends.
Don't store the PDO or set it to null in the teardown:
Remove static private $pdo = null;
or
protected function tearDown()
{
parent::tearDown();
$em = \ORM\Provider::getInstance()->getEntityManager(\ORM\Provider::DEFAULT_ID);
$em->getConnection()->close();
if ($this->conn) {
$this->conn->close();
}
self::$pdo = null;
}
I don't think you need to store the PDO if you are going to pass it into the PHPUnit DB connection
Here's what I'm trying to do: I've got a db.php file that does all the db manipulation.
It has 2 static methods, connect and deconnect.
In my other file i simply use db::connect() and db::deconnect(). The mysql_close($con) in the deconnect method just doesn't know who $con is.
Since I don't want to instantiate my class static is the only way to go.
Declaring 'private $con' in class db doesn't seem to have an effect.
Any ideas?
class db {
public static function connect() {
$dbData = parse_ini_file($_SERVER['DOCUMENT_ROOT'].'/config.ini');
$con = mysql_connect($dbData['host'],$dbData['user'],$dbData['pass']);
$db = mysql_select_db($dbData['db']);
if ((!$con) || (!$db))
return 0;
else return 1;
}
public static function deconnect() {
mysql_close($con);
}
}
In deconnect, $con is out of scope.
You should make it a static member, like this:
class db {
static $con;
public static function connect() {
$dbData = parse_ini_file($_SERVER['DOCUMENT_ROOT'].'/config.ini');
self::$con = mysql_connect($dbData['host'],$dbData['user'],$dbData['pass']);
$db = mysql_select_db($dbData['db']);
if ((!self::$con) || (!$db))
return 0;
else return 1;
}
public static function deconnect() {
if( !isset( self::$con ) ) return;
mysql_close( self::$con );
}
}
The message makes sense as $con is out of scope in your deconnect() method (deconnect...?).
Use a static data member
class db {
static $con;
}
Access it through self::$con.
Well, it seems to me that $con is a local variable (local to the method connect). Thus, when you call mysql_close, most likely $con is undefined. Try declaring $con as a private variable in your class. Look here for more info on how to do that.