In laravel 9.47 / botman 2.7.8 / laravel-translatable 6.3 app I make some singleton class for
working with translatable functionality when moving from one botman conversation to other with all locate functionality in class AppLocale.php.
When user opens page with botman wizard he needs to select language and next all conversation messages must be in this language.
Method setCurrentLocale is used for this and it is called once.
It works, but the problem is that refering method getInstance of AppLocale(when I read data from database and I need text fields in selected language)
at some poment I see that in method getInstance is called with null $instance and default english is set from getCurrentLocale method:
I check it in logging messsages.
<?php
namespace App\Library;
use App;
class AppLocale
{
private static $instance;
public static function setCurrentLocale(string $currentLocale, string $file, string $line)
{
app()->setLocale($currentLocale);
}
public static function getInstance(string $action, string $file, string $line)
{
\Log::info(varDump(self::$instance, ' -1 getInstances self::$instance::'));
if (self::$instance === null) {
\Log::info( ' CREATED AppLocale $action ::' . $action);
\Log::info( ' CREATED AppLocale $file::' . $file);
\Log::info( ' CREATED AppLocale $line::' . $line);
\Log::info(' -1 new CREATED AppLocale::');
self::$instance = new AppLocale();
}
\Log::info(varDump(self::$instance, ' -1 AFTER getInstances self::$instance::'));
return self::$instance;
}
public static function getCurrentLocale(string $action, string $file, string $line)
{
$currentLocale = app()->getLocale();
\Log::info(varDump($currentLocale, ' -1 getCurrentLocale $currentLocale::'));
if(empty($currentLocale)) {
\Log::info( ' SETTING $action getCurrentLocale::' . $action);
\Log::info( ' SETTING $file::' . $file);
\Log::info( ' SETTING $line::' . $line);
return self::APP_LOCALE_ENGLISH;
}
return $currentLocale;
}
I call getCurrentLocale from Repository Service :
<?php
namespace App\Library\Services;
use App\Library\AppLocale;
class DbRepository
{
public static function getQuizCategory(int $quizCategoryId): array
{
$quizCategory = QuizCategory::findOrFail($quizCategoryId);
$quizCategoryArray = $quizCategory->toArray();
\Log::info(varDump($quizCategoryArray, ' -1 getQuizCategory $quizCategoryArray::'));
$quizCategoryArray['locale_name'] = $quizCategory->getTranslation('name', AppLocale::getInstance('getQuizCategory', __FILE__, __LINE__)->getCurrentLocale('getQuizCategory', __FILE__, __LINE__));
\Log::info(varDump($quizCategoryArray['locale_name'], ' -15 $quizCategoryArray[locale_name::'));
return $quizCategoryArray;
}
public static function getQuizzesByQuizCategoryIds(array $quizCategoryIds, bool $active = null): array
{
$quizzes = Quiz::getByActive($active)
->getByQuizCategoryId($quizCategoryIds)
->get()
->map(function ($quizItem) {
$quizItem->locale_question = $quizItem->getTranslation('question', AppLocale::getInstance('getQuizzesByQuizCategoryIds', __FILE__, __LINE__)->getCurrentLocale('getQuizzesByQuizCategoryIds', __FILE__, __LINE__));
return $quizItem;
});
\Log::info(varDump($quizzes, ' -1 getQuizzesByQuizCategoryIds $quizzes::'));
return $quizzes->toArray();
}
public static function getQuizAnswersByQuizId($quizId): array {
$quizAnswers = QuizAnswer::getByQuizId($quizId)
->get()
->map(function ($quizAnswerItem) {
$quizAnswerItem->locale_text = $quizAnswerItem->getTranslation('text', AppLocale::getInstance('getItem', __FILE__, __LINE__)->getCurrentLocale('getQuizAnswersByQuizId', __FILE__, __LINE__));
return $quizAnswerItem;
});
return $quizAnswers->toArray();
}
public static function getQuizAnswer(int $quizAnswerId): array
{
$quizAnswer = QuizAnswer::findOrFail($quizAnswerId);
$quizAnswer->locale_text = $quizAnswer->getTranslation('text', AppLocale::getInstance('getQuizAnswer', __FILE__, __LINE__)->getCurrentLocale('getQuizAnswer', __FILE__, __LINE__));
return $quizAnswer->toArray();
}
Cheching logs I see that current language is lost after method getQuizAnswer calling (self::$instance is null) and do not see why?
Any ideas what is wrong at my side and how that can be fixed?
All quizz wizards within 1 conversation.
Thanks!
Related
I laravel 9 project with squizlabs/php_codesniffer my phpstorm 2021 shows error :
Expected parameter of type '\TValue', 'Vote' provided
in model when I use table name in scope condition :
class QuizQualityResult extends Model
{
protected $table = 'quiz_quality_results';
public function scopeGetByVoteCategories($query, $voteCategoryId= null)
{
// “new Vote” is marked as error
$voteTable = with(new Vote)->getTable();
if (!empty($voteCategoryId)) {
if ( is_array($voteCategoryId) ) {
$query->whereIn( $voteTable . '.vote_category_id', $voteCategoryId);
} else {
$query->where( $voteTable . ' . vote_category_id', $voteCategoryId);
}
}
return $query;
}
If there is a way to fix this error ? Or maybe to use better syntax here ?
Thanks!
There is no need for helper with() here
$voteTable = (new Vote())->getTable()
Ps: there is a feeling that your method does not work the way you intended. Perhaps you meant to do the following (I could be wrong):
public function scopeGetByVoteCategories($query, $voteCategoryId = null)
{
if (empty($voteCategoryId)) {
return $query;
}
return $query->whereHas('vote', static function ($query) use ($voteCategoryId) {
if (is_array($voteCategoryId)) {
return $query->whereIn('vote_category_id', $voteCategoryId);
}
return $query->where('vote_category_id', $voteCategoryId);
});
}
public function vote()
{
// your relationship
}
Yii2 framework. When I save multiple ActiveRecords in AFTER_INSERT_EVENT of another ActiveRecord, the values in the database is not updated fast enough, so old values are shown when redirect to viewing the data.
To be more specific: Standard XAMPP environment with PHP 7.2.9. I have made a trait to make it easy to have extra attributes with history in model (either existing attributes or new attributes). The trait is used on ActiveRecord.
Notice the sleep(5) in function TL_save. This handled the problem, but it is not the correct solution. How do I ensure all is updated before it is read again? I want to avoid use locks on the row as that would require alteration of a table before it can be used. Is there a way around it? Transactions - I have tried it but perhaps not correct as it had no effect. A reload of the view page also solves the problem, but again: not very classy :-)
Also: Should I share this code on GitHub? I have not done so before and are not quite sure if it would be of any value to others really.
trait TimelineTrait
{
private $timelineConfig;
public function timelineInit($config)
{
$std = [
'attributes' => [], // required
'_oldAttributes'=>[],
'datetime'=> date('Y-m-d H:i:s'),
'validationRule'=>'safe',
'table'=>$this->tableName(),
'onlyDirty'=>true, // using !=, not !==
'events'=>[
self::EVENT_AFTER_INSERT=>[$this, 'TL_EventAfterInsert'],
self::EVENT_AFTER_UPDATE=>[$this, 'TL_EventAfterUpdate'],
self::EVENT_AFTER_FIND=>[$this, 'TL_EventAfterFind'],
self::EVENT_AFTER_DELETE=>[$this, 'TL_EventAfterDelete'],
],
'TimelineClass'=>Timeline::class,
/*
Must have the following attributes
id integer primary key auto increment not null,
table varchar(64) not null,
table_id integer not null,
attribute varchar(64) not null,
datetime datetime not null
value text (can be null)
*/
];
$this->timelineConfig = array_replace_recursive($std, $config);
foreach($this->timelineConfig["events"]??[] as $trigger=>$handler)
$this->on($trigger, $handler);
}
public function __get($attr)
{
$cfg = &$this->timelineConfig;
if (in_array($attr, array_keys($cfg["attributes"])))
return $cfg["attributes"][$attr];
else
return parent::__get($attr);
}
public function __set($attr, $val)
{
$cfg = &$this->timelineConfig;
if (in_array($attr, array_keys($cfg["attributes"]))) {
$cfg["attributes"][$attr] = $val;
} else
parent::__set($attr, $val);
}
public function attributes()
{
return array_merge(parent::attributes(), $this->timelineConfig["attributes"]);
}
public function rules()
{
$temp = parent::rules();
$temp[] = [array_keys($this->timelineConfig["attributes"]), $this->timelineConfig["validationRule"]];
return $temp;
}
public function TL_EventAfterInsert($event)
{
$this->TL_save($event, true);
}
public function TL_EventAfterUpdate($event)
{
$this->TL_save($event, false);
}
private function TL_save($event, $insert)
{
$cfg = &$this->timelineConfig;
if ($cfg["onlyDirty"])
$cfg["_oldAttributes"] = $this->TL_attributesOnTime();
foreach($cfg["attributes"] as $attr=>$val) {
$a = [
'table'=>$cfg["table"],
'table_id'=>$this->id,
'attribute'=>$attr,
'datetime'=>$cfg["datetime"],
];
if ($insert)
$model=null;
else
$model = Timeline::find()->where($a)->one();
$isNew = empty($model); // this exact attribute does not exist on timeline already
if ($isNew)
$model = new $cfg["TimelineClass"]($a);
$model->value = $val;
if (!$cfg["onlyDirty"]
|| $cfg["onlyDirty"] && $model->value!=($cfg["_oldAttributes"][$attr]??\uniqid('force_true'))) {
$ok = $model->save();
if (!$ok) $this->addErrors($attr, $model->getErrorSummary());
}
}
sleep(5);
}
public function TL_EventAfterFind($event)
{
$cfg = &$this->timelineConfig;
$data = $this->TL_attributesOnTime();
foreach($data as $attr=>$val)
$cfg["attributes"][$attr] = $val;
$cfg["_oldAttributes"] = $cfg["attributes"];
}
private function TL_attributesOnTime()
{
$cfg = &$this->timelineConfig;
$timelineTable = $cfg["TimelineClass"]::tableName();
$sql = "SELECT t1.* FROM $timelineTable AS t1
LEFT JOIN (SELECT * FROM $timelineTable WHERE `table`=:table AND table_id=:table_id AND datetime<=:datetime) AS t2
ON (t1.table=t2.table and t1.table_id=t2.table_id and t1.datetime<t2.datetime AND t1.attribute=t2.attribute)
WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
";
$params = [
'table'=>$cfg["table"],
'table_id'=>$this->id,
':datetime'=>$cfg["datetime"],
];
$data = \Yii::$app->db->createCommand($sql,$params)->queryAll();
$data = ArrayHelper::map($data,'attribute','value');
return $data;
}
public function TL_EventAFterDelete($event)
{
$cfg = &$this->timelineConfig;
$cfg["TimelineClass"]::deleteAll([
'table'=>$cfg["table"],
'table_id'=>$event->sender->id
]);
}
}
Example of it's use:
<?php
namespace app\models;
class KeyTime extends Key
{
use \app\behaviors\TimelineTrait;
public function init()
{
parent::init();
$this->timelineInit([
'attributes'=>[
// default values for attributes
'keyid'=>'historic id', // this is existing attribute in Key model
'label'=>'mylabel', // label and color does not exist in Key model
'color'=>'red',
],
]);
}
}
The actionUpdate
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', [
'model' => $model,
]);
}
After many "flashes" with microtime(true) on, I found the reason it worked sometimes with sleep(1).
The answer is in TL_attributesOnTime. the last line in $sql was
WHERE t2.id IS NULL AND t1.datetime<:datetime AND t1.table=:table AND t1.table_id=:table_id
…but it should be…
WHERE t2.id IS NULL AND t1.datetime<=:datetime AND t1.table=:table AND t1.table_id=:table_id
Notice the < is changed to <= Otherwise when the record was saved in the same second as it was populated it would not be included.
Hope it can help somebody else.
I am using method post to create new data in xml file but The function c_element cannot be used in the function store
$DeTai = c_element('DeTai', $root);
This is my current code:
public function c_element($e_name, $parent)
{
global $xml;
$node = $xml->createElement($e_name);
$parent->appendChild($node);
return $node;
}
public function c_value($value, $parent)
{
global $xml;
$value = $xml->createTextNode($value);
$parent->appendChild($value);
return $value;
}
public function store(Request $request)
{
$xml = new DOMDocument("1.0","UTF-8");
$xml->load('../xml/QuanLyDoAnTotNghiep.xml');
if ($request->isMethod('post'))
{
$madt= $request->madt;
$noidungdetai = $request->noidungdetai;
$root=$xml->getElementsByTagName("QuanLyDoAnTotNghiep")->item(0);
$DeTai = c_element("DeTai", $root); //error in here
$s_madt = c_element('MaDT', $DeTai);
c_value("$madt", $s_madt);
$s_noidungdetai = c_element('NoiDungDeTai', $DeTai);
c_value("$noidungdetai", $s_noidungdetai);
$xml->formatOutput=true;
$xml->save('../xml/QuanLyDoAnTotNghiep.xml');
echo "Thêm mới thành công!!!";
}
}
use this keyword to call one method in different method of same class
$DeTai = $this->c_element('DeTai', $root);
to know more about it please visit this
Thanks..
In my current configuration, a user's email is stored on a remote server that I need to hit with a curl quest.
Luckily, I only need the email once a day when a certain process runs. However, when that process does run it will need to reference the email multiple times.
This is the current accessor I have set up for email. The problem is the curl request is being called every time I use $user->email. What's the best way to avoid this?
in UserModel:
public function getEmailAttribute(){
$curl = new Curl;
$responseJson = $curl->post('https://www.dailycred.com/admin/api/user.json',array(
'client_id'=>getenv('dailycredId')
,'client_secret'=>getenv('dailycredSecret')
,'user_id'=>$this->id
));
$response = json_decode($responseJson);
return $response->email;
}
private $cached_email = false;
public function getEmailAttribute(){
if ($this->cached_email){
// if set return cached value
return $this->cached_email;
}
// get the email
$curl = new Curl;
$responseJson = $curl->post('https://www.dailycred.com/admin/api/user.json',array(
'client_id'=>getenv('dailycredId')
,'client_secret'=>getenv('dailycredSecret')
,'user_id'=>$this->id
));
$response = json_decode($responseJson);
// cache the value
$this->cached_email = $response->email;
// and return
return $this->cached_email;
}
Depending on your use case make adjustments (ie. session, cache , static property...).
Extend a the Eloquent Model class
namespace App\Models\Utils;
use Illuminate\Database\Eloquent\Model as OldModel;
class MyModel extends OldModel
{
private $cachedAttributes = [];
public function getCachedAttribute(string $key, Callable $callable)
{
if (!array_key_exists($key, $this->cachedAttributes)) {
$this->setCachedAttribute($key, call_user_func($callable));
}
return $this->cachedAttributes[$key];
}
public function setCachedAttribute(string $key, $value)
{
return $this->cachedAttributes[$key] = $value;
}
public function refresh()
{
unset($this->cachedAttributes);
return parent::refresh();
}
}
make your class
class ElementWithEmail extends MyModel
{
const ATTRIBUTE_KEY_FOR_EMAIL = 'Email';
public function getEmailAttribute(){
$key = self::ATTRIBUTE_KEY_FOR_EMAIL;
$callable = [$this, 'getEmail'];
return $this->getCachedAttribute($key, $callable);
}
protected function getEmail()
{
$curl = new Curl;
$responseJson = $curl->post('https://www.dailycred.com/admin/api/user.json',array(
'client_id'=>getenv('dailycredId')
,'client_secret'=>getenv('dailycredSecret')
,'user_id'=>$this->id
));
$response = json_decode($responseJson);
return $response->email;
}
}
Call it from your code
$element = new ElementWithEmail();
echo $element->email;
Can I write chainable functions in CodeIgniter?
So if I have functions like these :
function generate_error(){
return $data['result'] = array('code'=> '0',
'message'=> 'error brother');
}
function display_error(){
$a= '<pre>';
$a.= print_r($data);
$a.= '</pre>';
return $a;
}
I want to call those by chaining them :
echo $this->generate_error()->display_error();
The reason why I want to seperate these functions are because display_error() is only useful for development, so when it comes to production, I can just remove the display_error() or something like that.
Thanks!
To write chainable functions they musy be part of a class, from the function you then return a reference to the current class (usually $this).
If you return anything other than a reference to a class it will fail.
It is also possible to return a reference to another class (e.g. when you use the code igniter active records class get() function it returns a reference to the DBresult class)
class example {
private $first = 0;
private $second = 0;
public function first($first = null){
$this->first = $first;
return $this;
}
public function second($second = null){
$this->second = $second;
return $this;
}
public function add(){
return $this->first + $this->second;
}
}
$example = new example();
//echo's 15
echo $example->first(5)->second(10)->add();
//will FAIL
echo $example->first(5)->add()->second(10);
you should return $this in your function to make chain-able functions in php oop
public function example()
{
// your function content
return $this;
}