Couldn't get much help from the World Wide Wide Wide Web ...
The situation is:
My model has two manyToMany fields and one oneToMany field.
When I update the model, doctrine magically remove the old values for manyToMany fields and set the new one,
but for the oneToMany field it does not remove the old value but add the new one.
Is this a normal behaviour?
To work around I am DQLing to empty the oneToMany field target Entity for the mapped by model id and persisting new values
i.e Model: Rule has
/**
* #OneToMany(targetEntity="\Tier", mappedBy="rule", cascade={"persist", "remove"})
* #JoinColumn(name="ruleId", referencedColumnName="ruleId")
* #var Tier[]
*/
public $tiers;
And in the Rule Mapper I am deleting all tiers before calling PERSIST to get UPDATE working:
public function resetTiers($ruleId)
{
$modelClass = "Tier";
$q = $this->doctrineEntityManager->createQuery("Delete from $modelClass m where m.rule =" . $ruleId);
$numDeleted = $q->execute();
return $numDeleted;
}
I don't mind doing this way as long as its OK and not introducing any bad practice.
Thank you for your time.
Please read this bit of Doctrine's documentation.
Doctrine will only check the owning side of an association for changes.
And:
OneToMany is always the inverse side of a bidirectional association.
Although:
You can pick the owning side of a many-to-many association yourself.
So yes, it's normal behavior.
My guess is your ManyToMany relations are owned by your model (i.e. the Doctrine declaration you put in your property's comment says inversedBy and not mappedBy). Which would be why they're automatically updated (as per the doc linked above).
So either you start working on the other side's entities (namely Tier), or you trick the ORM by updating the other side's entities directly through your Rule's accessors, probably something like:
public function setTiers($tiers) {
foreach ($this->tiers as $tier) {
$tier->setRule(null);
}
foreach ($tiers as $tier) {
$tier->setRule($this);
}
if (is_array($tiers)) {
//this wrapper is useful if you use syntaxes like
//$someRule->getTiers()->add($someTier); which is usual if you use
//an addTier(Tier $tier) method declared in Rule
$tiers = new \Doctrine\Common\Collections\ArrayCollection($tiers);
}
$this->tiers = $tiers;
}
For the first option, working on other side's entities, just calling $someTier->setRule($someRule) will auto-update* $someRule's $tiers.
*: auto-update should happen 1/ when you flush your mods and 2/ maybe you'll need to refresh the $someRule object (I mean the EM's refresh method).
All of this is "my guess", anyone feel free to correct, and OP feel free to give some feedback about how it went if you try this! ;)
Here is some more doc about updating related entities, especially at the bottom of the page, section 6.14.1.
Hope this helps!
Related
I upgraded my application from Sencha Touch 2.4 to ExtJs 6.5.3.
On Sencha Touch 2.4, there is a function for the Association (Ext.data.association.Association) by the name of getType() that returns the association-type as a string, for example: "hasOne".
as seen for example, in here (the Model source code of Sencha Touch 2.4):
for (i = 0; i < associationCount; i++) {
association = associations[i];
associationName = association.getName();
type = association.getType();
allow = true;
if (associationType) {
allow = type == associationType;
}
if (allow && type.toLowerCase() == 'hasmany') {
In my project, by understanding if the association type is hasmany, hasone or belongsto I can "choose" what type of SQL query to create (not originally written by me, but that's a large project and I can't contact the original developer), so that's a must for me.
I try to look into the Extjs 6 / 6.5 documentation and couldn't find anything.
Seems like it was deprecated a long time ago.
I was thinking about inserting the "type" inside the model as an object of models and types, for example:
{
'MyProject.model.Receipt': 'hasone',
'MyProject.model.Order': 'hasmany',
'MyProject.model.invoice': 'belongsto',
...
}
and then try to access it from the model itself and find the type by the association "parent" model.
It feels like a risk and an overkill for such a (suppose-to-be) easy task.
I tried to also find online for solutions but it looks like no one ever tried to find a solution for something like that.
Thank you
The associations property on a record is a bit tricky and a little obscured from us as developers. If you use the inverse property in your associations, they'll sometimes appear in the associations property, which is problematic. I've raised an issue with Sencha Support asking for a reliable method on returning the actual associations on a record, but I've also come up with an interim solution to determine if they're truly an association. I've extended this idea to maybe something that might help you with the types? But you'll have to test this out to determine if it actually works for you... I only use hasOne and hasMany in my code, so I don't know if this is right for belongsTo.
Here's the Fiddle, and here's the code:
Ext.override(Ext.data.Model, {
isActualAssociation: function (role) {
var isThing = !role.instanceName || role.fromSingle;
if (!isThing) {
console.log('found weirdo', role)
}
return isThing;
},
getAssociationType: function (association) {
if (association.fromSingle) {
return 'hasOne';
}
else if (association.storeName) {
return 'hasMany';
}
return 'belongsTo';
}
});
I working on an application that has its own database and gets user information from another serivce (an LDAP is this case, through an API package).
Say I have a tables called Articles, with a column user_id. There is no Users table, instead a user or set of users is retrieved through the external API:
$user = LDAPConnector::getUser($user_id);
$users = LDAPConnector::getUsers([1, 2, 5, 6]);
Of course I want retrieving data from inside a controller to be as simple as possible, ideally still with something like:
$articles = $this->Articles->find()->contain('Users');
foreach ($articles as $article) {
echo $article->user->getFullname();
}
I'm not sure how to approach this.
Where should I place the code in the table object to allow integration with the external API?
And as a bonus question: How to minimise the number of LDAP queries when filling the Entities?
i.e. it seems to be a lot faster by first retrieving the relevant users with a single ->getUsers() and placing them later, even though iterating over the articles and using multiple ->getUser() might be simpler.
The most simple solution would be to use a result formatter to fetch and inject the external data.
The more sophisticated solution would a custom association, and a custom association loader, but given how database-centric associations are, you'd probably also have to come up with a table and possibly a query implementation that handles your LDAP datasource. While it would be rather simple to move this into a custom association, containing the association will look up a matching table, cause the schema to be inspected, etc.
So I'll stick with providing an example for the first option. A result formatter would be pretty simple, something like this:
$this->Articles
->find()
->formatResults(function (\Cake\Collection\CollectionInterface $results) {
$userIds = array_unique($results->extract('user_id')->toArray());
$users = LDAPConnector::getUsers($userIds);
$usersMap = collection($users)->indexBy('id')->toArray();
return $results
->map(function ($article) use ($usersMap) {
if (isset($usersMap[$article['user_id']])) {
$article['user'] = $usersMap[$article['user_id']];
}
return $article;
});
});
The example makes the assumption that the data returned from LDAPConnector::getUsers() is a collection of associative arrays, with an id key that matches the user id. You'd have to adapt this accordingly, depending on what exactly LDAPConnector::getUsers() returns.
That aside, the example should be rather self-explanatory, first obtain a unique list of users IDs found in the queried articles, obtain the LDAP users using those IDs, then inject the users into the articles.
If you wanted to have entities in your results, then create entities from the user data, for example like this:
$userData = $usersMap[$article['user_id']];
$article['user'] = new \App\Model\Entity\User($userData);
For better reusability, put the formatter in a custom finder. In your ArticlesTable class:
public function findWithUsers(\Cake\ORM\Query $query, array $options)
{
return $query->formatResults(/* ... */);
}
Then you can just do $this->Articles->find('withUsers'), just as simple as containing.
See also
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
I have a search engine which calls a Cakephp action and receives which model the engine should search in eg. "Projects". The variable is called $data_type;
Right now I use this to check if the model exists:
// Check if Table really exists
if(!TableRegistry::get($data_type)){
// Send error response to view
$response = [
'success' => false,
'error' => 'Data type does not exist'
];
$this->set('response', $response);
return;
}
I'm not sure I'm doing it the right or the safest way to check if a model exists, because I don't know if the TableRegistry::get() function is vulnerable to SQL injection behind the scenes.
I also found that inputing an empty string to the get() function doesn't need in a false result??? Is there a safe solution I can implement that will solve my problem?
TableRegistry::get() is not safe to use with user input
First things first. It's probably rather complicated to inject dangerous SQL via TableRegistry::get(), but not impossible, as the alias passed in the first argument will be used as the database table name in case an auto/generic-table instance is created. However the schema lookup will most likely fail before anything else, also the name will be subject to inflection, specifically underscore and lowercase inflection, so an injection attempt like
Foo; DELETE * FROM Bar;
would end up as:
foo;d_e_l_e_t_e*f_r_o_m_bar;
This would break things as it's invalid SQL, but it won't cause further harm. The bottom line however is that TableRegistry::get() cannot be regarded as safe to use with user input!
The class of the returned instance indicates a table class' existence
TableRegistry::get() looks up and instantiates possible existing table classes for the given alias, and if that fails, it will create a so called auto/generic-table, which is an instance of \Cake\ORM\Table instead of an instance of a concrete subclass thereof.
So you could check the return value against \Cake\ORM\Table to figure whether you've retrieved an instance of an actual existing table class:
$table = TableRegistry::get($data_type);
if (get_class($table) === \Cake\ORM\Table::class) {
// not an existing table class
// ...
}
Use a whitelist
That being said, unless you're working on some kind of administration tool that explicitly needs to be able to access to all tables, the proper thing do would be to use some sort of whitelisting, as having users arbitrarily look up any tables they want could be a security risk:
$whitelist = [
'Projects',
'...'
];
if (in_array($data_type, $whitelist, true) !== true) {
// not in the whitelist, access prohibited
// ...
}
Ideally you'd go even further and apply similar restrictions to the columns that can be looked up.
You may want to checkout https://github.com/FriendsOfCake/awesome-cakephp#search for some ready made search plugins.
I want to use Yii2 and redis as database.
So far, I got Redis ActiveRecord Class for Yii2 from Here.
link1
link2
but, I got a problem. WHY THIS CLASS ADDS ANYTHING AS HASH IN REDIS????
Above that I cant Find in which pattern it Insert data. I add one user and it will add a user under user:xxx namespace and another record under s:user:xxx and so on but none of theme has any fields that i defined in attributes!! only contain IDs.
I know that a Key-value type database and RDBMS are different and also know how can implement relation like records in Redis, but I don't know why it will only save IDs.
I could not find any example of using redis ActiveRecords so far.
There is one in here and its not good enough.
So here is my main wuestion: how can add data to redis Using activeRecords and different data types In YII2?
And if its impossible with ActiveRecords what is the best solution? in this case
ANOTHER QUESTION: is it possible to use a Model instead and write my own model::save() method? and what is the best data validation solution at this rate?
Actually I want to make a telegram bot, so i should get messages and send them in RabitMQ and get data in a worker, do the process and save results to Redis, and finally send response to user through the RabitMQ.
So I need to do a lot of validations AND OF COURSE AUTHENTICATIONS and save and select and range and save to sets an lists and this and that ....
I want a good way to make Model or active record or the proper solution to validation, save and retrieve data to Redis and Yii2.
Redis DB can be declared as a cache component or as a database connection or both.
When it is declared as a cache component (using the yii/redis/cache) it is accessible within that component to store key/value pairs as shown here.
$cache = Yii::$app->cache;
// try retrieving $data from cache
$data = $cache->get($key);
// store $data in cache so that it can be retrieved next time
$cache->set($key, $data);
// one more example:
$access_token = Yii::$app->security->generateRandomString();
$cache->add(
// key
$access_token,
// data (can also be an array)
[
'id' => Yii::$app->user->identity->id
'name' => Yii::$app->user->identity->name
],
// expires
60*60*3
);
Also other components may start using it for caching proposes like session if configured to do so or like the yii\web\UrlManager which by default will try to cache the generated URL rules in whatever valid caching mechanism defined under the config file's cache component as explained here. So it is normal to find some stored data other than yours in that case.
When Redis is declared as a DB connection like in the links you provided which means using the yii\redis\Connection class you can make your model extending its \yii\redis\ActiveRecord class as any other ActiveRecord model in Yii. The only difference I know so far is that you need to define your attributes manually as there is no DB schema to parse for NoSQL databases. Then just define your rules, scenarios, relations, events, ... as any other ActiveRecord model:
class Customer extends \yii\redis\ActiveRecord
{
public function attributes()
{
return ['id', 'name', 'address', 'registration_date'];
}
public function rules()
{
return [
['name', 'required'],
['name', 'string', 'min' => 3, 'max' => 12, 'on' => 'register'],
...
];
}
public function attributeLabels() {...}
...
}
All available methods including save(), validate(), getErrors(), ... could be found here and should be used like any other ActiveRecord class as shown in the official guide.
In an architecture where objects have many complex relationships, what are some maintainable approaches to dealing with
Resolving Dependencies
Optimistic Updates
in react applications?
For example, given this type of schema:
```
type Foo {
...
otherFooID: String,
bars: List<Bar>
}
type Bar {
...
bizID: String,
}
type Biz {
...
}
```
A user might want to save the following ->
firstBiz = Biz();
secondBiz = Biz();
firstFoo = Foo({bars: [Bar({biz: firstBiz})]
secondFoo = Foo({bars: [Bar({biz: secondBiz})] otherFooId: firstFooId.id})
First Problem: Choosing real ids
The first problem with above is having the correct id. i.e in order for secondFoo to save, it needs to know the actual id of firstFoo.
To solve this, we could make the tradeoff, of letting the client choose the id, using something like a uuid. I don't see anything terribly wrong this this, so we can say this can work
Second Problem: Saving in order
Even if we determine id's from the frontend, the server still needs to receive these save requests in order.
```
- save firstFoo
// okay. now firstFoo.id is valid
- save secondFoo
// okay, it was able to resolve otherFooID to firstFoo
```
The reasoning here is that the backend must guarantee that any id that is being referenced is valid.
```
- save secondFoo
// backend throws an error otherFooId is invalid
- save firstfoo
// okay
```
I am unsure what the best way to attack this problem is
The current approaches that come to mind
Have custom actions, that do the coordination via promises
save(biz).then(_ => save(Bar).then(_ => save(firstFoo)).then(_ => save(second)
The downside here is that it is quite complex, and the number of these kinds of combinations will continue to grow
Create a pending / resolve helper
const pending = {}
const resolve = (obj, refFn) => {
return Promise.all(obj, refFn(obj));
}
const fooRefs = (foo) => {
return foo.bars.map(bar => bar.id).concat(foo.otherFooId);
}
pending[firstFoo].id = resolve(firstFoo, fooRefs).then(_ => save(firstFoo))
```
The problem with 2. is that it can cause a bunch of errors easily, if we forget to resolve or to add to pending.
Potential Solutions
It seems like Relay or Om next can solve these issues, but i would like something less high power. Perhaps something that can work in with redux, or maybe it's some concept I am missing.
Thoughts much appreciated
I have a JS/PHP implementation of such a system
My approach is to serialize records both on the client and server using a reference system
For example unsaved Foo1 has GUID eeffa3, and a second Foo references its id key as {otherFooId: '#Foo#eeffa3[id]' }
Similarily you can reference a whole object like this
Foo#eefa3:{bars['#Baz#ffg4', '#Baz#ffg5']}
Now the client-side serializer would build a tree of relations and model attributes like this
{
modelsToSave:[
'Foo#effe3':{
attribs:{name:'John', title:'Mr.'},
relations:{bars:['#Bar#ffg4']}
},
'Bar#ffg4':{
attribs:{id:5}
relations:{parentFoo:'#Foo#effe3'}
},
]
}
As you can see in this example I have described circular relations between unsaved objects in pure JSON.
The key here is to hold these "record" objects in client-side memory and never mutate their GUID
The server can figure out the order of saving by saving first records without "parent" dependencies, then records which depend on those parents
After saving, the server wil return the same reference map, but now the attribs will also include primary keys and foreign keys
JS walks the received map twice (first pass just update server-received attributes, second pass substitute record references and attribute references to real records and attributes).
So there are 2 mechanisms for referencing a record, a client-side GUID and a server-side PK
When receiving a server JSON, you match your GUID with the server primary key