Eliminate data from active dataprovider in Yii2 - yii2

I'm looking for a way to rotate via data in ActiveDataProvider and erase some rows. I have rather peculiar data structure and I can't get precise data I need with ->andFilterWhere
I know it is possible to use SqlDataProvider but I would prefer to get to know a way to be able to do foreach on every row in ActiveDataProvider and just unset those ones I don't need.
I'm not pasting my code - I use rather simple controller and model generated via Gii - so nothing crazy here.
Will be really glad if anyone could point me into the right direction.

You can do something like this:
$dataProvider = new ActiveDataProvider(['query' => $query]);
foreach ($dataProvider->models as $model) {
//your logic
}

ok, I have found an answer, so I thought I'd share.
To still be able to use active dataprovider and make a funky sql query I want the long way - I prepared a prequery which gave me only id's of rows I wanted and then I passed them into normal search query in ActiveDataProvider's search.
$query->andFilterWhere([
'customer_id' => $tempids,
This way what I got was - I limited the main query only to preselected rows.

You may use getModels() and setModels() to modify list of models available in data provider:
$models = $dataProvider->getModels();
foreach ($models as $index => $model) {
if (/* some condition */) {
unset($models[$index]);
}
}
$dataProvider->setModels($models);
But this is inefficient (you're creating unnecessary models) and will does not work correctly with pagination (you may get less records on page than you want, including edge case with empty page). It is better idea to filter records on query level - if you're able to filter them using SQL, you should be able to do this using ActiveQuery. It should be possible to add most conditions using andWhere(), including handling subquery:
$subquery = (new Query())->from(/* ... */)-> // ... rest of conditions
$dataProvider->query->andWhere(['customer_id' => $subquery]);

Related

How select a single row october cms

How to select a single row on october cms?
How can a simple thing be so complicated here?
I thought it would be something to help us and not to disturb something that is as simple as
SELECT * FROM `engegraph_forms_membros`
Here it's like fighting against demons without a bible, oh god why?
Why make the query difficult for newbie?
I understand you don't speak English natively but you should watch every single one of these videos.
Does the record belong to a model in a plugin? Here are the docs on how to work with models.
You make a plugin, set the database which creates models, and then make components to be ran in your CMS Pages.
In a component.php file you can have something like this: Here I am calling the model class Agreements with use Author\Plugin\Models\Agreements;. This allows me to run a function/method to retrieve all agreements or one agreements using laravel's eloquent collection services.
Lets say we have the ID of a record. Well we can either call on the Agreements model with ::find or with ::where. You will noticed I have two functions that essentially do the same thing. ::find uses the primary key of the models (in my case the id) and will return a singular record. *Note that find can take an array and return a collection of records; like ::where. Using ::where we are going to look for the ID. *Note ::where always returns a collection which is why I have included ->first().
<?php namespace Author\Plugin\Components;
use Session;
use Input;
use Crypt;
use Db;
use Redirect;
use Illuminate\Contracts\Encryption\DecryptException;
use October\Rain\Support\Collection;
use Author\Plugin\Models\Agreements;
class GetAgreement extends \Cms\Classes\ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Get one agreement',
'description' => 'Get an agreement to change or delete it'
];
}
public function onRun() {
$this->page['agreement'] = $this->getWithFindAgreement;
}
public function getWithFindAgreement() {
$id = 1;
$agreement = Agreements::find($id);
return $agreement;
}
public function getWithWhereAgreement() {
$id = 1;
$agreement = Agreements::where($id)->first();
return $agreement;
}
}
If for some reason you aren't working with models, here are the docs to work with Databases. You will have to register the use Db; facade.
Here call the table you want and use ::where to query it. *Note the use of ->first() again.
$users = Db::table('users')->get();
$user = $users->where('id', 1)->first();
There are two simple ways to select a single row:
This will give you the'first' record in the selected recordset.
SELECT top 1 * FROM `engegraph_forms_membros`
This will select all the records that meet the predicate requirement that the value of <columnname> is equal to <value>
SELECT * FROM `engegraph_forms_membros` where <columnname>=<value>
If you select a record where multiple values meet that requirement, then you can (randomly) pick one by combining the solutions...
SELECT top 1 * FROM `engegraph_forms_membros` where <columnname>=<value>
But be aware that without an ORDER BY clause, the underlying data is unordered and prone to change uncontrollably, which is why most people (including your boss) will find the use of 'Top' to be improper for real use.

