I need to change the queried table from a Laravel-model right before the query begins.
Normally you make a query like this:
ExampleModel::where('column_name', =, 'value')->get();
For one case I want to use a view-table which contains information from multiple tables combined in one view.
Therefore I need to switch the table of ExampleModel only for this one situation, e.g.:
ExampleModel::table('my_view')->where(...)->get();
It is not an option to use DB::table('my_view')->where(...)->get() because of several local scopes which need to be applied on ExampleModel.
As I could see, there are the following options:
somehow change the models table-name on the fly (as shown above)
Create a new Model used only in this use case which has the view defined as model-table
write all my scopes into a chained DB-command
Are there any other options?
The laravel way to Handle this would be to have a dedicated model for your view, with Scopes applying to each of the relevant models :
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class AgeScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('age', '>', 200);
}
}
Then in your ExampleModel AND you MyView Model
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AgeScope);
}
So that when you want to edit a scope it will be reflected on each of your queries
BUT
You will allways know if your model is from your view_table or from you example_model table.
If you need to have some accessor or function used by both, I recomand you to put them in a Trait and use them on both Models
trait ExampleModelTrait
{
getTestAttribute(){
return strtolower($this->column_name);
}
}
and then
use ExampleModelTrait;
you can do this stuff by passing table name to setTable() method for example :
$user = new Users();
$user->setTable('customers');
$user->where(id,1)
...
Related
I have a File entity for handle files upload in other entities (news/blog/etc).
I point to it with a OneToOne relation and it works fine. But I would change the upload dir, for each relation entity :
upload/news
upload/blog
The upload path is set in my file entity so i dont know how to automaticaly update the path foreach relations...
do you have an idea on how to do it ?
Thanks
Of course you can do it.
On your file entity side, you can add a uploadDir attribute, and create a setter like this :
private $uploadDir;
public function setUploadDir($uploadDir)
{
if (!$this->uploadDir) {
$this->uploadDir = $uploadDir;
}
}
your comment suggest you use Symfony with Doctrine right ?
So you can edit the classical getUploadDir() method like this:
protected function getUploadDir()
{
return 'uploads/' . $this->uploadDir;
}
In the 'parent' entity you have to update this attribute (when it is created) before persist or update.
(I personally use life cycle callbacks but you can do it manually in your controller)
use Doctrine\ORM\Mapping as ORM;
/**
* News
*
* #ORM\Table(name="News")
* #ORM\HasLifecycleCallbacks
*/
class News
{
//....
/**
* #ORM\OneToOne(targetEntity="File",cascade={"persist","remove"})
*/
private $file;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
function setUploadDir()
{
$this->getFile()->setUploadDir('news');
}
// ....
You can also add a constant to make the code cleaner...
I hope it'll help you
Right now I have:
$products = Product::findAll([1,2,3,4]);
foreach ($products as $product){
$text = $product->part->type->texts;
}
This returns the related records from Texts table.
But I need to have only 1 record from it, and to do that I need to have one more condition in the last join type->texts, which is not defined in the model. It's dynamic session variable.
Is there any way to do this?
If you want modify the last relation query to have additional condition and return one record instead of many, simply change last relation call like so:
$text = $product->part->type->getTexts()->andWhere(...)->one();
Direct relation method call returns yii\db\ActiveQuery instance so you can modify conditions how you want.
If you want to use modified relation in more than just one place, create separate method for that:
/**
* #return yii\db\ActiveQuery
*/
public function getDynamicText()
{
// Insert link from texts relation similar as in hasMany() and additional condition in andWhere()
return $this->hasOne(...)->andWhere(...);
}
And then use it:
$text = $product->part->type->dynamicText;
In this case, scopes would be a handy solution, especially if you're going to use complicated conditions.
1. Start by creating a model that extends ActiveQuery with a method that will be used to add conditions to your query, for example active = 1:
namespace app\models;
use yii\db\ActiveQuery;
class TextQuery extends ActiveQuery
{
public function active($state = 1)
{
$this->andWhere(['active' => $state]); // or any other condition
return $this;
}
}
2. Override the find() method in your Text model:
public static function find()
{
return new \app\models\TextQuery(get_called_class());
}
3. Add a method in your Type model that retrieves your relational data via the newly made scope:
public function getActiveText()
{
return $this->hasMany(Text::className(), ['type_id' => 'id'])->active();
}
Finally, use it as follows:
$text = $product->part->type->activeText;
The docs are pretty clear on this, check 'em out.
I get some data from an API inside my Symfony 2 application using Guzzle. Those data are properly mapped into my model. The model is a Contact object with some properties. One of those property is an array of ContactMethod done like this
/**
* #SerializedName("methods")
* #Type("array<My\Bundle\Model\ContactMethod>")
*/
private $methods;
The ContactMethod object has just two properties: type and value
class ContactMethod {
/**
* #SerializedName("type")
* #Type("string")
*/
private $type;
/**
* #SerializedName("value")
* #Type("string")
*/
private $value;
... setters and getters ...
I've created a ContactType with its buildForm function in order to display all the property of Contact in a form. But when it comes to display the methods property I would like to have a TextInput for each of the ContactMethod and that input should take the type as label and the value as value.
class ContactType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options = array())
{
$builder
->add(...all the other simple fields...)
->add(...what shall I add here ? )
I also need this to work in the opposite way: when all the text inputs are filled and submitted, the fields has to be packed together into an array of ContactMethod so I can reserialize and send back the data to the API.
I've tried to play around with custom form types and also Data Transformers but I could not find a solution.
Your Contact object is mapped onto one ContactType, so just add a property methods and its type (e.g.) "method_list" if it's declared as a service or just new MethodListType().
The list is dynamic, so you have to write your own buildView method for the new Type.
We are familiar with Components and Helpers in CakePHP.
I have an ABC Component and XYZ helper and both have same function (around 2000 lines total 4000 lines).
there is any way to use same function in Controller and .CTP files. it's not good to use same function 2 time.
any method so i can use Component/Helper function in Helper/Component without rewrite ?
same method into component and helper >>
Component
class DATAComponent extends Component {
public $components = array('Session', 'THmail');
public function UsaStateList()
{ /********/}
Helper
class LabHelper extends AppHelper {
public function UsaStateList()
{ /********/}
}
Well, you will have to rewrite something, it's not going to solve itself.
CakePHP is still PHP, so you can easily apply common patterns to keeps things DRY. The most straight forward way would probably be to move the shared functionality into an utility class that your component and helper can both use internally while leaving their public API unchanged.
Some of CakePHPs helpers do something similar, check for example the time helper.
http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html
http://book.cakephp.org/2.0/en/core-utility-libraries/time.html#CakeTime
Traits might be an option too, depending on the amount of functionality being shared and how much it is tied to the use in a component/helper.
I wanted to use a component inside a helper. So i came out with the following solution.
<?php
App::uses('AppHelper', 'View/Helper');
App::import('Component', 'MyComponent');
class MyHelperHelper extends AppHelper {
private $MyComponent = null;
public function __construct(View $View, $settings = array()) {
$collection = new ComponentCollection();
$this->MyComponent = new MyComponentComponent($collection);
parent::__construct($View, $settings);
}
public function myCustomFunction() {
return $this->MyComponent->myCustomFunction();
}
}
Simple Answer
For common functions across your application, add a Lib or Utility class.
app/Lib/MyClass.php
class MyClass {
public static function usaStateList() {
// ...
}
}
Then add this at the top of whichever file you want access to the function:
App::uses('MyClass', 'Lib');
And call your function wherever you like:
$myClass = new MyClass();
$states = $myClass::usaStateList();
Better Answer
It looks to me like your specific problem is that you want to be able to get a list of US states in both your controller and your view. The best way to do this is to have a database table of US states.
Create a table in your database called us_states.
Example SQL:
CREATE TABLE `us_states` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(20) NOT NULL,
`abbreviation` CHAR(2) NOT NULL
) ENGINE = MYISAM
Insert all the states as data in that table. You can find SQL dumps on the Internet which already have that for you (e.g. Like this one).
Create a UsState model in CakePHP.
/**
* Model for US States
*
* #package app.Model
*/
class UsState extends AppModel {
/**
* Default sort order
*
* #var string|array
*/
public $order = 'UsState.name';
}
What you can then do is access the states from your controller just by using the model.
/**
* Your Controller
*
* #package app.Controller
*/
class YourController extends AppController {
public function index() {
// Get a list of US states
$this->loadModel('UsState');
$states = $this->UsState->find('all');
}
}
And if you want to access those states in your View, then you should pass along that data as you normally would any other variables.
I imagine you would want to do that so you can have a select menu of US states, perhaps.
public function index() {
// Get a list of US states
$this->loadModel('UsState');
$states = $this->UsState->find('all');
// Make the states into an array we can use for a select menu
$stateOptions = array();
foreach ($states as $state) {
$stateOptions[$state['id']] = $state['name'];
}
// Send the options to the View
$this->set(compact('stateOptions'));
}
And in your view you can display a select menu for that like this:
echo $this->Form->select('us_state_id', $stateOptions);
I would go with a class in Lib folder. It is pretty clear how to deal with a library class that has only static methods. So, I omit this case. A workable solution for instantiating the class could be to create it in the controller and put it into the registry. If you really need to access CakeRequest, CakeResponse and CakeSession (take a note that CakeSession has many static methods, so you often do not need an instance of that class) from that class you can set it from the controller:
$MyLib = new MyLib();
$MyLib->setRequest($this->request); // optional
ClassRegistry::addObject('MyLib', $MyLib);
Then from the view or any other place you would just get an instance of MyLib from the registry:
ClassRegistry::getObject('MyLib');
or simply
$list = ClassRegistry::getObject('MyLib')->UsaStateList();
So, your class would be something like this:
// /Lib/MyLib.php
class MyLib {
public function setRequest(CakeRequest request) {...}
public function UsaStateList() {...}
}
ok you want to use a single function in component and helper, I can think of 3 things you can do:
Calling a function from the component in your helper.
Calling a function from a helper in your component.
Create a model or use an existing model where you put the function, and you can use this function in your component or your help.
Option numbre 3:
In your helper and component, you need import a model, assuming that your function be in a model "StateList":
how you call the funcion of the model "StateList" in your helper, so:
App::import("Model", "StateList");
$model = new StateList();
$model->UsaStateList();
how you call the funcion of the model "StateList" in your component, so:
$model = ClassRegistry::init('StateList');
$model->UsaStateList();
ans if you want use the same function in a controller just:
$this->loadModel('StateList');
$this->StateList->UsaStateList();
<?php
class User extends AppModel {
var $name = 'User';
var $displayField = 'fname';
}
How can I only return users from this model that have a "standing" of "1"? I am not looking to do this from the controller but, from the model.
[Solution] In model
function beforeFind($queryData){
$queryData['conditions']['standing'] = 1;
return $queryData;
}
The easiest way to do this would be to put in some filtering conditions in your beforeFind callback. Modifying the $queryData variable and adding your restriction to the conditions key should do it.
From the manual entry - http://book.cakephp.org/1.3/en/view/1049/beforeFind
Called before any find-related operation. The $queryData passed to
this callback contains information about the current query:
conditions, fields, etc.
If you do not wish the find operation to begin (possibly based on a
decision relating to the $queryData options), return false. Otherwise,
return the possibly modified $queryData, or anything you want to get
passed to find and its counterparts.
You might use this callback to restrict find operations based on a
user’s role, or make caching decisions based on the current load.