fuel / orm Goto Github PK
View Code? Open in Web Editor NEWFuel PHP Framework - Fuel v1.x ORM
Home Page: http://fuelphp.com/docs/packages/orm/intro.html
Fuel PHP Framework - Fuel v1.x ORM
Home Page: http://fuelphp.com/docs/packages/orm/intro.html
We need some special handling when saving fields that have data_type => boolean (particularly if null is not allowed);
If i create a model with 3 boolean fields and then set them like
$model = new My_Model::factory();
$model->boolean1 = 1;
$model->boolean2 = 2;
$model->save(); // this will fail as no value has been defined for boolean3
The above behavior may be acceptable, but the place where this is an issue is when also using the Typing Observer and doing something like this:
$model = new My_Model::factory();
$model->boolean1 = 1;
$model->boolean2 = 1;
$model->boolean3 = 0;
$model->save(); // this works
$model->unrelated_property = 'foo';
$model->save(); // This will fail saying that boolean3 is required.
With Typing ORM sets those fields to type boolean (which is should) but boolean false = doesn't exist, so when ORM tries to save those fields it skips over them resulting in an error if they are required.
Easiest solution in can think of is check all fields's data_types before saving and if they are boolean, set them to 1 if they have some value or 0 if they dont.
Adding multiple hasmany/many-many objects has the problem that it will multiply your output. Thus adding a relation with 3 objects to a single parent will return 1 x 3 objects. But adding another relation to your query with 5 objects makes it 1 x 3 x 5 = 15 rows for 9 objects. And it get progressively worse after that.
To solve this subsequent to-many relations could be fetching using an extra query:
SELECT .... FROM relation WHERE id IN (parent_id_1, parent_id_2, etc...)
What has to be figured out is how to configure this (seperate method from relation()
or a flag in relation method second param array). The current get()
method with the relation classes could probably be modified to work with plural objects as well.
It'll probably be a method named include()
that includes a relation with the object without joining, while relation()
will join a relation on the parent object. If someone has a better name, let me know...
Find some way of loading multiple object types into the same model property.
It doesn't work like Query::count(), they should match up
There may be a good reason to do it the way it's done, but I thought I'd ask anyway.
In Orm\Model it has a number of commented out static properties and does property_exists() checks on them before assigning a default value. For example, line 113:
return property_exists($class, '_connection') ? static::$_connection : null;
Would the most efficient and object oriented way to do this not be to define $_connection as null and simply return it? This way, there's no overhead of doing a property_exists() and the property can be overridden.
Cheers
Phil
It would be very handy if we could setup our 'schema' in the $_properties variable and then generate the create table queries from that. This would limit defining our schema to one place: in $_properties rather than $_properties and migrations. Migrations could still be used but they would just call something like Model_User->table_up() and Model_User->table_down() which would do the actual work.
$artist = Model_Artist::find($artistId, array(
'related' => array(
'products' => array(
'order_by' => array('products.id' => 'DESC'),
'limit' => $limit,
'offset' => $start,
),
),
));
// generates:
/*
SELECT `t0`.`id` AS `t0_c0`, `t0`.`artist_id` AS `t0_c1`, `t0`.`ean` AS `t0_c2`, `t0`.`asin` AS `t0_c3`, `t0`.`source` AS `t0_c4`, `t0`.`title` AS `t0_c5`, `t0`.`formattedPrice` AS `t0_c6`, `t0`.`price` AS `t0_c7`, `t0`.`detailPageURL` AS `t0_c8`, `t0`.`description` AS `t0_c9`, `t0`.`mediumImageURL` AS `t0_c10`, `t0`.`amazon_group` AS `t0_c11` FROM `products` AS `t0` WHERE `t0`.`artist_id` = 157
*/
Limit and Offset are not included in the generated query. (One Artist - many Products).
$code->free_products = array();
foreach (\Input::post('free_products', array()) as $id) {
if (($obj = \Model_Product::find($id)))
$code->free_products[] = $obj;
}
The expected behaviour of the above code would be that relations which are no longer in $code->free_products would be deleted. This does not happen.
Model_Thread::find()->related('posts')->where('posts.id', '=', 'threads.last_post_id')->get();
The 'last_post_id' from the 'threads' table can be used to compare with the 'posts' table 'id'.
Allow fetching multiple levels of relations:
Model_Example::query()->related('relation', array('related' => 'sub_relation'))->get();
you wonder about before_find or before_read or something like that ? i need execute something before reading but _init load only first time not before each action.
or have other idea ? thanks
Add some default conditions for queries, "where" should go with gigantic warning sign for people who don't realize what they're doing.
Hello,
$a = Model_Reviewcomment::count_by_review_id($review_id);
Is generating the wrong SQL query.
it generates: SELECT COUNT(t0.id) AS count_result FROM reviewcomments
AS t0
the provided SQL omits the needed where clause:
'WHERE review_id = ....'
If you have multiple objects of the same type in a file, while that object uses the Validation Observer you get errors like:
<strong>Notice:</strong> Field with this name exists already, cannot be overwritten through add(). in PKGPATH/orm/classes/observer/validation.php [43]: add
Example how to produce error:
$model1 = My_Model::factory(array(...));
$model1->save();
$model2 = My_Model::factory(array(....));
$model2->save();
This is caused by Observer\Validation creating it's Fieldset named after the Model's Class. Therefore when you have 2 of the same type of models it ends up using the same Fieldset Instance causing things to muck up.
I come along alot of scenarios where I want to group rows in order to find the "last updated" record. I know this can be achieved in relations by limiting it to 1 result and ordering the right way, but when there is a relation to external data (say, resources retrieved from a 3rd party) group by functionality would help alot. It's the only way I can imagine to get that one record per relation identifier without having to do a separate query for every single external id, or to process your result set afterwards.
To clarify, this is what you sorta do now (untested example)
$external_ids = array('3442,'5113','3193');
$results = array();
foreach ($external_ids as $ex_id) {
$result[] = Model_Entry::find()
->where('ex_id', $ex_id)
->order_by('updated_at', 'desc')
->limit(1);
}
or this (untested example)
$external_ids = array('3442,'5113','3193');
$tmp_results = Model_Entry::find()
->where('ex_id', 'IN', $external_ids)
->order_by('updated_at', 'desc');
$results = array();
foreach ($tmp_results as $t_result) {
if (count($external_ids) <= count($results)) break;
if (array_key_exists($t_result->ex_id, $results)) continue;
$results[$t_result->ex_id] = $t_result;
}
Allthough, with group by functionality, in my mind, it could look as simple and elegant as this
$external_ids = array('3442,'5113','3193');
$results = Model_Entry::find()
->where('ex_id', 'IN', $external_ids)
->order_by('updated_at', 'desc')
->group_by('ex_id');
I don't know if grouping fits in the Active Record pattern, as the resulting objects would not represent a single row, but an aggregration of multiple. However, this code could prove very useful in certain cases.
Currently only partially implemented.
Currently when you run a query that results in an array with multiple model entries and send the results to REST it will just result in an array with empty entries.
A simple way to remedy this is to make all the properties of each result set public properties, that way when json_encode runs through it it will properly be converted to a JSON object.
This returns empty 'tags' array
$view->content = Model_Music::query()
->where('list_id', '=', $_list_id)
->related('list')
->related('list.tags')
->get_one();
This returns 'tags' array correctly:
$view->content = Model_List::query()
->where('list_id', '=', $_list_id)
->related('tags')
->get_one();
Queries seems right, the ORM seems to not be sorting out the 'tags' array or something.
Edit: 'list' are related one-to-many to 'tags' and 'music' is related one-to-one to 'list' there is no relationship between 'tags' and 'music' without 'list'
Currently, Model::create() generates an insert for all defined columns, whether they are assigned a value or not.
This means that you'll get an error on columns defined as 'NOT NULL' when no value is set, even though the database has a default value defined.
It would be really useful if a models properties were, or could optionally, be declared as null by default when creating a new instance.
For example it would nice to be able to populate a form using a models public properties (see below). As it stands a newly created model instance requires all it's attributes setting manually.
<?php
class Controller_Welcome extends Controller_Template
{
public function action_input($id = null)
{
$article = Model_Article::find($id) OR new Model_Article;
$this->template->content = View::factory('welcome/index', array('article' => $article));
}
}
?>
<?php echo Form::open(); ?>
<?php echo Form::input('title', $article->title);
<?php // Lots more form fields here. ?>
<?php echo Form::close(); ?>
Currently only supported through Query::order_by() must also add support to a Model's permanent conditions and to Model::find('x', array())
Hello,
when I tried using observer in custom directory, I started getting this:
Error: Argument 1 passed to Observer_Queue::after_load() must be an instance of Model, instance of Model_Queue given, called in /var/www/vhosts/vibe/fuel/packages/orm/classes/observer.php on line 23 and defined in /var/www/vhosts/vibe/fuel/app/classes/observer/queue.php on 10
example code here: http://scrp.at/adu
As you can see, simply removing Model from method arguments, solved the problem.
Not sure if this is a bug, just wanted to let you know :)
if I pust this observer with light modifications into the packages/orm dir... they work with "Model "
Thanks for good TOOL and good luck! :)
If you do, you'll get
Orm\Exception [ Error ]: Primary key cannot be changed.
Looks like the reset of the primary keys doesn't work, because it gets blocked in __set().
Possible workaround:
// Reset primary keys (work around a __set() limitation)
$keys = static::$_primary_key;
static::$_primary_key = array();
foreach ($keys as $pk)
{
$this->{$pk} = null;
}
static::$_primary_key = $keys;
Currently it works with "varchar(25)" and "bigint" while list_columns provides far more descriptive output.
More of a Fuel\Core item, will be left for 1.1 in any case:
Memory gradually increases as I used related entries.
If I do a query such as
$queue = \Model_Queue::find()->related('keyword')->get();
And then loop using
foreach ($queue as $entry)
{
$keyword = $entry->keyword->keyword;
unset($entry);
}
Memory gradually increases...
If I comment out
//$keyword = $entry->keyword->keyword;
Then memory remains constant.
I'm trying to see what might be causing it, but might be better for the developer to have a look?
cannot be limit located on end of sql ? because when i order-ing along joined table (with limit) = its ordering only limited part mean data are first limited aned next ordered..this is not desirable - at least for me
i think this should be right - ordering all data and after that limit all data to some rows
what you mean about this ? thanks
When you create a new record with the ORM, it doesn't respect the field defaults and puts NULL in instead. If a value isn't set in what's passed over to the save()
method, the ORM shouldn't be setting it to NULL, it should be letting the table set what goes in the fields that were omitted.
Should be possible in 2 ways:
$query->select('name', 'city'); // only select name & city
$query->select(array('last_name' => false)); // selects every column except "last_name"
When a model is gotten like this all other fields will not be available.
First thoughts:
__set()
/__get()
can't use Model::properties() anymore$user = Model_User::find_by_email("[email protected]");
echo 'Email: '.$user->email; echo '
';
echo 'User changed: '.$user->is_changed() ? 'true' : 'false'; echo '
';
$user->email = "[email protected]";
echo 'Email: '.$user->email; echo '
';
echo 'User changed: '.$user->is_changed() ? 'true' : 'false'; echo '
';
$user2 = Model_User::find_by_email("[email protected]");
echo 'Email: '.$user2->email; echo '
';
echo 'User2 changed: '.$user2->is_changed() ? 'true' : 'false'; echo '
';
$user->save();
Results in:
Email: [email protected]
true
Email: [email protected]
true
Email: [email protected]
true
But the record is not saved. Further investigation reveals that the 2nd object comes from cache and probably resets the original values. Thus not detecting any changes and not saving.
Issue edited by Jelmer:
This relates to issue #17 and can be closed but for the suggestion to make to_array() work like as_array in the DB class: https://github.com/fuel/core/blob/master/classes/database/result.php#L92
End edit.
It may be wise to blacklist certain fields from toArray or hydration unless specifically requested for. I realized this when I noticed my REST controller which returns a chat message model object and the related user object contained the users password hash. I realize this can be fixed with an unset call in the controller but I think it would be wise to be safe rather than sorry in situations where private data may be exposed (password hashes, credit card numbers, etc.)
Create a query with a nested where:
$query = \Model_Lead::query();
$query->where(array('id','>',10));
$query->where_open();
$query->where(array('id','>',20));
$query->or_where(array('id','<',15));
$query->where_close();
get works as expected
$query->get();
SELECT ... FROM `leads` AS `t0` WHERE `t0`.`id` > 10 AND (`t0`.`id` > 20 OR `t0`.`id` < 15)
but count drops the nested OR
$query->count();
SELECT COUNT(t0.id) AS count_result FROM `leads` AS `t0` WHERE `id` > 10
This doesn't work
$data['categories'] = \Model_Category::find('all', array(
'related' => array(
'sub_categories' => array(
'order_by' => array(
'order' => 'asc',
'name' => 'asc'
),
),
),
'order_by' => array(
'order' => 'asc',
'name' => 'asc'
),
));
Throws this error, notice the last 2 empty things in the ORDER BY.
Fuel\Core\Database_Exception [ 1054 ]: Unknown column '' in 'order clause' [ SELECT `t0`.`id` AS `t0_c0`, `t0`.`name` AS `t0_c1`, `t0`.`url` AS `t0_c2`, `t0`.`page_title` AS `t0_c3`, `t0`.`meta_description` AS `t0_c4`, `t0`.`meta_keywords` AS `t0_c5`, `t0`.`order` AS `t0_c6`, `t0`.`created_at` AS `t0_c7`, `t0`.`updated_at` AS `t0_c8`, `t1`.`id` AS `t1_c0`, `t1`.`category_id` AS `t1_c1`, `t1`.`name` AS `t1_c2`, `t1`.`url` AS `t1_c3`, `t1`.`page_title` AS `t1_c4`, `t1`.`meta_description` AS `t1_c5`, `t1`.`meta_keywords` AS `t1_c6`, `t1`.`order` AS `t1_c7`, `t1`.`created_at` AS `t1_c8`, `t1`.`updated_at` AS `t1_c9` FROM `categories` AS `t0` LEFT JOIN `sub_categories` AS `t1` ON (`t0`.`id` = `t1`.`category_id`) ORDER BY `t0`.`order` ASC, `t0`.`name` ASC, `` ASC, `` ASC ]
This works, and does what I wanted the above to do.
$data['categories'] = \Model_Category::find()->order_by('order', 'asc')->related('sub_categories')->order_by('order', 'asc')->get();
All of a sudden I get this error when I'm trying to call the save() method on creating a new record (plain and simple, no relations).
Backtrace;
The code in my app hasn't changed for weeks, I only update fuel (develop) once every few days. The code is up to date as of 30min ago.
For singular relations this is relatively easy: HasOne-BelongsTo always goes both ways and loading the one tells you all about the other way around.
Same goes for loading a HasMany relation, you'd know all the BelongsTo go right back.
But loading a BelongsTo to a HasMany back is partial (only the 1, instead of the possible many). This might be supported in a limited way:
$article = Model_Article::query()->related('user')->get_one();
$article->user->articles[$article->id]; // works
$article->user->articles; // lazy loads the articles
$article->user->articles[$unloaded_id]; // also lazy loads
Not yet sure if this is possible, one way might be to change how this works. Use ArrayAccess instead of objects to wrap plural relations.
to_array() doesn't seem to work when using get() method for a model, since it returns an array of objects, get_one() however works.
ErrorException [ Error ]: Call to a member function to_array() on a non-object
I'm getting this when using add_model in a validation in a module controller.
I think that in orm/classes/observer/validation.php on line 16 it should be:
class ValidationFailed extends \Exception {}
instead of
class ValidationFailed extends Exception {}
This is more intuitive to use. Eg:
public function before_insert()
{
$this->foo = 'bar';
}
as oposed to :
protected static $_observers = array(
'Model_Comment' => array('before_insert'),
);
public static function orm_notify($model, $event)
{
if ($event === 'before_insert')
$model->foo = 'bar';
}
It only needs a small adition to work like this. Eg: add this at the bottom of observe() in Orm\Model
:
if (method_exists($this, $event))
{
$this->{$event}();
}
Jelmer, I was reading http://fuelphp.com/forums/topics/view/1198 and the confusion with the kind of objects the find method can return.
I think perhaps is better the find method only to accept an id or an options array, and implement the first and last options as methods, like this:
$model->find()->where()->last();
or
$model->find()->where()->get_one('last');
I know you like consistency :). I think this way improves that and avoids confusions, besides helps to refine queries.
Just a suggestion.
Best regards. Santiago.
it would be good if Orm supported joining (/or switching) of multiple databases (of course without support for joining between connections)
a) setting database models in each group (if is not set will use the default connection) in case that the application needs to connect to multiple databases
or
b) Possibility switching database group? in controller? (for example when application uses more languages โโ- but tables are same - one database = one language)
I have 2 models User and Company. Company has many users and belongs to creator (1 user, delete = false). User belongs to company.
When I delete a user from the company, I get the error: Orm\FrozenObject [ Error ]: No changes allowed.
Also another problem with stationwagon, I have Articles and Category (Category has many articles. Article belongs to category).
When I delete an article, instead of removing the article it removes the category that the article belongs to which is kinda confusing.
I wasn't able to trace errors.
I have some code which takes an instance of Model_Subscriber ($sub) and creates an instance of Model_List ($list) under $sub->lists - e.g. $sub->lists[] = $list;
The code then adds a few instances of Model_SubscriberField to $sub->fields
Following this, I call a save() on $sub.
This results in the Subscriber being inserted into the right table. It also results in the data from the instances of Model_SubscriberField in $sub->fields being inserted into the right table. However... the new instance of Model_List from $sub->lists is not inserted.
If I call save() on $sub directly after adding the list to $sub->lists and again after adding the fields to $sub->fields this works as expected. I have also checked that $sub->lists has not been modified between when I add to it and when I call save().
Is it by design that save() on $sub will not save multiple relations and as such, does this need documenting; or is this a bug?
The code is built as a package and I can provide this if the answer/solution is not obvious.
I think there is an issue with primary key when it's called something else other than "id"
protected static $_primary_key = array('user_id');
Fuel\Core\Database_Exception [ 1054 ]: Unknown column 't0.id' in 'field list' [ SELECT COUNT(t0.id) AS count_result FROM users
AS t0
WHERE t0
.username
= 'wwwe' ]
In my project I have parts and orders. When you add a new order it creates a new part with a related part.order . However not every part necessarily has a related order (part.order_id = null) It seems that when to_array trys to parse through my models it errors on this scenario with:
ErrorException [ Error ]: Call to a member function to_array() on a non-object
PKGPATH/orm/classes/model.php @ line 1176
1171 $array[$name][$id] = $r->to_array();
1172 }
1173 }
1174 else
1175 {
1176 $array[$name] = $rel->to_array();
1177 }
1178 }
1179
1180 return $array;
1181 }
When using count in a many-to-many query, it seems to returns the first row too. Instead of only the count_result column
SQL: http://scrp.at/d6113bd3da91cabee0046d4ab982a9da26efe28d
$query = Model_Shows::find()->where('list_id', $id)->related('list')->where('list.list_state', 'active')->related('tags');
$query->count();
[Edit by jschreuder]I edited the main topic, any performance related research or follow-up questions should go here for now.[/edit]
I have a query that has a few nested relations and a couple multi-level nested relations. On a particular page this query returns 1,700 rows. The actual execution time of the query is in the ballpark of .001 ms. However the entire page takes 5-8seconds to load.
I have noticed that the hydrate function gets called about 6,000 times. I am curious if orm isn't catching when objects have already been hydrated and is re-hydrating them?
I want to order-by a find on a Model with a Database_Expression
I've seen the post "order_by random" (http://fuelphp.com/forums/topics/view/1831) and the issue (#37)
It seems that it works with DB requests but not with Model requests.
Warning for 2.
ErrorException [ Warning ]: Illegal offset type
APPPATH/classes/controller/test.php @ line 23:
23: Model_Table::find('all', array('order_by' => array(DB::expr('ISNULL(table.flied)') => 'ASC')));
Warning for 3.
ErrorException [ Warning ]: Illegal offset type
PKGPATH/orm/classes/query.php @ line 363:
363: $this->order_by[$property] = $direction;
Set them with the relation (= they will always be used):
protected static $_has_many = array('rel' => array('where' => array('status_id' => 3)));
Or change them on the fly:
Model_Example::find()->related('rel', array('where' => array('status_id' => 3)))->get();
These need to be clearly documented though and cascading deletes may need some extra logic. This will lead to massive confusion with those not fully understanding what this does (example: "my relations aren't fetched" "did you use where conditions?" "yes" "do your relations match your where conditions" "Oh.")
This adds rudamentary support for database views that can be used for retrieval instead of the normal table.
order_by(DB::expr('RAND()')) or order_by('rand') are not yet supported.
Using find('first') it returns blank because of LIMIT added to the tags table query before the WHERE query
FROM (SELECT `t0`.`tag_id`, `t0`.`tag_type`, `t0`.`tag_name`, `t0`.`tag_slug` FROM `mini_tags` AS `t0` ORDER BY `t0`.`tag_id` ASC LIMIT 1) AS `t0` LEFT JOIN `mini_lists` AS `t1` ON (`t0`.`tag_id` = `t1`.`list_id`) LEFT JOIN `mini_lists_music` AS `t2` ON (`t1`.`list_id` = `t2`.`list_id`) WHERE (`t0`.`tag_slug` = 'supernatural')
This is the alternate syntax query, which works correct. using get_one();
FROM (SELECT `t0`.`tag_id`, `t0`.`tag_type`, `t0`.`tag_name`, `t0`.`tag_slug` FROM `mini_tags` AS `t0` WHERE `t0`.`tag_slug` = 'supernatural' LIMIT 1) AS `t0` LEFT JOIN `mini_lists` AS `t1` ON (`t0`.`tag_id` = `t1`.`list_id`) LEFT JOIN `mini_lists_music` AS `t2` ON (`t1`.`list_id` = `t2`.`list_id`)
There is another bug also with deep relations, but i'm trying to find the exact cause. In this situation i'm getting 'list' related (one-to-one) 'music' related (one-to-many) to 'tags', but when reversing to get tags from list the 'tags' end up empty. I will make a proper post regarding this once i figure out if its my mistake or an error.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.