I am five days into Laravel and after hours of watching Jeffrey Way I decided to delve into building an app to learn.
I am stuck at the point of working with tables in a hasManyThrough layout and identifying the columns that are the linkage between the tables. Eloquent is trying to use a column called 'id" as primary key that it cannot find. In my tables I am using the naming convention tablename_id like below. In my class function, I nominate the columns to use, but it fails with the error:
QueryException in Connection.php line 620:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'cable_installations.id' in 'on clause' (SQL: select `cable_specifications`.*, `cable_installations`.`cable_specifications_id` from `cable_specifications` inner join `cable_installations` on `cable_installations`.`id` = `cable_specifications`.`cable_installations_id` where `cable_installations`.`cable_specifications_id` is null)
I am trying to retrieve:
A distinct list of cable specifications that are allowed with a selected cable_installation_method
Thanks!
TABLE 1: cable_specifications
cable_specifications_id (REPEATS)
other_columns...
TABLE 2: cable_installation_methods
cable_installation_methods_id (UNIQUE)
other_columns...
TABLE 3: cable_installations (PIVOT)
cable_specifications_id (REPEATS)
cable_installation_methods_id (REPEATS)
My classes are:
use Illuminate\Database\Eloquent\Model as Eloquent;
class CableInstallationMethod extends Eloquent {
protected $table = 'cable_installation_methods';
protected $fillable = [];
public function CableInstallation()
{
return $this->hasMany('CableInstallation');
}
public function CableSpecByInstall()
{
return $this->hasManyThrough('App\CableSpecification', 'App\CableInstallation', 'cable_specifications_id', 'cable_installations_id');
}
}
In my controller, I call this function per below:
public function VoltageDropLoad()
{
$InstallationMethods = CableInstallationMethod::all();
$CableSelected = CableInstallationMethod::where("cable_installation_methods_id", 1)->first();
$CableTypes = $CableSelected->CableSpecByInstall()distinct()->get()->toJson();
return view('pages.voltcalc', compact('InstallationMethods', 'CableTypes', 'CableTypes'));
}
Based on your CableInstallationMethod class, you may have missed defining the primary key field for your models:
use Illuminate\Database\Eloquent\Model as Eloquent;
class CableInstallationMethod extends Eloquent {
protected $table = 'cable_installation_methods';
/* Need to define $primaryKey column here, normally defaults to 'id' */
protected $primaryKey = 'cable_installation_methods_id';
protected $fillable = [];
public function CableInstallation()
{
return $this->hasMany('CableInstallation');
}
public function CableSpecByInstall()
{
return $this->hasManyThrough('App\CableSpecification', 'App\CableInstallation', 'cable_specifications_id', 'cable_installations_id');
}
}
With the primary key set you can also take advantage of using Model::find($id) instead of using Model:where(...)->first()
public function VoltageDropLoad()
{
$InstallationMethods = CableInstallationMethod::all();
$CableSelected = CableInstallationMethod::find(1);
$CableTypes = $CableSelected->CableSpecByInstall()->distinct()->get()->toJson();
return view('pages.voltcalc', compact('InstallationMethods', 'CableTypes', 'CableTypes'));
}
Related
Using Lumen 5.5.2 and illuminate/database v5.5.17.
I have 3 models set up, where one belongs to the other 2. So Quote, has an area, and a depot.
The relationship with the depot works as expected, the area returns null.
for example
$quoteModel = new Quote();
$quote = $quoteModel
->with('area')
->with('depot')
->where('id', '=', $id)
->first();
echo 'depot id : ' , $quote->depot->id , "<br>\n";
echo 'area id : ' , $quote->area->id , "<br>\n";
The depot id will be echoed, the area will cause an error because it is not an object.
Passing the models names as an array ->with(['area', 'depot']), or just requesting area (either method) does not fix it.
Quote.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Quote extends EloquentModel {
protected $table = 'quotes';
public function area() {
return $this->belongsTo('\App\Models\Area', 'area_id', 'id');
}
public function depot() {
return $this->belongsTo('\App\Models\Depot', 'depot_id', 'id');
}
}
Area.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Area extends EloquentModel {
protected $table = 'areas';
public $timestamps = false;
public $incrementing = false;
public function quotes() {
return $this->hasMany('\App\Models\Quote', 'area_id', 'id');
}
}
Depot.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Depot extends EloquentModel {
protected $table = 'depots';
public $timestamps = false;
public $incrementing = false;
public function quotes() {
return $this->hasMany('\App\Models\Quote', 'depot_id', 'id');
}
}
If I create a parse error in Area.php the script will fail, proving it is being included.
I have a listener set up so I can log the queries, and they show up just fine.
select * from `quotes` where `id` = 99192 limit 1
select * from `areas` where `areas`.`id` in (072)
select * from `depots` where `depots`.`id` in (07)
If I run the area query manually it returns the row I expect.
I tried changing the name of the area relationship, and it doesn't help.
So the missing piece of the puzzle, was that this project is setup up against a legacy database as part of updating an existing web app.
Turns out that there was some datatype inconsistency; I found this out when I could successfully link another model to area with no issues. The field for the area_id is normally a zero filled int, but for some reason on the quotes table it was a char; so the data looked correct when browsing in adminer, and worked when copied and pasted, but did not match up somewhere in Eloquents internals.
Changing the datatype on the table fixes the issue.
I'm new to Laravel-Mongodb, trying to get result by parameter but it's not working
Model:
use Jenssegers\Mongodb\Model as Eloquent;
class Customer extends Eloquent {
protected $connection = 'mongodb';
protected $collection = 'Customer';
}
Controller:
class AdminController extends Controller
{
public function index() {
return Customer::all();
}
public function show($id) {
return Customer::find($id);
}
}
It's alright for index() but it will return empty for show($id), it will work if using:
return Customer::find(1);
I'm not sure why it's not working with parameter, am I missing something?
You need to add one protected variable in your model like below
protected $primaryKey = “customerId”
You can add your own primary key to this variable but if you won’t add this line in model, model will by default take _id as your primary key and _id is autogenerated mongodb’s unique id.
Thats the reason why you are not able to get record by id.
1 is not a valid ObjectId. Try to find a valid ID with a tool like Robomongo or just list your customers with your index method to find out what the IDs are.
The query should look more like this:
return Customer::find("507f1f77bcf86cd799439011");
You can read more about MongoDBs ObjectId here:
https://docs.mongodb.org/manual/reference/object-id/
i have a question on mongodb, model cakephp and relationships.
I'd create the following relations:
User -> hasMany -> City
City -> belongsTo -> User
In MongoDB, I have two tables:
users
cities (with key user_id)
In cakephp, I have 2 model:
User.php
class User extends Model {
public $name = 'User';
public $actsAs = array('Containable');
public $hasMany = array ('City');
..
}
and:
City.php
class City extends Model {
public $name = 'City';
public $actsAs = array('Containable');
public $belongsTo = array('User');
..
}
In my controller I use :
$user = $this->User->find('all');
but it doesn't work. In sql dump, cakephp uses a find only on tbl users.
Why? Where I wrong?
I normally place recursive to -1 and containable in app model, so it applies to all models you create unless you override specifically.
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
Your relationships are fine, although I usually add className and foreignKey just to be safe and clear. In your controller you should do something like this:
$users = $this->User->find('all', array(
'contain' => array(
'City'
)
));
Recursive will prevent any associated records being included by default, this is good as sometimes you do not need the recursive data and extra data will help slow down your application.
Next adding contain into your find call may seem like a chore but it will be clear and concise what you are querying, any 3rd party developer will understand exactly what you are doing if they know how to use Cake. Hope this helps.
I am trying to use the find function to query the database table by id. The table has 4 primary keys (3 foreign keys).
This is the code of the table data gateway:
class Application_Model_DbTable_Assigneduser extends Zend_Db_Table_Abstract
{
protected $_name = 'assigneduser';
}
This is the code of the Mapper:
public function find($id)
{
$result = $this->getDbTable()->find($id);
if(count($result) == 0)
return;
$row=$result->current();
$assignedUser = new Application_Model_AssignedUser();
$assignedUser->setId($row->id)
->setIdProject($row->id_project)
->setIdUser($row->id_user)
->setIdTask($row->id_task);
}
The code I use to instantiate the mapper and where I use the method find:
public function indexAction()
{
echo "<xmp>";
$user=new Application_Model_AssignedUserMapper();
print_r($user->find(3));
echo '</xmp>';
}
I used the print_r and xmp tag to have a good look at what the code returns. The exception message I get is:
Message: Too few columns for the primary key
I have no idea what to do to fix it. Any idea? Thank you!
Ok find() will return rows by Primary Key and only primary key if you need to pass a compound primary key it must be as an array.
Here is the doc block for find():
/**
* Fetches rows by primary key. The argument specifies one or more primary
* key value(s). To find multiple rows by primary key, the argument must
* be an array.
*
* This method accepts a variable number of arguments. If the table has a
* multi-column primary key, the number of arguments must be the same as
* the number of columns in the primary key. To find multiple rows in a
* table with a multi-column primary key, each argument must be an array
* with the same number of elements.
*
* The find() method always returns a Rowset object, even if only one row
* was found.
*
* #param mixed $key The value(s) of the primary keys.
* #return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria.
* #throws Zend_Db_Table_Exception
*/
To fix:
class Application_Model_DbTable_Assigneduser extends Zend_Db_Table_Abstract
{
protected $_name = 'assigneduser';
protected $_primary = array('column','column'...); //This is not strictly required but may help.
}
public function indexAction()
{
$user=new Application_Model_AssignedUserMapper();
Zend_Debug::dump($user->find(array(3,,,)), 'User');//outputs formatted var_dump with optional label as second arg.
}
Now to make this easy:
public function find($id)
{
$select = $this->getDbTable->select();
$select->where('id = ?', $id);
$result = $this->getDbTable()->fetchRow($select);//will return only one row, if you need more use fetchAll()
if(is_null($result)) //fetchRow() returns NULL if no rows found.
return;
$row=$result;
$assignedUser = new Application_Model_AssignedUser();
$assignedUser->setId($row->id)
->setIdProject($row->id_project)
->setIdUser($row->id_user)
->setIdTask($row->id_task);
}
Using fetchRow() you can query against any column in the row, but it will only return one row. If you need a rowset returned you can use fetchAll() with the same query options and you will get a rowset.
Hope this helps.
I just had the same problem. On ZF version 1.11.11, it is just as RockyFord said, you just have to map in the DbTable class an array of columns that are the primary key like this:
class AssignedUser extends Zend_Db_Table_Abstract
{
protected $_name = 'assigned_user';
protected $_primary = array('user_id','project_id','task_id');
}
But when you want to find this record, you can't just find by one of the primary keys, not even pass an array with them (that's what I was doing), the find method is waiting a unlimited number of arguments as the primary key's values (following the same order you declared), like this:
$assignedUserTable = new AssignedUser();
$rowset = $assignedUserTable->find( $userId, $projectId, $taskId );
$row = $rowset->current();
The latest EF Code First NuGet package comes with a custom implementation of IDatabaseInitializer called DontDropDbJustCreateTablesIfModelChanged. As the name implies, when a model change is detected, it will not drop and recreate the whole database, just drop and recreate the tables.
Say I have this model class:
public class User
{
public string Username { get; set; }
// This property is new; the model has changed!
public string OpenID { get; set; }
}
How would one go about implementing an IDatabaseInitializer that doesn't drop any tables either. In this case, it would just add an OpenID column to the User table?
I think it is a matter of SQL. So for SQL Server you can write something like:
public class MyInitializer : IDatabaseInitializer<MyContext>
{
public void InitializeDatabase(MyContext context)
{
context.Database.SqlCommand(
#"
IF NOT EXISTS (SELECT 1 FROM sys.columns AS col
INNER JOIN sys.tables AS tab ON tab.object_Id = col.object_Id
WHERE tab.Name = 'User' AND col.Name = 'OpenId')
BEGIN
ALTER TABLE dbo.User ADD OpenId INT;
END");
}
}
But in the same way you can execute such script without adding it to your application which I think is much better approach.
With the current version of Code First, you cannot simply amend your schema and preserve any data that you might have in your tables. If maintaining data, such as reference data / lookup tables is important with this release you can create your own Initializer and override the Seed method to populate your tables
public class MyDbInitializer : DropCreateDatabaseIfModelChanges<MyContext>
{
protected override void Seed(MyContext context)
{
var countries = new List<Country>
{
new Country {Id=1, Name="United Kingdom"},
new Country{Id=2, Name="Ireland"}
};
countries.ForEach(c => context.Countries.Add(c));
}
}
And then use this in your Application_Start:
Database.SetInitializer<MyContext>(new MyDbInitializer());
I believe that this is being addressed currently by the EF Team, but wasn't ready for release at the time the Code First drop came out. You can see a preview here: Code First Migrations