Yii2 - Assign a "fix" condition

I am building an app, where an account can have many services, all the information is related to a service. In example:
Account A has 3 services and each service has pages.
In order to avoid someone modifying the service_id when saving a page, at the moment I do:
if(Yii::$app->request->isPost) {
$post = Yii::$app->request->post();
$model->load($post);
$model->service_id = $this->service->id;
}
Where $model->service_id = $this->service->id helps me assign the selected service_id after loading table to model and avoid someone sending service_id from the form.
But in case someone in the future, develops "documents" I would like to avoid the developer handling this service_id on all the queries.
So First it I thought I could try:
public function beforeFind($queryData) {
parent::beforeFind();
$queryData['conditions'] = array('service_id' => 2);
return $queryData;
}
But still needs the developer to implement it. So maybe is there a way to create a "BaseService" model where all other service related models should extend from but not sure how to:
Add the condition from the parent model?
How to pass the id to this model so it keeps it during all queries?
Maybe there is a simple solution, and I am overcomplicating myself due long hours working, not sure.
That is a default condition to apply for all queries. In case your application is built on top of ActiveRecord classes (not performing direct SQL queries or on the fly QueryBuilder) then you can simply override the find() method inside your Model class:
public static function find()
{
/* you can add more dynamic logic here */
return parent::find()->where(['service_id' => 2]);
}
By default, all controllers in Yii2 are using Model::find() to retrieve data from database, adding such condition should be enough to not retrieve anything with a different service_id than 2. Direct http GET requests by ID should then output 404 if that condition isn't satisfied and retrieving them as relational data within a different model class should return a filtered array.
IMPORTANT: To not break that implementation you need to:
Always use
ActiveRecord. Otherwise you'll need to manually add the condition to your queries.
(This is not correct) Be carful on when to use asArray() as it omits ActiveRecord features (See note and explanation
here). Otherwise you need to manually re-declare the condition like: Account::find()->where(['service_id' => 2])->asArray()->all();
Always use andWhere() to merge conditions because where() will override/ignore the default one. Example: Account::find()->andWhere('age>30')->all();
to reuse such filters you can put them into a custom ActiveQuery.
in your ActiveRecord:
public static function find() {
return (new ActiveQuery(get_called_class()));
}
ActiveQuery:
public function service($service = 2) {
return $this->andWhere(['service_id' => $service]);
}
your model based on your ActiveRecord:
public static function find() {
return (new ActiveQuery(get_called_class()))->service(2);
}
alternatively
$model->find()->service(1);
also, this might be of interest (setting Default values per scenario)

ActiveDataProvider display value yii2

i just want to know that is it possible to display data of $dataprovider independent of any gridview.
Like if the $dataprovider contains value of a particular query result than it will be stored as array in that.
So how can i call only one value from $dataprovider
For example my $dataprovider contains value of all select * from user where status=10
so is it possible just to display $dataprovider->user>name just first record.
Thank you
You can certainly access $dataProvider->models as suggested by scaisEdge and get the single model you need, but that is not very efficient, because it executes the query and retrieves all models only to be discarded later.
You can, however, get access to dataProvider's query object, and with it get the single model you need.
$newQuery = clone $dataProvider->query;
$model = $newQuery->limit(1)->one();
Cloning the query is not necessary if it's ok to modify the dataProvider (if you don't use it anywhere else).
UPDATE:
$this->title = isset($dataProvider->models[0]->name) ? $dataProvider->models[0]->name : 'empty result';
Yes inside a dataProvider you can find the models. These are a collection of model and then if you iterate over this collection you can use the single model data e.g.:
foreach( $dataProvider->models as $myModel){
echo myModel->field1;
echo myModel->filed2:
........ // and so on for all the data you need
}
You can see this Yii2 framework doc for ref

