I would like to create a yii2 model without a database. Instead, the data is generated dynamically and not stored, just displayed to the user as a json. Basically, I would just like a get a simple, basic example of a non-database Model working but I can't find any documentation on it.
So how would I write a model without a database? I have extended \yii\base\Model but I get the following error message:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<name>PHP Fatal Error</name>
<message>Call to undefined method my\app\models\Test::find()</message>
<code>1</code>
<type>yii\base\ErrorException</type>
<file>/my/app/vendor/yiisoft/yii2/rest/IndexAction.php</file>
<line>61</line>
<stack-trace>
<item>#0 [internal function]: yii\base\ErrorHandler->handleFatalError()</item>
<item>#1 {main}</item>
</stack-trace>
</response>
To implement find(), I must return a database query object.
My Model is completely blank, I'm just looking for a simple example to understand the principal.
<?php
namespace my\app\models;
class Test extends \yii\base\Model{
}
This is a Model from one of my projects. This Model is not connected with any database.
<?php
/**
* Created by PhpStorm.
* User: Abhimanyu
* Date: 18-02-2015
* Time: 22:07
*/
namespace backend\models;
use yii\base\Model;
class BasicSettingForm extends Model
{
public $appName;
public $appBackendTheme;
public $appFrontendTheme;
public $cacheClass;
public $appTour;
public function rules()
{
return [
// Application Name
['appName', 'required'],
['appName', 'string', 'max' => 150],
// Application Backend Theme
['appBackendTheme', 'required'],
// Application Frontend Theme
['appFrontendTheme', 'required'],
// Cache Class
['cacheClass', 'required'],
['cacheClass', 'string', 'max' => 128],
// Application Tour
['appTour', 'boolean']
];
}
public function attributeLabels()
{
return [
'appName' => 'Application Name',
'appFrontendTheme' => 'Frontend Theme',
'appBackendTheme' => 'Backend Theme',
'cacheClass' => 'Cache Class',
'appTour' => 'Show introduction tour for new users'
];
}
}
Use this Model like any other.
e.g. view.php:
<?php
/**
* Created by PhpStorm.
* User: Abhimanyu
* Date: 18-02-2015
* Time: 16:47
*/
use abhimanyu\installer\helpers\enums\Configuration as Enum;
use yii\caching\DbCache;
use yii\caching\FileCache;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/** #var $this \yii\web\View */
/** #var $model \backend\models\BasicSettingForm */
/** #var $themes */
$this->title = 'Basic Settings - ' . Yii::$app->name;
?>
<div class="panel panel-default">
<div class="panel-heading">Basic Settings</div>
<div class="panel-body">
<?= $this->render('/alert') ?>
<?php $form = ActiveForm::begin([
'id' => 'basic-setting-form',
'enableAjaxValidation' => FALSE,
]); ?>
<h4>Application Settings</h4>
<div class="form-group">
<?= $form->field($model, 'appName')->textInput([
'value' => Yii::$app->config->get(
Enum::APP_NAME, 'Starter Kit'),
'autofocus' => TRUE,
'autocomplete' => 'off'
])
?>
</div>
<hr/>
<h4>Theme Settings</h4>
<div class="form-group">
<?= $form->field($model, 'appBackendTheme')->dropDownList($themes, [
'class' => 'form-control',
'options' => [
Yii::$app->config->get(Enum::APP_BACKEND_THEME, 'yeti') => ['selected ' => TRUE]
]
]) ?>
</div>
<div class="form-group">
<?= $form->field($model, 'appFrontendTheme')->dropDownList($themes, [
'class' => 'form-control',
'options' => [
Yii::$app->config->get(Enum::APP_FRONTEND_THEME, 'readable') => ['selected ' => TRUE]
]
]) ?>
</div>
<hr/>
<h4>Cache Setting</h4>
<div class="form-group">
<?= $form->field($model, 'cacheClass')->dropDownList(
[
FileCache::className() => 'File Cache',
DbCache::className() => 'Db Cache'
],
[
'class' => 'form-control',
'options' => [
Yii::$app->config->get(Enum::CACHE_CLASS, FileCache::className()) => ['selected ' => TRUE]
]
]) ?>
</div>
<hr/>
<h4>Introduction Tour</h4>
<div class="form-group">
<div class="checkbox">
<?= $form->field($model, 'appTour')->checkbox() ?>
</div>
</div>
<?= Html::submitButton('Save', ['class' => 'btn btn-primary']) ?>
<?php $form::end(); ?>
</div>
The reason for using a model is to perform some kind of logic on data that you are getting from somewhere. A model can be used to perform data validation, return properties of the model and their labels, and allows for massive assignment. If you don't need these features for your data model, then don't use a model!
If you are not requiring data validation (i.e. you are not changing any data via forms or other external source), and you are not requiring access to behaviors or events, then you probably need to just use yii\base\Object. This will give you access to getters and setters for properties of the object, which seems to be all you need.
So your class ends up looking like this. I've included getting data from another model, in case that's what you want to do;
<?php
namespace my\app\models;
use \path\to\some\other\model\to\use\OtherModels;
class Test extends \yii\base\Object{
public function getProperty1(){
return "Whatever you want property1 to be";
}
public function getProperty2(){
return "Whatever you want property2 to be";
}
public function getOtherModels(){
return OtherModels::findAll();
}
}
You would then simply use it like this;
$test = new Test;
echo $test->property1;
foreach ($test->otherModels as $otherModel){
\\Do something
}
The function you have tried to use, find(),is only relevant to a database and so won't be available to your class if you've extended yii\base\Model, yii\base\Component or yii\base\Object, unless you want to define such a function yourself.
A lightweight way to create models without database backend is to use a DynamicModel:
DynamicModel is a model class primarily used to support ad hoc data validation.
Just write in your controller:
$model = new DynamicModel(compact('name', 'email'));
$model->addRule(['name', 'email'], 'string', ['max' => 128])
->addRule('email', 'email')
->validate();
and then pass $model to your view.
A full example can be found in http://www.yiiframework.com/wiki/759/create-form-with-dynamicmodel/.
This is perfect for user input for calling APIs, creating forms on the fly, etc.
As has been pointed out in the comments and other answers, your model needs to extend \yii\db\BaseActiveRecord. That said you can store your json as a nosql database such as MongoDb or in a key-value cache such as Redis instead. Both have Yii implementions: \yii\mongodb\ActiveRecord and \yii\redis\ActiveRecord
Related
I'm trying to create an 'auto-placeholder' element using Yii2 and since I couldn't find an actual answer to my question, I thought I'd try it here.
For example, I have this field:
<?= $form->field($model, 'username',
[
'template'=>'{input}{label}{error}'
])
->textInput(['placeHolder'=>'{name}')
->label(false);
?>
However this case would obviously render "name" in the placeholder attribute.
But I would like to generate the placeholder attribute automatically depending on the model's variable I'm using, causing it to render the following:
<input type="text" id="loginform-username" class="form-control" name="LoginForm[username]" placeholder="Username">
Is there a known way of accessing and inserting the form->field's attribute and displaying it inside its own element?
Yes we can do by defining the attributes labels in model file like below.
public function attributeLabels() {
return [
'username' => 'Username',
];
}
then you can fetch the label automatically based on fields like following.
<?= $form->field($model, 'username',
[
'template'=>'{input}{label}{error}'
])
->textInput(['placeholder' => $model->getAttributeLabel('username'))
->label(false);
?>
I hope this will sort it out your problem.
If you are in for some extra hassle you can extend ActiveField class for that.
class MyActiveField extends \yii\widgets\ActiveField
{
public function textInput($options = [])
{
if (empty($options['placeholder'])) {
$options['placeholder'] = $this->model->getAttributeLabel($this->attribute);
}
return parent::textInput($options);
}
}
Now just need to use your class instead of default one.
You can do every time in view:
<?php $form = ActiveForm::begin([
'fieldClass' => 'fully\qualified\name\of\MyActiveField'
]); ?>
Or extend ActiveForm:
class MyActiveForm extends \yii\widgets\ActiveForm
{
$fieldClass = 'fully\qualified\name\of\MyActiveField';
}
and use it instead of default ActiveForm widget:
<?php $form = MyActiveForm::begin(); ?>
Now you can use <?= $form->field($model, 'attribute')->textInput() ?> (or just <?= $form->field($model, 'attribute') ?> since textInput is default) and placeholder should be there.
I'm reviewing a basic contact form which is not associated with any model. I would like some advice on the best way to leverage Cake's automatic view rendering of field errors for this situation.
Controller
Performs validation through a custom Validator.
public function index()
{
if ($this->request->is('post')) {
// Validate the form
$validator = new EnquiryValidator();
$data = $this->request->data();
$errors = $validator->errors($data);
if (empty($errors)) {
// Send email, etc.
// ...
// Refresh page on success
}
// Show error
$this->Flash->error('Unable to send email');
}
}
View
<?= $this->Form->create(); ?>
<?= $this->Form->input('name', [
'autofocus' => 'autofocus',
'placeholder' => 'Your name',
'required'
]);
?>
<?= $this->Form->input('email', [
'placeholder' => 'Your email address',
'required'
]);
?>
<?= $this->Form->input('subject', [
'placeholder' => 'What would you like to discuss?',
'required'
]);
?>
<?= $this->Form->input('message', [
'label' => 'Query',
'placeholder' => 'How can we help?',
'cols' => '30',
'rows' => '10',
'required'
]);
?>
<div class="text-right">
<?= $this->Form->button('Send'); ?>
</div>
<?= $this->Form->end(); ?>
Currently the form will not show any errors next to the input fields. I assume it's because there is no entity associated with the form or something like that, but I'm not sure.
What is the best solution? Can the validation be performed in a better way to automatically provide field errors in the view?
Modelless forms
Use a modelless form. It can be used to validate data and perform actions, similar to tables and entities, and the form helper supports it just like entities, ie, you simply pass the modelless form instance to the FormHelper::create() call.
Here's the example from the docs, modified a little to match your case:
src/Form/EnquiryForm.php
namespace App\Form;
use App\...\EnquiryValidator;
use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;
class EnquiryForm extends Form
{
protected function _buildSchema(Schema $schema)
{
return $schema
->addField('name', 'string')
->addField('email', ['type' => 'string'])
->addField('subject', ['type' => 'string'])
->addField('message', ['type' => 'text']);
}
protected function _buildValidator(Validator $validator)
{
return new EnquiryValidator();
}
protected function _execute(array $data)
{
// Send email, etc.
return true;
}
}
in your controller
use App\Form\EnquiryForm;
// ...
public function index()
{
$enquiry = new EnquiryForm();
if ($this->request->is('post')) {
if ($enquiry->execute($this->request->data)) {
$this->Flash->success('Everything is fine.');
// ...
} else {
$this->Flash->error('Unable to send email.');
}
}
$this->set('enquiry', $enquiry);
}
in your view template
<?= $this->Form->create($enquiry); ?>
See also
Cookbook > Modelless Forms
LoginForm:
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// username should be a number and of 8 digits
[['username'], 'number', 'message'=>'{attribute} must be a number'],
[['username'], 'string', 'length' => 8],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* #param string $attribute the attribute currently being validated
* #param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
I have set up 2 rules for the same field as you can see above:
[['username'], 'number', 'message'=>'{attribute} must be a number'],
[['username'], 'string', 'length' => 8],
I would like the form to display different error messages for the following 3 scenarios situations:
The provided value is neither a number, nor 8 characters (digits).
The provided value is a number, but is not of 8 characters (digits).
The provided value is not a number, but is of 8 characters (digits).
My question is 2 fold:
A. Is there a way to combine these rules in any standard, Yii2 way.
B. In my previous question I have tried to set up a custom validator (the obvious way to solve this), but it was very simply ignored. The only way I could make it validate was if I added the username field to a scenario. However, once I added password too, it was again ignored. Any reason's for this that you can think of? EDIT: skipOnError = false changed nothing at all in this behaviour.
So please, when you answer, make sure you test it preferably in yii2/advanced; I barely touched the default set up, so it should be easy to test.
EDIT: for clarity, I would like to only allow numbers that are of 8 characters (digits), so they can potentially have a leading 0, eg. 00000001, or 00000000 for that matter. This is why it has to be a numeric string.
The best way to combine rules and display custom error messages for different situations is to create a custom validator. Now if you want that to work on client-side too (it was one of my problems detailed in question B above, thanks to #Beowulfenator for the lead on this), you have to create an actual custom validator class extended from the yii2 native validator class.
Here is an example:
CustomValidator.php
<?php
namespace app\components\validators;
use Yii;
use yii\validators\Validator;
class CustomValidator extends Validator
{
public function init() {
parent::init();
}
public function validateAttribute($model, $attribute) {
$model->addError($attribute, $attribute.' message');
}
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
messages.push('$attribute message');
JS;
}
}
LoginForm.php
<?php
namespace common\models;
use Yii;
use yii\base\Model;
use app\components\validators\CustomValidator;
/**
* Login form
*/
class LoginForm extends Model
{
public $username;
public $password;
public $custom;
private $_user;
/**
* #inheritdoc
*/
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// username should be a number and of 8 digits
[['username'], 'number', 'message'=>'{attribute} must be a number'],
[['username'], 'string', 'length' => 8],
// password is validated by validatePassword()
['password', 'validatePassword'],
['custom', CustomValidator::className()],
];
}
// ...
login.php
<?php
/* #var $this yii\web\View */
/* #var $form yii\bootstrap\ActiveForm */
/* #var $model \common\models\LoginForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
$this->title = 'Login';
?>
<div class="site-login text-center">
<h1><?php echo Yii::$app->name; ?></h1>
<?php $form = ActiveForm::begin([
'id' => 'login-form',
'fieldConfig' => ['template' => "{label}\n{input}"],
'enableClientValidation' => true,
'validateOnSubmit' => true,
]); ?>
<?= $form->errorSummary($model, ['header'=>'']) ?>
<div class="row">
<div class="col-lg-4 col-lg-offset-4">
<div class="col-lg-10 col-lg-offset-1">
<div style="margin-top:40px">
<?= $form->field($model, 'username') ?>
</div>
<div>
<?= $form->field($model, 'password')->passwordInput() ?>
</div>
<div>
<?= $form->field($model, 'custom') ?>
</div>
<div class="form-group" style="margin-top:40px">
<?= Html::submitButton('Login', ['class' => 'btn btn-default', 'name' => 'login-button']) ?>
</div>
</div>
</div>
</div>
<?php ActiveForm::end(); ?>
</div>
Finally you need this :
the value is required
the value must be a string of 8 chars
the value must contains only digits
So you should simply try :
['username', 'required'],
['username', 'string', 'min' => 8, 'max' => 8],
['username', 'match', 'pattern' => '/^[0-9]{8}$/', 'message'=>'{attribute} must be a number'],
Yii2 ignores your validation rules may because you duplicated not only attribue but also types.
With number validation, i think you should use min/max option to validate number length.
For this case:
'min'=>10000000,'max'=>99999999
I am trying to get to figure out the proper way of handling a form receiving relational data in Yii2. I haven't been able to find any good examples of this. I have 2 models Sets and SetsIntensity, every Set may have one SetsIntensity associated with it. I'm am trying to make a form where you can input both at the same time. I'm not sure how to handle getting the input for a particular field 'intensity' in SetsIntensity.
Where
$model = new \app\models\Sets();
If I put it in the field like this client validation won't work and the attribute name is ambiguous and saving becomes difficult
<?= $form->field($model, 'lift_id_fk') ?>
<?= $form->field($model, 'reps') ?>
<?= $form->field($model, 'sets') ?>
<?= $form->field($model, 'type') ?>
<?= $form->field($model, 'setsintensity') ?>
I would like to do something like this but I get an error if I do
<?= $form->field($model, 'setsintensity.intensity') ?>
Exception (Unknown Property) 'yii\base\UnknownPropertyException' with message 'Getting unknown property: app\models\Sets::setsintensity.intensity'
I could do make another object in the controller $setsintensity = new Setsintensity(); but I feel this is a cumbersome solution and probably not good practice especially for handling multiple relations
<?= $form->field($setsintensity, 'intensity') ?>
relevant code from SetsModel
class Sets extends \yii\db\ActiveRecord
{
public function scenarios() {
$scenarios = parent::scenarios();
$scenarios['program'] = ['lift_id_fk', 'reps', 'sets', 'type', 'intensity'];
return $scenarios;
}
public function rules()
{
return [
[['lift_id_fk'], 'required'],
[['lift_id_fk', 'reps', 'sets','setsintensity'], 'integer'],
[['type'], 'string', 'max' => 1],
['intensity', 'safe', 'on'=>'program']
];
}
public function getSetsintensity()
{
return $this->hasOne(Setsintensity::className(), ['sets_id_fk' => 'sets_id_pk']);
}
SetsIntensity Model
class Setsintensity extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'setsintensity';
}
public function rules()
{
return [
[['sets_id_fk', 'intensity', 'ref_set'], 'required'],
[['sets_id_fk', 'intensity', 'ref_set'], 'integer']
];
}
public function getSetsIdFk()
{
return $this->hasOne(Sets::className(), ['sets_id_pk' => 'sets_id_fk']);
}
}
I was also thinking maybe I could put in a hasOne() relation for the specific attribute 'intensity' in 'Sets'
You should simply try this :
<?= $form->field($model->setsintensity, 'intensity') ?>
EDIT : And because "every Set may have one SetsIntensity", you should check this relation before displaying form, e.g. :
if ($model->setsintensity===null)
{
$setsintensity = new SetsIntensity;
$model->link('setsintensity', setsintensity);
}
PS: link method requires that the primary key value is not null.
I am trying to insert record using Cakephp.My model name is something like User.php.
And My working controller name is SignupsController.I want to insert record using this two but I cant.I am give my some codes below :
View :
<?php echo $this->Form->create('Signups',array('action' => 'registration'));?>
<div class="row-fluid">
<div class="span5">
<label class="">*First Name</label>
<?php echo $this->Form->input('first_name', array('type' => 'text','label' => false, 'class' => 'input-xlarge validate[required]', 'div' => false)); ?>
</div>
<div class="span5">
<label class="">*Last Name</label>
<?php echo $this->Form->input('last_name', array('type' => 'text', 'label' => false, 'class' => 'input-xlarge validate[required]', 'div' => false)); ?>
</div>
</div>
<?php echo $this->Form->end(); ?>
My controller code is given below :
class SignupsController extends AppController {
var $name = 'Signups';
var $uses=array("User");
public function registration()
{
$this->layout="reserved";
$this->Club->create();
if (isset($_POST['registration'])) {
echo "This is";
echo "<pre>";print_r($this->request->data);echo"</pre>";
$this->User->save($this->request->data);
//$this->Session->setFlash(__('Promoter registration has been done successfully'));
//$this->redirect('registration');
//$this->redirect(array('action' => 'registration'));
}
}
}
My model name is different which's name is User.php
I want to insert the record using this above code.Any idea how to insert?
you can do this by loading the users model in current controller just write the following line
$this->loadModel('Name of the Model').
then
$this->nameofmodel->save()
As you are unable to understand see this
Controller::loadModel(string $modelClass, mixed $id)¶
The loadModel() function comes handy when you need to use a model which is not the controller’s default model or its associated model:
$this->loadModel('Article');
$recentArticles = $this->Article->find(
'all',
array('limit' => 5, 'order' => 'Article.created DESC')
);
$this->loadModel('User', 2);
$user = $this->User->read();
Above pasted code is taken from CookBook of Cakephp, if you still do not understand just read it it has complete detailed explanation you can also see this to understand
you can use it with $uses variable in SignupController
class SingupController extends AppController
{
public $uses = array('User');
//rest of stuff
}
Or, if you want, you can load it on-demand inside a method:
$this->loadModel('User'); //now model is loaded inside controller and used via $this->User
EDIT: Your data array has to include the name of the model you're saving. So, replace:
$this->Form->create('Signups',array('action' => 'registration')
with:
$this->Form->create('User',array('url' => array('controller' => 'signups', 'action' => 'registration'));