In a Laravel environment (v 5.7) I've two tables: films and versions related in a 1 to Many relationship defined in Models as so:
class Film extends Model {
...
public function versions() {
return $this->hasMany(Version::class);
}
}
class Version extends Model {
...
public function film() {
return $this->belongsTo(Film::class);
}
}
I want to pick a random Version of a Film and its related parent Film, so in a controller I do this:
...
use App\Film;
use App\Version;
class HomeController extends Controller {
...
public function index() {
$rndVersion = Version::inRandomOrder()->first();
$relatedFilm = $rndVersion->film->first();
All works fine, except the fact that the returned film is always the first of the entire recordset, and not the parent of the version I previously loaded from the DB.
I've tried to retrieve all together with:
$rndVersion = Version::inRandomOrder()->with('film')->first();
and it works, but it packs all in a single object stored in $rndVersion, and I'd rather prefer to keep the two things separate.
So, what am I doing wrong?
What shall I generally do to get the parent record of a selected/loaded one?
Thank you in advance.
I found the issue...
The way you call the relationship method defined in the Model changes the returned result.
In general, if you call:
$model->relationship() //with parenthesis
an instance of the relationship is returned. You are calling the method as a method and, as it is for Eloquent model classes, it "serves as powerful query builder" and "provides powerful method chaining and querying capabilities" (see here).
Otherwise if you call:
$model->relationship //without parenthesis
you get directly an instance of the Collection. This way you are calling the method as a property and getting a Laravel Collection thus letting you loop on the collection, get record (model) properties and use all the methods of the Collection class.
So, in my case, writing
$rndVersione = Version::inRandomOrder()->first();
$rndFilm = $rndVersione->film()->first(); //film() WITH parenthesis
works, I guess because the first() method is called on the relationship as a method, therefore maintaining the correctness of query building.
Related
I am confused about the distinction and treatment of the index() and view() functions inside CakePHP 2.4.
Specifically, I am trying to modify a 3rd party API authored in CakePHP 2.4
Consider this Controller:
class EventsController extends AppController {
<snip>
public function index() {
if ($this->request->params['named']) {
$conditions = $this->request->params['named'];
} else {
$conditions = array();
}
}
This allows me to construct a URL like http://myserver/api/events/index/StartTime >=:12/EndTime <=15.json and the StartTime and EndTime get passed into conditions when I do a find.
That's great.
Now that same Controller has this additional function:
public function view($id) {
}
This function seems to be called when I invoke a url like http://myserver/api/events/1234.json
I am trying to extend view with more parameters, just like index. For example, I'd like to invoke:
http://myserver/api/events/1234/MonitorId =:3.json and MonitorId =:3 gets passed as a parameter to handle in view. I don't want to add these into the function definitions - it can be anything.
If I try and do this, I get a "Controller" not found, but this same approach works in index()
Can someone help me achieve my goal and help explain what is going on?
(IF it helps, the full code of the controller is here)
As I can see, you are using CakePHP RESTful routing routes.php
Here,
index is used for listing all the resources e.g. all blog posts. URL /events/index is getting mapped to index method in controller automagically.
view is used for showing a specific resource which can be identified by some unique identifier like id or uuid e.g. specific blog post. URL /events/<some_identifier> gets mapped to view method
What you need to do is modify view definition to read all query string parameters that you pass in URL and call Model accordingly. By default. routes parses the first string after events in URL to resource ID and passes it as first argument to view method in controller. This is scaffolded code.
You can use named parameters $this->request->params['named'] in your view definition and construct your queries. No hard-coding.
It turns out that I need to explicitly call the view action to pass it more parameters.
So api/events/view/218245/AlarmFrames >=: 80.json works as intended and then I can get the parameters with $conditions = $this->request->params['named'];
My problem is this. I have a controller "FacsController", and a method.
public function access()
{
$facs = $this->Facs->find()->all();
return $facs;
}
This method works perfectly, data is returned properly.
But what I need is to call this method within another controller, "PagesController".
public function display()
{
$var = new FacsController();
$var->access();
$this->set('vars', $var);
$this->set('_serialize', ['vars']);
}
Unfortunately here I do not get the data returned from the FacsController.
Can someone help me? What am I doing wrong.
If you want data from a model, then you use the model, not a controller! If you ever feel the need that one controller needs to access another controller, then this is almost always an indicator for a failure in your application design. Also you never instantiate controllers yourself (unless in unit tests maybe)!
If you want to keep things DRY, create proper custom methods in your model (table class) and use them to encapsulate further logic.
That being said, like in every other controller, load the model via $this->loadModel(), or even TableRegistry::get().
$var = $this->loadModel('Facs')->find()->all();
See also
Cookbook > Controllers > Loading Additional Models
Cookbook > Database Access & ORM > Table Objects > Getting Instances of a Table Class
What is the better place to write out database queries in Laravel?
In Controllers or in Models?
Please let me know which way is correct:
Using in controllers like this?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use DB; // <--------
use App\Http\Requests;
use App\Http\Controllers\Controller;
class UsersController extends Controller
{
public function getUser()
{
return DB::table('users')->where('name', 'John')->value('email');
}
}
Using in models like this?
<?php
namespace App\Models;
use DB; // <--------
use Illuminate\Database\Eloquent\Model;
class UsersModel extends Model
{
protected $table = 'users';
public function getUser()
{
return DB::table('users')->where('name', 'John')->value('email');
}
}
Or none of the above?
Academically, it's better to keep all data based logic inside models only. But on practice it's more convinient to keep simple queries inside a controller and use scopes for repeated stuff. You get more readable and easier to maintain code in this case. Also, all Laravel books suggest the same.
Anyway it's more opinion based.
Your usage here is actually better solved with scopes.
use Illuminate\Eloquent\Database\Builder;
use Illuminate\Eloquent\Database\Model;
class User extends Model
{
// Did you know that if your model name is the singular version of your table name, you don't need to include this?
protected $table = 'users';
public function scopeForName(Builder $query, $name)
{
return $query->where('name', $name);
}
}
And now usage is this:
$user = User::forName('John')->email;
The topic of where you should place your queries is a common one. It ultimately comes down to your personal preference and your particular situation.
If you need to reuse the query elsewhere I would recommend placing the query within your model. However, I must warn you that you may encounter problems when using this option because changing the query can have unexpected results, if the query is used in multiple places within your application.
If you do not need to reuse the query, place it within the controller. This way you never have to worry about the query changing and breaking your controller method.
To answer your question, there is no definite place where you should place queries. All I can suggest is don't overcomplicate your solution, and don't try to shoehorn all your queries into one pattern.
There are other options to the two examples you provided though. For more information on these check out these videos:
Scopes, Repos, and Query Objects
The Query Builder
I'm trying to register custom constructor (add some properties) to entity.
I've done it like that :
var Entity = function () {
this.prop = "test";
};
var store = manager.metadataStore;
store.registerEntityTypeCtor("Entity", Entity);
And that works fine when Entity is the actual entity that I query. I've got a problem when Entity is a base type of object that I query for example when I have Task which derive from Entity. Property prop exists on Task but it's not initialized.
Is inheritance not supported ?
I'm using AngularJS and oData
Edit:
I've managed to do what I needed by using third param in registerEntityTypeCtor - init method. I initializ prop there. But shouldn't it work without init method as well ?
The problem with the constructor is that it's taking a dependancy on things that don't exists in the definition until you've completed the query.
Since breeze creates properties as observables,
you can predefine the property you depend on as knockout observable:
this.prop = ko.observable("test");
In my controller, I would like to set some values and have them exist (or live) throughout the different views in my app.
I read somewhere that I need to use beforeFilter function, but I am not sure if that is correct and how I go about doing that.
So in my controller I want to have
public function page1() {
$this->Model->setId('123');
}
public function page2() {
$this->Model->getId(); // would able to get the Id that was set from page1 function
$this->Model->setName('Bob');
}
public function page3() {
$this->Model->getId();
$this->Model->getName();
}
Let me know if you have questions.
To have this kind of "persistence" throughout the views, I guess the most simple approach is sessions.
Note that with the code you provided (I know is not a working example), you want the variable to be persistent inside a model, but that same model wont be maintained between actions of the same controller (or other controller for that matter).
You have to set that variable in sessions and retrieve it when you want to use it, or in the database and create methods in the model to get the last inserted id, for example.
So your code would be like
public function page1() {
$this->Session->write('id', '123');
}
public function page2() {
$this->Session->read('id');
$this->Session->write('name', 'Bob');
}
public function page3() {
$this->Session->read('id');
$this->Session->read('name');
}
Have a single model method that returns all your data in an array like
return array('varName' => $value, 'varName2' => $value2);
You can then call this model method from the controllers before filter and simply do
$this->set(ClassRegistry::init('MyModel')->commonViewVars());
If you would describe for what you think you need to do that I could give you a better advice like using requestAction() for example, this might be another option depending on what you're trying to do.
Depending on what data you want to pass to every page you should really consider to cache it. If its a menu for example cache it "forever" and just update the cache when the menu changes.