Find call returning outdated info immediately after Save

In my application, an action takes some user generated input and uses it to update an entry in the database.
The relevant code resembles the following:
$this->Model->save($data);
$result = $this->Model->findById($id);
The problem is that the contents of $result are outdated. That is, $result contains the record as it was before the save.
I'm assuming that the entry just isn't updated until the function returns. However, I can't do the obvious
$result = $this->Model->save($data);
because this method does not preserve relationships. In this case, the model belongs to another model. I need to be able to get the updated record and the record it belongs to.
See if
$result = $this->Model->findById($id);
is loading from the cache, not the database.
Take a look at:
http://fredericiana.com/2007/09/05/cakephp-delete-cached-models-on-database-change/
http://snook.ca/archives/cakephp/delete_cached_models/
Personally, I've never liked Cake's magic find functions. I find them difficult to add extra conditions/joins/etc to and I much prefer to type a few more lines and get something specific that I can easily adjust.
Sounds in this case like the magic find function is returning from the cache, and you should try a regular find call instead:
$this->Model->save($data);
$result = $this->Model->find('first', array('conditions' => array('id' => $id)));
However, you should try and narrow down your problem and find out exactly what it is for future reference. You could try manually clearing the cache before you do your magic find and see if you get the correct response:
$this->Model->save($data);
Cache::clear();
$result = $this->Model->findById($id);
Another unlikely yet possible option is that your $id variable may not be pointing to the correct model row, and your save might be creating a new one so that when you findById($id) the $id is different to the ID of the row you've just saved/created. In this case, it's worth doing a debug($id) or $this->log('ID is: ' . $id, 'my_test_log'); before and after your safe or at intervals through your code to work out what is happening and where.

Linq to Sql: Am I doing this right?

I am working on a database access layer project and decided to use Linq to SQL for it. One of the things I wanted to do was provide an API that can take Linq Expressions as arguments to retrieve data. For instance, I wrote this API now that looks like this:
public Result GetResults(System.Linq.Expressions.Expression<Func<Result, bool>> predicate)
{
Result result = db.Results.SingleOrDefault(predicate);
return result;
}
You can then use this API to query the database for a Result row that satisfies certain conditions, for example:
Result result = Provider.GetResults(r => r.ID == 11);
This works very well. I am able to get the one row I want based on my conditions.
Next step was taking this to be able to get multiple objects back from the database.
The way I got it to work was like this:
public List<Result> GetResults(System.Linq.Expressions.Expression<Func<Result, bool>> predicate)
{
List<Result> results = db.Results.Select(r => r).Where(r => r.ID == 11).ToList<Result>();
return results;
}
As you can see, I call a Select with r => r, this gives me back everything and then I use a Where to filter to what I need.
It works... but something tells me that I am doing it really ugly. I could be wrong, but doesn't this pull EVERYTHING out of the Results table then filters it? or does it put together the correct SQL statement that does filter at the database level?
Anyway... I would highly appreciate some guidance on how I can accomplish this task. How do I write an API that takes a Linq Expression as an argument and returns a set of objects from the database based on that expression.
Thanks!
The Select(r=>r) does nothing (except change from Table<T> to IQueryable<T> - but nothing useful). And I assume you intended to pass predicate to the Where?
Indeed, this doesn't pull everything out and filter it - the appropriate WHERE (TSQL) clause is generated. This is possible because of "deferred execution" and "composability" - meaning: it doesn't actually execute anything until you start iterating the data (in the ToList()) - until then you are simply shaping the query.
You can see this by doing somthing like:
db.Log = Console.Out;
and look at the TSQL. Or run a TSQL trace. To make it prettier, simplify it to:
return db.Results.Where(predicate).ToList();
I know you said you wanted to pass a predicate and return a List but have you considered returning an IQueryable
Then you could call:
GetResults().Where(r => r.SomeProperty == "SomeValue")
.OrderBy(r => r.SomeOtherProperty)
.Skip(10)
.Take(10); //etc.
With your current API design you would return all records and then have to get 10, where as the above would return only the 10 you needed...
Just some thoughts...