How do you optimize the DB queries made by sonata admin in the list and edit views ?
i LeftJoined some queries that i made using the querybuilder in my entity repository , this already helped a lot, brought my queries down from 100+ to about 22.
But the remaining queries are the ones that happen automatically by using the formbuilder and listmapper.
Is there anyway i can further optimize the queries made by those classes ?
im not even sure at this point where the queries are made... i tried to overwrite the findBy, findAll methods of the repository but they seem to use something like
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->load($criteria, null, null, array(), 0, 1, $orderBy);
Not sure how i can add a join statement to that...
And i guess it's not just in the sonata admin but also for queries that i use in the front-end that use the built-in EntityRepository find, findAll, findOneBy etc... functions.
In your admin class override the createQuery() method like this :
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query
->addSelect(...)
->leftJoin(...) // use $query->getRootAlias() here
;
return $query;
}
For the edit view it's a little bit more tricky because you need to override the sonata controller by extending Sonata\AdminBundle\Controller\CRUDController and then override the editAction() method.
The controller name can be specified in the third argument of the service declaration.
I know it some month but if it can help someone :
I didn't wanted to change the controller so i take a look inside the code.
In your admin class, you can override the function getObject($id):
// code from sonata admin class
public function getObject($id)
{
$object = $this->getModelManager()->find($this->getClass(), $id);
foreach ($this->getExtensions() as $extension) {
$extension->alterObject($this, $object);
}
return $object;
}
Then put your own code in and return your object.
That's all, no need to override sonata controller.
Related
I am making a project with Laravel 5.6 and at the moment I am making a sidebar with links to access the functionality of specified controller. f.e. if I am in posts blade, it will show PostsController methods for the sidebar.
The problem is that every controller has different amount of methods, and I wouldn't want to make a mess with 10 different static layouts for sidebars.
Is there a way to access controller methods thru functionality that returns all methods of the controller to a view?
Or am I thinking this wrong.. If someone knows a better solution for this i'm all ears. :)
I know I can install packages for functionality but I want to know before that is there any simple solution.
EDIT1:
get_class_methods($this) returns following value:
Returned Methods of a Controller
I can add a validator that checks if "index" or "create" is present. Guess my problem was solved, thank you all who answered.
EDIT2:
The code that dumps the returned methods.
public function index()
{
$events = Event::all();
dd($controller = get_class_methods($this));
return view('events.index', compact(['events', 'controller']));
}
You could use the ´get_cass_methods´ function to grab all the methods on the controller class
function index() {
$methods = get_class_methods($this);
return view('posts', compact('methods'));
}
if you want to filter out methods from the parent class
function index() {
$methods = array_diff(get_class_methods($this),get_class_methods(get_parent_class()));
return view('posts', compact('methods'));
}
I want to add some joins onto my Auth::user() query. How do I do this without creating a brand new query? I just want to be able to make the default call of Auth::user() different than:
SELECT * FROM `users` WHERE `id` = ?
to
SELECT * FROM users INNER JOIN user_icons ON user_icons.ID = users.iconid WHERE `id` = ?
I'm using the default model User class.
Laravel provides a way for you to extend the Auth functionality. First, you need to create a class that implements the Illuminate\Auth\UserProviderInterface. Once you have your class, you call Auth::extend() to configure Auth with your new class.
For your case, the easiest thing for you to do would be to create a class that extends Illuminate\Auth\EloquentUserProvider. You'll want to update the retrieveBy* methods to add in your custom joins. For example:
class MyEloquentUserProvider extends Illuminate\Auth\EloquentUserProvider {
public function retrieveById($identifier) {
return $this->createModel()->newQuery()->join(/*join params here*/)->find($identifier);
}
public function retrieveByToken($identifier, $token) {
// your code with join added here
}
public function retrieveByCredentials(array $credentials)
// your code with join added here
}
}
Once your class is fleshed out, you need to tell Auth to use it:
Auth::extend('eloquent', function($app) {
return new MyEloquentUserProvider($app['hash'], $app['config']['auth.model']);
});
The first parameter to the Auth::extend method is the name of the auth driver being used as defined in app/config/auth.php. If you want, you can create a new driver (e.g. 'myeloquent'), but you'd need to update your Auth::extend statement and your app/config/auth.php driver.
Once all this is done, Auth::user() will end up calling your MyEloquentUserProvider::retrieveById method.
Fair warning: I have not actually done this myself, and none of this is personally tested. You will probably want to check out the documentation (L4.1 docs, L4.2 docs) and look at the Laravel code.
Other notes:
People have already chimed in that this is probably not what you want to do. However, the this information may be helpful to you and others looking to extend Auth for some other reason.
Considering your inner join, if a user does not have an associated user_icons record, Auth::user() will not return a record anymore, and the user probably won't be able to log in at all.
If you have 1:n relation:
Add a "icons" table to you database with a foreign key "user_id".
Add a "Icon" Model to your models.
<?php
class Icon extends Eloquent{
...
}
?>
In Model Class "User" add a function:
public function icons() {
return $this->hasMany('Icon');
}
Now you can do this:
$userIcons = Auth::user()->icons();
Lets see my architect:
Model:
// links table: (ID, LINKNAME)
Class Link extends Link_base
{
}
Controller:
public function index()
{
$this->links = new Doctrine - here I build the query, SELECT, ORDER BY, etc
}
in this example, the model can be remain empty (no serious logic), all I need is a select with an order by. Im not sure I can use Doctrine in controller though - should I remake it like this?
Class Link extends Link_base
{
public function getLinks()
{
return new Doctrine - here I build the query, SELECT, ORDER BY, etc;
}
}
Controller:
public function index()
{
$this->links = Links::getLinks();
}
Im not sure which way seems to be OK. Of course, when selecting needs a more complex, formatting todo-s, it goes to the model or helper - but I feel like I just made a new (unnecessary) layer. This getLinks() used only once. In other words: Doctrine may be only used in model, or can it be used in controllers too?
Your entities (or models if you prefer that name) should not know how they are saved to / retrieved from the database. They should just be simple PHP objects, only containing a number of properties (corresponding to the database columns) and their getters and setters.
(If you are interested, read a bit about the single responsibility principle which states that every class should have one, and only one responsibility. If you make your entities both responsible for storing data and knowing how to save that data in the database, you will have a greater chance of introducing bugs when one of those things changes.)
You can fetch entities from inside your controller:
<?php
namespace Your\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class LinkController extends Controller
{
public function fooAction()
{
$links = $this->getDoctrine()
->getRepository('YourBundle:Link')
->findAll();
// do something with the result, like passing it to a template
}
}
However, you might need a more complex query (that includes sorting and filtering) and you might need to run that query from multiple controllers. In that case, you don't want to duplicate that logic to multiple controllers, you want to keep that logic in one central place.
To do so, create a repository:
<?php
namespace Your\Bundle\Repository;
use Doctrine\ORM\EntityRepository;
class LinkRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(
'SELECT l FROM YourBundle:Link l ORDER BY l.name ASC'
)
->getResult();
}
}
And add the repository class to your mapping:
Your\Bundle\Entity\Link:
type: entity
repositoryClass: Your\Bundle\Repository\LinkRepository
(check the Symfony's documentation about custom repositories if you're using XML or annotations instead of Yaml)
Now in your controller, you can simply update your fooAction method so it uses your custom repository method:
public function fooAction()
{
$links = $this->getDoctrine()
->getRepository('YourBundle:Link')
->findAllOrderedByName();
}
For more information, Symfony's documentation includes a great article about Doctrine. If you haven't done so already, I'd definately recommend reading it.
I have News model, when i query news, i want it brings news where status = 1 as default.
News::all(); // select * from news where status = 1
News::where('anotherColumn',2)->get(); // select * from news where status = 1 and where category = 2
Is this possible? What i want is so similar to soft delete feature (it gets where deleted_at is not null and if all data is wanted withTrashed function can be used).
I looked docs but i couldn't find anything helpful. Also, i tried to handle it in construct at News model but it didn't worked either.
Thanks.
I normally override newQuery() for this. newQuery() is the method that Eloquent use to construct a new query.
class News extends Eloquent {
public function newQuery($excludeDeleted = true) {
return parent::newQuery($excludeDeleted)
->where(status, '=', 1);
}
}
Now your News::all() will only output your news with status = 1.
It's been already mentioned but here is a quick example using global scope which might be the best current solution since you wont have to override Eloquent methods and would result into the same behavior but with more control of your model.
Just add this to your model :
protected static function boot()
{
parent::boot();
static::addGlobalScope('exclude_deleted', function (Builder $builder) {
$builder->whereNull('deleted_at');
});
}
You can also create a child Scope class and reuse it for multiple Models.
For more information, Laravel doc explained pretty much everything about it:
https://laravel.com/docs/5.6/eloquent#global-scopes
I think the closes you'll get, without actually going in to change some core files...
is Query Scope...
Scopes allow you to easily re-use query logic in your models. To define a scope, simply prefix a model method with scope:
class News extends Eloquent {
public function scopeStatus($query)
{
return $query->where('status', '=', 1);
}
}
Utilizing that scope
$news = News::status()->get();
$news2 = News::status()->where('anotherColumn',2)->get();
Its not quite what you wanted...but its definitely a little shorter than typing
News::where('status','=',1)->get();
over and over
I am looking for a way how to attach a behavior to model displayed in a grid view in Yii Framework. The grid view is using CActiveDataProvider and I need every $data element to have a behavior attached to it. The model shouldn't attach the behavior after construct, since it is related to the grid view only.
Thanks
You can create the following class to use to create a data provider.
ActiveDataProvider extends CActiveDataProvider{
public function getData(){
$data = parent::getData();
foreach($data as &$model){
$model->attachBehavior('aName', new mybehavior());
}
return $data;
}
}
Another option (instead of creating a CActiveDataProvider override as suggested in another answer) is to do all your model querying ahead of time and attach your behaviors in your controller. Then pass to a CArrayDataProvider.
Hmm, thinking about it, I like the other approach better :-) I'll leave this for completeness sake.