I know there is the answer to use asArray().
But what if I need model from relation and array at the same time?
In this example demoJson is without relations:
$demo = Demo::find()->with('bundles')->one();
// view
<?= var demoJson = json_encode($demo) ?> <!-- Using as array ERROR -->
<?= $demo->bundles[0]->someFunc() ?> <!-- Using model OK -->
In this example there is no someFunc() because a simple array used:
$demo = Demo::find()->with('bundles')->asArray()->one();
// view
<?= var demoJson = json_encode($demo) ?> <!-- Using as array OK -->
<?= $demo['bundles'][0]->someFunc() ?> <!-- Using model ERROR -->
So, how to get array from model with all its relations but without using asArray.
You might try:
$demo = Demo::find()->with('bundles')->limit(1)->one();
// view
<?= var demoJson = json_encode($demo->toArray()) ?>
<?= $demo->bundles[0]->someFunc() ?>
The Demo model could be this:
namespace app\models;
use yii\db\ActiveRecord;
Class Demo extends ActiveRecord
{
// ...
/**
* #return array
*/
public function fields()
{
$fields = parent::fields();
if ($this->isNewRecord) {
return $fields;
}
$fields['bundles'] = function() {
$bundles = [];
foreach ($this->bundles as $bundle) {
$bundles[] = $bundle->toArray();
}
return $bundles;
}
return $fields;
}
}
Related
I have a model with the following custom attributes topic_names, topic_details (string fields). I have also a model form with the custom attributes and custom rules. When I insert wrong data in the form fields, there is a model error, but it isn't displayed.
Model code:
......
public function rules()
{
return [
...
[['topics_names','topics_details'],'string'],
[['topics_names'],'checkCorrectAndSetTopics'],
];
}
public function checkCorrectAndSetTopics(){
if($this->topics_names AND $this->topics_details){
$topicsNamesArray = explode(',',$this->topics_names);
$topicsDetailsArray = explode(';',$this->topics_details);
if(sizeof($topicsNamesArray) !== sizeof($topicsDetailsArray)){
$this->addError('topics_names', \Yii::t('app', 'The topics names and details sets have different sizes'));
return FALSE;
}
}
return TRUE;
}
The problem is when the second rules is violeted, the form doesn't show any error, but there is. I checked it debugging the code below.
Form code:
..........
<?php
ActiveForm::$autoIdPrefix = createRandomId();//Function which creates a random id
$form = ActiveForm::begin(
['enableAjaxValidation' => true, "options"=> ["class"=>"extra-form"]]);
?>
<?= $form->errorSummary($model); ?>
<?= $form->field($model, 'topics_names')->textInput()
->label(\Yii::t('app', 'Topics Names'))?>
<?= $form->field($model, 'topics_details')->textarea(['rows' => 6])
->label(\Yii::t('app', 'Topics Details'))?>
........
Controller code:
public function actionAddExtraData($id){
if(!Yii::$app->request->isAjax){
throw new ForbiddenHttpException(\Yii::t('app','Cannot access this action directly.'));
}
$event = $this->findModel($id);
$extraData = ExtraData::find()
->andWhere(['event_id'=>$id])
->one();
if(!$extraData){
$extraData = new ExtraData();
$extraData->event_id = $id;
}else{
$extraData->prePerformForm();//Insert data on custom attributes
}
if(Yii::$app->request->isPost AND Yii::$app->request->isAjax AND Yii::$app->request->post("submitting") != TRUE
AND $extraData->load(Yii::$app->request->post())){
Yii::$app->response->format = Response::FORMAT_JSON;
$validation = ActiveForm::validate($extraData);
return $validation;
}
if ($extraData->load(Yii::$app->request->post()) && $extraData->save()) {
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ["success" => TRUE];
} else {
return $this->redirect(Yii::$app->request->referrer);
}
}
return $this->renderAjax('_event_extra_form',['model'=>$extraData,'event'=>$event]);
}
First thing, pretty sure you don't need to return true or false, you just need to add error. Second thing, in your example you name the attribute, you can actually get this when defining the function, so your function could look something like this
public function checkCorrectAndSetTopics($attribute, $model){
if($this->topics_names AND $this->topics_details){
$topicsNamesArray = explode(',',$this->topics_names);
$topicsDetailsArray = explode(';',$this->topics_details);
if(sizeof($topicsNamesArray) !== sizeof($topicsDetailsArray)){
$this->addError($attribute, \Yii::t('app', 'The topics names and details sets have different sizes'));
}
}
}
This question already has answers here:
Yii Framework 2.0 Uploading Files Error finfo_file(): failed to open stream: No such file or directory
(10 answers)
Closed 3 years ago.
I'm coding a view to upload a file, the user may choose between upload it to the filesystem or upload it to the filesystem and record the filename on a datatable.
At this moment, the first stage (upload it to the filesystem only) is working, regards to the second option, the file is uploaded to the directory but I can't add the filename info to the table; I am confuse with these 2 facts: 1) I am getting this error: finfo_file(/tmp/phpKB2h1A): failed to open stream: No such file or directory, 2) the var (tplfilename) is blank
This is the View:
<?php
use yii\widgets\ActiveForm;
?>
<?php if (Yii::$app->session->hasFlash('sminfo2')): ?>
<div class="alert alert-success">
<?= Yii::$app->session->getFlash('sminfo2');?>
</div>
<?php else: ?>
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->field($model, 'tplfilename')->fileInput()->label('Choose your Template',['class'=>'label-class']) ?>
<?= $form->field($model, 'destination')->radioList(array('1'=>'Filesystem','2'=>'Datatable'))->label('File store destination:') ?>
<button>Submit</button>
<?php ActiveForm::end() ?>
<?php endif; ?>
This is the controller's function:
public function actionUploadtpl(){
$model = new UploadtplForm();
if (Yii::$app->request->isPost) {
$model->destination = $_POST['UploadtplForm']['destination'];
$model->tplfilename = UploadedFile::getInstance($model, 'tplfilename');
if($model->destination == "1"){
if ($model->upload()) {
// file is uploaded successfully
Yii::$app->session->setFlash("sminfo2", "Template file uploaded successfully to Filesystem");
return $this->refresh();
//return;
}//end of if model upload
}//end of if model destination ==1
else{
if($model->upload()){
$model->save();
Yii::$app->session->setFlash("sminfo2", "Template file uploaded successfully to Datatable");
return $this->refresh();
}
}
}//end of isPost
return $this->render('uploadtpl', ['model' => $model]);
}//end of uploadtplform
And this is the model:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\web\UploadedFile;
use yii\db\ActiveRecord;
/**
* #property int $id
* #property string $tplfilename
**/
//class UploadtplForm extends Model
class UploadtplForm extends \yii\db\ActiveRecord
{
/**
* #var UploadedFile
*/
public $tplfilename; //upload filename
public $destination; //set by user on view: upload the file to filesystem or datatable
public static function tableName(){
return 'templates';
}
public function rules()
{
return [
[['tplfilename'], 'file', 'skipOnEmpty' => false, 'extensions' => 'php'],
];
}
public function upload()
{
/*
if ($this->validate()) {
$this->tplFile->saveAs('mail/views' . $this->tplFile->baseName . '.' . $this->tplFile->extension);
return true;
} else {
return false;
}
*/
//store using filesystem
$this->tplfilename->saveAs('../mail/views/' . $this->tplfilename->baseName . '.' . $this->tplfilename->extension);
return true;
}//end of upload function
public function attributeLabels()
{
return [
'id' => 'ID',
'tplfilename' => 'File name',
];
}
}
?>
I would like to have your comments about how to achieve to upload the file and record the filename on the data table, most of the issue is about this line: $model->save().
Here is my model like an example:
/*
* Upload image
* #params $url Url where to upload
* #return filename
*/
public function upload($url, $image_name)
{
try{
if ($this->validate()) {
$this->imageFile->saveAs($url . $this->imageFile->baseName . '.' . $this->imageFile->extension);
if($image_name){
FileHelper::unlink($url . $image_name);
}
return $this->imageFile->baseName . '.' . $this->imageFile->extension;
}
} catch (Exception $ex) {
throw new Exception("Error while upload file.");
return false;
}
Here is my controller:
/**
* Upload image for product.
* If update is successful, the browser will be redirected to the 'view' page.
* #param integer $id
* #return mixed
*/
protected function Upload(UploadedFile $object, $name = null)
{
$user_id = $this->getUser()->id;
$model = new UploadForm();
$model->imageFile = $object;
$path = Yii::getAlias('#frontend/uploads/user-files/user-') . $user_id;
//check if dirrectory exist
if ( ! is_dir($path)) {
FileHelper::createDirectory($path);
}
$url = $path . '/';
return $model->upload($url, $name);
}
and here i call my method from action and save the model:
$cart_item_option->option_type_value = $this->Upload(UploadedFile::getInstanceByName('image'), null);
$cart_item_option->save();
I have yii2 application using advanced template and database mySql, i already make function for import an excel file to one of the table,
I made the function in a controller named student that contains CRUD of students data.this is my code
public function actionImportExcel()
{
$inputFile = 'uploads/siswa_file.xlsx';
try{
$inputFileType = \PHPExcel_IOFactory::identify($inputFile);
$objReader = \PHPExcel_IOFactory::createReader($inputFileType);
$objPHPExcel = $objReader->load($inputFile);
} catch (Exception $e) {
die('Error');
}
$sheet = $objPHPExcel->getSheet(0);
$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();
for($row=1; $row <= $highestRow; $row++)
{
$rowData = $sheet->rangeToArray('A'.$row.':'.$highestColumn.$row,NULL,TRUE,FALSE);
if($row==1)
{
continue;
}
$siswa = new Siswa();
$siswa->nis = $rowData[0][0];
$siswa->nama_siswa = $rowData[0][1];
$siswa->jenis_kelamin = $rowData[0][2];
$siswa->ttl = $rowData[0][3];
$siswa->alamat = $rowData[0][4];
$siswa->telp = $rowData[0][5];
$siswa->agama = $rowData[0][6];
$siswa->nama_ortu = $rowData[0][7];
$siswa->telp_ortu = $rowData[0][8];
$siswa->pekerjaan_ortu = $rowData[0][9];
$siswa->tahun_masuk = $rowData[0][10];
$siswa->kelas = $rowData[0][11];
$siswa->save();
print_r($siswa->getErrors());
}
die('okay');
}
but i don't know how to make a button in a view to make this function work. i mean i want to make a button that when the user click the button and browse their excel file they can import that file and the data inside the excel can import to database
First you should upload the file
and then processing with your function
there are several parts of code you must produce ..
eg a view for the user to upload the file
View: #app/views/site/upload.php
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->errorSummary($model); ?>
<?= $form->field($model, 'imageFile')->fileInput() ?>
<button>Submit</button>
<?php ActiveForm::end() ?>
Controller: #app/controllers/SiteController.php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\models\UploadForm;
use yii\web\UploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->imageFile = UploadedFile::getInstance($model, 'imageFile');
if ($model->upload()) {
// file is uploaded successfully
return;
}
}
return $this->render('upload', ['model' => $model]);
}
}
Model: #app/models/UploadForm.php
namespace app\models;
use yii\base\Model;
use yii\web\UploadedFile;
class UploadForm extends Model
{
/**
* #var UploadedFile
*/
public $imageFile;
public function rules()
{
return [
[['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'],
];
}
public function upload()
{
if ($this->validate()) {
$this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension);
return true;
} else {
return false;
}
}
}
the code is from this doc
I need 2 dependent fields in framework Yii2 Example:
I want when i insert in "Numero de Personas"= 5 or other numeric value, automatically "Consumo de Agua"= 2 * "Numero de Personas" = 10.
I saw onchange yii2 but i don't know how use it, i think onchange yii2 is an alternative.
<?= $form->field($model, 'NUM_PERSONAS')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'AGUA_CONSUMO')->textInput(['maxlength' => true]) ?>
Update:
With 2 calculated fields example:
$("#consumo-form #{$aguaId},#consumo-form #{$personaId}").on("keyup", function (e) {
var persona = $("#consumo-form #{$personaId}").val();
var agua = $("#consumo-form #{$aguaId}").val();
$("#consumo-form #{$indConsumoAguaM}").val(persona*agua)
});
Here is the approach i would use:
in your view:
<?php
$personaId = Html::getInputId($model, 'NUM_PERSONAS');
$aguaId = Html::getInputId($model, 'AGUA_CONSUMO');
?>
<!-- Begin of form -->
<?= $form->field($model, 'NUM_PERSONAS')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'AGUA_CONSUMO')->textInput(['maxlength' => true]) ?>
<!-- End of form -->
<?php
$js = <<<JS
$("#my-form-id #{$personaId}").on("keyup", function (e) {
var persona = $(this).val();
$("#my-form-id #{$aguaId}").val(persona*2)
});
$("#my-form-id #{$aguaId}").on("keyup", function (e) {
var agua = $(this).val()%2 == 0 ? $(this).val() : $(this).val() - 1;
$("#my-form-id #{$personaId}").val(agua/2)
});
JS;
$this->registerJs($js);
With that, both fields will try to auto-complete after the other being changed. But, just to be safe, we need to add a validation on model's rules also.
In your Model:
public function rules()
{
return [
// Your rules
[['NUM_PERSONAS', 'AGUA_CONSUMO'], 'checkValues']
];
}
public function checkValues($attribute)
{
$persona = $this->NUM_PERSONAS;
$agua = $this->AGUA_CONSUMO%2 == 0 ? $this->AGUA_CONSUMO : $this->AGUA_CONSUMO - 1;
if ($persona != $agua/2) {
$this->addError($attribute, 'Error message');
}
}
I have a model with an attribute that holds a CSV string.
(The model is actually an ActiveRecord object but I guess this is not important. Correct me if I'm wrong.)
/**
* #property string $colors Can be something like "red" or "red,green,blue" or ""
*/
class Product extends Model {
}
And I have a form in which I'd like to display this attribute as a checkboxList so that the user can select the possible values with simple clicks instead of typing into a textInput.
Theoretically, it should look similar to this:
<?php $availableColors = ['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue']; ?>
<?php $form = ActiveForm::begin([]); ?>
<?= $form->field($model, 'colors')->checkboxList($availableColors) ?>
<?php ActiveForm::end(); ?>
This does obviously not work since the field colors would need to be an array. But in my model it is a string.
What would be a good way to achieve that? With JS or pseudo attributes? The colors attribute must not be changed since it is already used in other contexts that shouldn't be modified.
You can override beforeValidate method in your model, to implode your colors array into string. In your view you can use following:
<?= $form->field($model, 'colors')->checkboxList($availableColors,
[
'item'=> function ($index, $label, $name, $checked, $value) use ($model) {
$colors = explode(';', $model->colors);
$checked = in_array($value, $colors);
return Html::checkbox($name, $checked, [
'value' => $value,
'label' => $label,
]);
}
]) ?>
CSV is a file format used for moving tabular data between programs that natively operate on incompatible formats. Using it as a model attribute is not very elegant (to say it nicely). In my opinion you should have started out storing your colors in an array.
That being said you can achieve converting the array data from the dropdown list to CSV using the beforeValidate() function in your model:
public function beforeValidate() {
$this->colors = explode(';', $this->colors);
return parent::beforeValidate();
}
I think this is a PHP question, but anyway you can use PHP explode for build the array you need. See here for more details and then user the array inside the checkboxList
Now I solved it with an extra model for the form. This seems to me a proper solution.
/**
* #property string $colors Can be something like "red" or "red,green,blue" or ""
*/
class Product extends Model {
}
/**
* #property string[] $colorsAsArray
*/
class ProductForm extends Product {
public function rules() {
return array_merge(parent::rules(), [
['colorsAsArray', 'safe'] // just to make it possible to use load()
]);
}
public function getColorsAsArray() {
return explode(',', $this->colors);
}
public function setColorsAsArray($value) {
$this->colors = self::implode($value);
}
protected static function implode($value) {
if ($value == 'none-value') return '';
return implode(',', $value);
}
/* - - - - - - - - - - optional - - - - - - - - - - */
public function attributeLabels() {
$attributeLabels = parent::attributeLabels();
return array_merge($attributeLabels, [
'colorsAsArray' => $attributeLabels['colors'],
]);
}
}
With this I can use the form that way:
<?php $availableColors = ['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue']; ?>
<?php $form = ActiveForm::begin([]); ?>
<?= $form->field($model, 'colorsAsArray')
->checkboxList($availableColors, ['unselect' => 'none-value']) ?>
<?php ActiveForm::end(); ?>
Of course, now the controller has to use the inherited model class.
The solution deals also with the issue if no checkbox is selected. That is why 'none-value' is introduced.