-
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TryFind functions. Generate exception if elements not found. #61
Comments
Which Exception? It cannot be HttpException because these methods may be used in console. If it will be general purpose exception then you'll have to catch it and throw Http one: try {
$customer = Customer::findOne(123);
} catch (\Exception $e) {
// throw 404
}
$id = $customer->id;
$email = $customer->email; To me it's the same as doing: $customer = Customer::findOne(123);
if (!$customer) {
// throw 404
}
$id = $customer->id;
$email = $customer->email; That's why I don't see how this addition is useful. |
For example: If you want 404 code for rest Api, you should change \yii\web\Responce |
And if you're in console app? |
Console app work correctly with \yii\db\Exception, and will correct work with: class NotFoundException extends \yii\db\Exception. |
implements HttpException? Makes no sense in console app. |
Yes, i correct it: HttpExceptionInterface |
Still doesn't make sense to have anything about HTTP in console. |
Ok. More abstraction? :) Which will return constants responceStatusCodeInterface::NOT_FOUND, responceStatusCodeInterface::SOMETHING_ELSE... Which used by \web\Response to produce http return status codes. |
@samdark This "HTTP stuff" is completely transparent for console. Interface may be useless at console level, but this does not mean that it does not make sense - it makes exception more flexible and nobody force you to handle HTTP-related interfaces in console. |
Now console application always return "1" exit code, if catch exception . But it can be useful. |
OK. While I don't see any pros of having these methods myself, I want to hear more opinions. |
I thought it cannot be useful, but now I'm not sure. |
@berosoboy can not or can? |
I think it can be done as controller trait: trait ActiveRecordFinderTrait
{
public function tryFindOne($activeRecordClass, $id)
{
$model = $activeRecordClass::findOne($id);
if ($model === null) {
throw new NotFoundHttpException();
}
return $model;
}
// Other helpful methods
} Then you can use it like this: class ProfileController extends Controller
{
use ActiveRecordFinderTrait;
public function actionView($id)
{
$profile = $this->tryFindOne(Profile::class, $id);
// Other stuff...
}
} |
@samdark Nowadays I do this way, like you suggested: $customer = Customer::findOne(123);
if (!$customer) {
// throw 404
}
$id = $customer->id;
$email = $customer->email; But today I'm thinking that such methods (tryFind functions) can be useful. |
It is just one more row. Is it really useful? |
I'm not sure. |
It`s common use case. Find element by identificator. Insert everywhere checks, it is lazy. You documentation show it :) |
I was wondering something like this: // Inside ActiveRecord
public function tryFindOne($pk) {
if (($model = static::findOne($pk)) !== null) {
return $model;
} else {
throw new ModelNotFoundException('Model of class ' . static::class . ' not found with pk = ' . $pk);
}
} So ModelNotFoundException could be handled separately in a custom ErrorHandler component, or in a custom ErrorAction. Regardless of the application is web or console. |
I like the idea, currently what I do is implement the find method on the controller and throw exceptions. Doing return type checks makes it more complicated and is more error-prone than throwing exceptions. Even if user code doesn't handle the exception, it'll be a clear error. Whether it is in the framework or not, it is of course easy to add to your base AR class. |
Let's have pros and cons. Nulls
Exceptions
|
In examples you use only one find method. Real code use several find methods: $user = User::tryFindOne(['id'=>$params['id']]);
$newCompany = Company::tryFindOne(['id'=>$params['company_id']]);
$invoice = Invoice::tryFindOne(['id'=>$params['ivoice_id']]); Nothing need more. Execption will be cathed in \yii\base\ErrorHandler and writed to log. Working with incorrect params will be interrupted. It can use only one catcher for several find methods. using null you should check every return value: $user = User::findOne(['id'=>$params['id']]);
if (!$user) {
}
$newCompany = Company::findOne(['id'=>$params['company_id']]);
if (!$newCompany) {
}
$invoice = Invoice::findOne(['id'=>$params['ivoice_id']]);
if (!$invoice) {
} You writting about error:
In most of errors it's really so, but not in all $invoice = Invoice::findOne(['id'=>$params['ivoice_id']]);
$invoiceInfo = json_encode($invoice); // Will be coorect json format
$queueInvoicesProcessor->sendNewInvoice($invoiceInfo); You will receive error in remote part of code, may be in next week. Exceptions
|
I like @analitic1983 's arguments. I'm not a fan of adding yet more static functions to the AR base class though. I'd rather not use the shortcuts and do something like this: // Edit: I realize this is not a good solution since the return type of `one()` will still need to include `null`.
User::find()->where(['id' => 123])->expect(1)->one();
// This one does allow for proper typing.
User::find()->where(['id' => 123])->requireOne(); I think it's a query concern not a model one. Shorter code doesn't necessarily make it better imo:
Edit: my preferences in this case are about the approach to doing this, not so much about the names of the functions. |
You're describing only one situation when you need all three find-s to find a record in order to continue. There's another case when not finding a record is fine. Mostly it's about non-ID conditions: $primaryOperator = null;
try {
$primaryOperator = User::tryFindOne(['type'=>'primary_operator']);
} catch (RecordNotFoundException $e) {
// do nothing
}
$secondaryOperator = null;
try {
$secondaryOperator= User::tryFindOne(['type'=>'secondary_operator']);
} catch (RecordNotFoundException $e) {
// do nothing
}
$extraOperator = null;
try {
$extraOperator= User::tryFindOne(['type'=>'extra_operator']);
} catch (RecordNotFoundException $e) {
// do nothing
}
$operator = $this->chooseOperator($primaryOperator, $secondaryOperator, $extaOperator); vs current: $primaryOperator = User::findOne(['type'=>'primary_operator']);
$secondaryOperator= User::findOne(['type'=>'secondary_operator']);
$extraOperator= User::findOne(['type'=>'extra_operator']);
$operator = $this->chooseOperator($primaryOperator, $secondaryOperator, $extaOperator); |
@samdark we are talking about adding functionality right? Not replacing the existing functionality? So you're free to use the current syntax in case you are fine with something failing? |
Ah. Right right. Then I agree to add it. Looks useful. |
Then the question that's open, is how. OP suggests adding static shortcut function(s) to AR similiar to |
I think that the AR is not responsible for this is. |
imo, the Model::findOrFail($pk) is shorter, but in just a few cases. If you have a simple crud operation it's okay, but if you need to find (findOrFail) for example 4 objects you have to add 4 try catch to each objects findOrFail method which is the same amount of code with the original findOne method. |
I think that these methods will confuse developers, because they add (new?) functionalities that can be checked with a simple null comparison. Less methods, more readability. |
@mrbig00 I think you're misunderstanding some things. When catching exceptions you only need to catch once. (Or even don't catch it but let your error handler handle it.) The 2 options are: User::findOrFail(); // Only supports the `findOne()` syntax.
User::find()->oneOrFail(); // Works for any query
```php
In both cases the name of the function is a separate discussion where I don't have a hard preference.
I do have a preference for the 2nd case since that is implemented on the query. |
Checking nulls is not the same as throwing exceptions. Regardless, you are free to ignore the new functions, they will be documented so that should combat any confusion. |
@SamMousa Said that, if you manage exceptions, you should check every type of exception (database not reachable, wrong field in sql, etc..), otherwise you will retrieve the same result of findOne() (simply record missing). Finally, IMHO, when in reference I see two or more similar methods, I think to the reason because they exist, and this could be confuse. |
@SamMousa Besides, this is only shortcut for throwing exception yourself or creating own method/trait which will implement it. Using it at query level will only make this shortcut harder to use. |
@rob006 it is still query functionality. We have all kinds of shortcut methods for query: What's wrong with having a method that returns 1 record and throws an exception if it doesn't exist?
Writing 10 extra characters doesn't really make it that much harder. And having stricter typing ( |
How do you want to get stricter typing at query level? At model level you can use |
The important thing for me is removing You're right that if we want to have the correct type hint out of the box the shortcut method is a requirement. Still in that case I'd like the shortcut method to forward the call to the query object instead of implementing it in the AR class itself. |
Yes, having it in custom query object is an option we can show in the guide but I don't think it belongs to generic Query object. |
So we don't add this functionality to the core? |
We can in static shortcuts in AR instances but something tells me that these should be part of Repository classes. |
Probably true, currently the static functions are our repositories... |
So I think it's better to create a section in the guide or make a mini-book about architectural patterns with Yii and document it there. |
Idk, if this has been mentioned, but exception mechanism is expencive in any language. So, that is not great to force usage of slow mechanism in a framework that is supposed to be fast |
@mattrh Exceptions are not slow. Not if you compare it with the whole process of fetching record from database and instantiating ActiveRecord object. |
In the current implementation this could be done
$customerQuery = new ActiveQuery(Customer::class, $this->db);
$customer = $customerQuery->withNotFoundException()->findOne($condition);
$customerQuery = new ActiveQuery(Customer::class, $this->db);
$customer = $customerQuery->findByCondition($condition)->onePopulate(true); In both variants for |
Now can be realized as a Related #344 |
Create tryFind(), tryFindOne(), tryFindByPk() wrappers. Wich will generate not found exception, if element not found.
It`s more simple to log errors.
Example from documentation: http://www.yiiframework.com/doc-2.0/guide-db-active-record.html
in bad case will failed on line $customer->id; instead of correct error in logs.
The text was updated successfully, but these errors were encountered: