after I did some research, I didn't find any proper answer to this question.
I'm starting writing tests for my CakePHP (3.x) app and I was wondering how can we add fixtures and their associations?. In other words, how can we link a fixture to another without writing the Primary and foreign keys directly inside the fixture.
i.e
I have a Users table and a UserProfiles table.
User hasOne UserProfile through user_id column. Here's my user fixture.
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class UsersFixture extends TestFixture
{
public $import = ['table' => 'users'];
public $records = [
[
'username' => 'mark#mail.com',
'primary_role' => 1
'is_active' => 1,
]
];
}
And here's my UserProfile fixture
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class UserProfilesFixture extends TestFixture
{
public $import = ['table' => 'user_profiles'];
public $records = [
[
'first_name' => 'Mark',
'last_name' => 'Yellowknife',
]
];
}
How do I link both record together? Aka, how can I tell the user profile record to add its user_id to be linked to the user record?
Must be a way to do this with Fixtures without writing the keys manually.
Thanks guys!
You have to define this in the fixtures. The only other way would be to issue update queries at runtime to set the foreign keys, but that's anything but a good idea.
Other than that there isn't really a way to do this, I mean, how would any code know which records needs to be associated with each other without you explicitly defining it?
So, you'll have to at least define the foreign key, and depending on how primary keys are being generated (auto incrementing integers, UUIDs, etc), you might have to define them too.
Related
I have a User model which has a hasMany relationship to a Brands model and I am having issues updating the Brands for a user properly.
I have a form which allows a user to enter / delete / update their own Brands in text fields, the current method i am using to update the users Brands after they have entered them all or edited them is to delete all existing Brands associated with the User then loop over the values, create the Brand model and then 'saveMany' ... But i seem to be getting a constraint violation when adding ... I am wondering if there is a better way to do this;
My User model has the following;
public function brands()
{
return $this->hasMany('Brands::class');
}
Then in my controller I have the following code to update the Brands;
$user->brands()->delete();
foreach ($request['brands'] as $brand) {
$brandArray[] = new Brand([
'name' => $brand['name'],
'rating' => $brand['rating'],
]);
}
!empty($brandArray) && $user->brands()->saveMany($brandArray);
Is there a better way of doing this?
Let's separate things into three parts:
# constraint key violation:
If you've added foreign key constraint on another table, and you need to delete the brands, you should also delete all those related data constrained by your foreign key.
# design
If deleting brand related data is not possible, then maybe we can think about if there is a better design. Maybe we could add a hook on the frontend that call a DELETE API whenever certain data is removed by the user.
# query
If the brand has some unique key, you could use upsert instead of saveMany. That will be more efficient.
# conclusion
I would suggest deleting brands by hooks on the frontend whenever a brand is removed by users, and use upsert to deal with create and update stuff
It looks fine to me. But from my point of view,
Instead of deleting all fields, then creating them. You can use updateOrCreate eloquent method inside your foreach.
And in place of foreach, you can use the map method.
Since you only want to delete all the previous brands of the user, and create brand new brands without editing anything, you can simply follow the concept below:
In your controller:
// Step 1) load all the user brands
$user->load('brands');
// Step 2) delete all the brands with this user
$user->brands()->delete();
// Step 3) create all the new brands.
$user->brands()->createMany($request['brands']);
/* Concept of how createMany works:
The brand array should look similar to this, you can do dd($request['brands']) to verify
$user->brands()->createMany([
['name' => 'A new name.', 'rating' => '1'],
['name' => 'Another new name.', 'rating' => '2'],
]);
*/
You can find more examples in laravel documentation on createMany method: https://laravel.com/docs/9.x/eloquent-relationships#the-create-method
You can also go a step further and validate the array from the request:
$data = request()->validate([
'brands.*' => 'required',
]);
$user->brands()->createMany($data['brands']);
Hope this helps, good luck!
When you save everything you have for the user, do this under:
foreach ($request['brands'] as $brand) {
Brand::updateOrCreate(
[
'name' => $brand['name'],
'rating' => $brand['rating'],
],
[
'name' => $brand['name'],
'rating' => $brand['rating'],
]
);
}
I want a database with two tables Users and Companies and the users table has a foreign key with the company id. So 1 company can have multiple users.
I created the models for this in laravel and also created a factory for each of the tables.
In my seeders I want to create multiple data lines at once and found this solution:
factory(App\Company::class, 10)->create();
This works fine for the company table. But I need to extend this for the users table so it searches for available company Ids.
Any ideas how I can do this?
If I can't search for available Ids, I would also be happy to extend it with the "company_id" field and give it random value from 1-10 (beacause I know that these are the Ids for now).
So basically I want to extend this to use the fields from the factory, but extend it with another field "company_id":
factory(App\Users::class, 10)->create();
Here is the User factory code:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'first_name' => $faker->firstName,
'last_name' => $faker->lastName,
'postcode' => $faker->postcode,
'city' => $faker->city
];
});
When dealing with such relationships, defining model factories can be a pain. You have a few options though.
Random blind IDs
If you're sure that a common range of IDs is available, you can just generate a random ID to make the relationship work:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
// ...
'company_id' => rand(1, 10),
];
});
Inline factories
This was the most widely used approach as promoted by Laracast's screencasts:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
// ...
'company_id' => factory(App\Company::class)->create()->id,
];
});
Or you can go the other way around and create one or more users in the company factory. This way, you only need to run factory() on one of your entities.
Random ID from database
As of Laravel's 5.2 release, you have access to an Eloquent method called inRandomOrder:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
// ...
'company_id' => Company::inRandomOrder()->first()->id,
];
});
Alternatives for Laravel releases before 5.2:
// Laravel 4.2.7 - 5.1:
User::orderByRaw("RAND()")->get();
// Laravel 4.0 - 4.2.6:
User::orderBy(DB::raw('RAND()'))->get();
// Laravel 3:
User::order_by(DB::raw('RAND()'))->get();
// Source: http://stackoverflow.com/a/13931676/65732
It looks like that, you want to create Companies and Users and also you want to attach users with companies at the same time. If this is what you are trying to achieve then you can do it easily using something like this:
factory(App\Company::class, 10)->create()->each(function($factory) {
$factory->users()->save(factory(App\User::class)->make());
});
In this case, you have to define the users relationship method in your Company model using the right foreign key.
This will create 10 companies and return a collection so on that collection using each method a single user will be created and attached with that company. Also, check the documentation.
If you're using Laravel 5.2 or above you can use this:
'company_id' => Company::inRandomOrder()->first()->id
I use MySQL using InnoDB tables with CodeIgniter Datamapper in my PHP application. Often, the user is given the option of deleting a record through the app by initiating a ->delete function call. When a record has child records (one-to-one or one-to-many), I would also like these records to be deleted along with the parent record, if it is stated by FK constraints in the database.
In this case, I have 2 tables, items and input_lines. I have confirmed that both are using InnoDB. Each item can have many input_lines, so input_lines has a field called item_id, which is set to NULL, indexed, and have FK constraints (ON CASCADE DELETE and ON CASCADE UPDATE). I have set the config element in the DM config file as
$config['cascade_delete'] = FALSE
Because in the documentation it says you should do that if you are using ON UPDATE/DELETE CASCADE. However, when the user initiates the $item->delete() method, only the item is deleted, and the item_id fields on the input_line records associated with the item are set to null.
My models look like this:
class Item extends DataMapper {
public $has_many = array('labour', 'item_type', 'input_line', 'custom_item_type');
...
}
class Input_line extends DataMapper {
public $has_one = array('item');
...
}
I have tried this with cascade_delete = false and true and it won't work. I know the constraints work because deleting the record with MySQL directly works as expected, deleting the child records.
What am I missing? Why is it setting the FK fields to null instead of deleting the record?
EDIT 1:
I decided against my better judgment to debug the delete function in datamapper.php (libraries directory).
I noticed this code in that function:
// Delete all "has many" and "has one" relations for this object first
foreach (array('has_many', 'has_one') as $type)
{
foreach ($this->{$type} as $model => $properties)
{
// do we want cascading delete's?
if ($properties['cascade_delete'])
{
....
So I var_dumped the contents of $properties, and I saw this:
array (size=8)
'class' => string 'labour' (length=6)
'other_field' => string 'item' (length=4)
'join_self_as' => string 'item' (length=4)
'join_other_as' => string 'labour' (length=6)
'join_table' => string '' (length=0)
'reciprocal' => boolean false
'auto_populate' => null
'cascade_delete' => boolean true
It appears the default for when the model doesn't have the property specifically initialized is overriding the config value. This seems like too glaring a mistake so there's definitely something I'm doing wrong somewhere...right? I really, really want to avoid hacking the DM core files...
EDIT 2:
I was thinking maybe the config file wasn't being found, but I checked the logs and there're entries stating that the Datamapper config file was successfully loaded, so that's not the issue.
Doesn't look like anyone has any answers.
My solution was to change the property in the datamapper library $cascade_delete to false, since it's set to true right now. It's unfortunate that I have to resort to hacking the core, but DM won't respect my changes in the config file for cascade_delete so I have no other choice.
If anyone comes across this question and has encountered an issue like this before, please comment.
I have come across the same problem, but finally I just did like this:
$sql = "DELETE FROM EVENT WHERE event_id=".$event_id.";";
$this->db->query ($sql );
In case we set "ON DELETE CASCADE" for the foreign key which refers to event_id, the above SQL works fine, so I just call it directly.
OK, I am a little bit lost...
I am pretty new to PHP, and I am trying to use CakePHP for my web-site.
My DB is composed of two tables:
users with user_id, name columns
copies with copy_id, copy_name, user_id (as foreign key to users) columns.
and I have the matching CakePHP elements:
User and Copy as a model
UserController as controller
I don't use a view since I just send the json from the controller.
I have added hasMany relation between the user model and the copy model see below.
var $hasMany = array(
'Copy' => array(
'className' => 'Friendship',
'foreignKey' => 'user_id'
)
);
Without the association every find() query on the users table works well, but after adding the hasMany to the model, the same find() queries on the users stop working (print_r doesn't show anything), and every find() query I am applying on the Copy model
$copy = $this->User->Copy->find('all', array(
'condition' => array('Copy.user_id' => '2')
));
ignores the condition part and just return the whole data base.
How can I debug the code execution? When I add debug($var) nothing happens.
I'm not an expert, but you can start with the following tips:
Try to follow the CakePHP database naming conventions. You don't have to, but it's so much easier to let the automagic happen... Change the primary keys in your tabel to 'id', e.g. users.user_is --> users.id, copies.copy_id -->copies.id.
Define a view, just for the sake of debugging. Pass whatever info from model to view with $this->set('users', $users); and display that in a <pre></pre> block
If this is your first php and/or CakePHP attempt, make sure you do at least the blog tutorial
Make CakePHP generate (bake) a working set of model/view/controllers for users and copies and examine the resulting code
There's good documentation about find: the multifunctional workhorseof all model data-retrieval functions
I think the main problem is this:
'condition' => array('Copy.user_id' => '2')
It should be "conditions".
Also, stick to the naming conventions. Thankfully Cake lets you override pretty much all its assumed names, but it's easier to just do what they expect by default.
The primary keys should be all named id
The controller should be pluralised: UsersController
First off, try as much as possible to follow CakePHP convention.
var $hasMany = array(
'Copy' => array(
'className' => 'Friendship',
'foreignKey' => 'user_id'
)
);
Your association name is 'Copy' which is a different table and model then on your classname, you have 'Friendship'.
Why not
var $hasMany = array(
'Copy' => array('className'=>'Copy')
);
or
var $hasMany = array(
'Friendship' => array('className'=>'Friendship')
);
or
var $hasMany = array(
'Copy' => array('className'=>'Copy'),
'Friendship' => array('className'=>'Friendship')
);
Also, check typo errors like conditions instead of condition
Your table name might be the problem too. I had a table named "Class" and that gave cake fits. I changed it to something like Myclass and it worked. Class was a reserved word and Copy might be one too.
Hi This is either a very specific or very generic quetion - I'm not sure, and I'm new to the Zend framework / oo generally. Please be patient if this is a stupid Q...
Anyway, I want to create a model which does something like:
Read all the itmes from a table 'gifts' into a row set
for each row in the table, read from a second table which shows how many have been bought, the append this as another "field" in the returned row
return the row set, with the number bought included.
Most of the simple Zend examples seem to only use one table in a model, but my reading seems to suggest that I should do most of the work there, rather than in the controller. If this is too generic a question, any example of a model that works with 2 tables and returns an array would be great!
thanks for your help in advance!
I assume second tables is something like "gift_order" or something.
In this case you need to specify tables relationships beetween "gift" and and "gift_order" via foreign keys and describe it in table class.
It will look like this
class GiftOrder extends Zend_Db_Table_Abstract
{
/** Table name */
protected $_name = 'gif_order';
protected $_referenceMap = array(
"Fileset" =>array(
"columns" => array("gifId"),
"refTableClass" => "Gift",
"refColumns" => array("id")
));
........................
You need to specify foreigh key constraint while create table with SQL
ALTER TABLE `gift_order`
ADD CONSTRAINT `order_to_gift` FOREIGN KEY (`giftId`) REFERENCES `gift` (`id`) ON DELETE CASCADE;
If this is something you looking for you could find more on this at this link link http://framework.zend.com/manual/en/zend.db.table.relationships.html
With such solution you will be able to loop gifts and get their orders without any complex SQL's
$rowSetGifts = $this->findGifts();
while($rowSetGifts->next()){
$gift = $rowSetGifts->current();
$orders = $gift->findGiftOrder();//This is magick methods, this is the same $gift->findDependentRowset('GiftOrder');
//Now you can do something with orders - count($orders), loop them or edit
}
I would recommend creating a function in your gifts model class that returns what you want. It would probably look something like:
public function getGiftWithAdditionalField($giftId) {
$select = $this->getAdapter()->select()
->from(array('g' => 'gifts'))
->joinLeft(array('table2' => 't2'), 'g.gift_id = t2.gift_id', array('field' => 'field'))
->where('g.gift_id = ?', $giftId);
return $this->getAdapter->fetchAll($select);
}
You can check out the Zend Framework Docs on Joins for more info.