doctrine2 - use custom query getter - doctrine

I'm using Symfony2 and Doctrine2. How would I go about using a different query for a get method? (poorly worded, let me explain)
i.e. I have an application entity, this has a one to many relation with Version
so in my application entity I have:
/**
* Get versions
*
* #return Doctrine\Common\Collections\Collection
*/
public function getVersions()
{
return $this->versions;
}
This is all good, however, how can I implement a method, such as
getCurrentVersion, where I perform a simple query, such as:
SELECT v.* FROM version WHERE application_id = 1 AND current = 1
and so in a twig template, I can do:
{% for application in applications %}
Name : {{ application.name }}
Current Version: {{ application.getCurrentVersion }}
{% endfor %}
Any help is much appreciated.
p.s. if i'm going about this wrong, please enlighten me.
Thanks.
EDIT: http://www.doctrine-project.org/docs/orm/2.1/en/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys Seriously?!
EDIT, I really don't want to do this, it is unnecessary and resourcefully wasteful:
public function getCurrentVersion
{
foreach($this->versions as $version)
{
if($version->current)
{
return $version;
}
}
}

Take a look into https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/working-with-associations.html#filtering-collections
I believe that's what you've been looking for.

First, you shouldn't touch the database from your entity. It's considered bad practice to inject an entity manager into a managed entity, and it has no way of knowing about the data layer otherwise.
To do what you're asking, you can use a custom entity repository with a getCurrentVersion method that takes an id or instance of the entity and runs the query on it:
// class Foo\Bundle\Repository\BarRepository
public function getCurrentVersion(MyClass $class)
{
$dql = 'SELECT v FROM Namespace\Of\Version\Class v WHERE v.application_id=:id AND current=1';
$query = $this->_em->createQuery($dql);
$query->setParameter('id', $class->getId());
$version = $query->execute();
return $version;
}
Note that if you're only expecting one version, you can use $query->getSingleResult(); instead of $query->execute();
However, it sounds like you're trying to do versioning/history on some of your classes, in which case, you should check out the Gedmo DoctrineExtensions and associated Symfony2 bundle; specifically, have a look at the Loggable behavioral extension, which handles versioning of your entities transparently.

Related

PhpStorm No Usages for get Attribute

I'm using the latest version of PhpStorm (2022.3.1) and Laravel 9+. This is not a huge issue, but rather a major eyesore.
For some reason when I use a Model to create a custom Attribute like this:
public function getFormattedStartDateAttribute(): string
{
if (!$this->start_date) {
return 'TBD';
} else {
return $this->start_date->format('M. d, Y');
}
}
And use in the view like this or this:
Date {{ $event->formattedStartDate }}
Date {{ $event->formatted_start_date }}
PhpStorm still says the method has no usages?
Image:
Is there a way to fix this? I've tried reindexing my project. This is a new feature called Code Vision.
The issue is very easy to solve, you can use barryvdh/laravel-ide-helper and then run php artisan ide:model, that will go over the models and create a PHPDoc block (you can add options and create them in the same model file or in a new file where you only have this PHPDock blocks), and PHPStorm will read this doc block and know when you are calling attributeWhatever is what type.
It will add this in your case:
/**
* #property string $formattedStartDate
*/
This way, any IDE that is capable of understanding PHPDock blocks, will understand that when you do $class->formattedStartDate, you are refering to that one, and it is of type string.
BUT, no IDE (unless using a plugin that I am not aware of) will understand that getFormattedStartDateAttribute -> formattedStartDate, so you will still get no usages for getFormattedStartDateAttribute, but at least you can track formattedStartDate and do whatever you want with it.
One quick tip, if you are using Laravel 9+, please change that code from:
public function getFormattedStartDateAttribute(): string
{
if (!$this->start_date) {
return 'TBD';
} else {
return $this->start_date->format('M. d, Y');
}
}
To:
public function formattedStartDate(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::get(
function ($value, $attributes) {
if (! $attributes['start_date']) {
return 'TBD';
} else {
return $attributes['start_date']->format('M. d, Y');
}
}
);
}
Why? Because using getFormattedStartDateAttribute is the old way, Laravel 9+ made it easier, read about Accessors.
See that getXXXXAttribute and setXXXXAttribute is not even present on the documentation anymore.

Resolving service based on model in AppServiceProvider

To give you a brief view: I am trying to make redirect based on conditions. If request satisfy conditions of given destination you will get redirect to certain destination.
I have Destination model which has many conditions (Condition model). I have many Conditions which extends basic Condition model like DateCondition, LocationCondition etc (made by polimorphic relation). Each of condition type should has 'service' which tells if given condition match do the request. Example DateCondition should has own DateConditionMatcher which implements ConditionMatcherInterface.
(just public function match($condition, $request)).
I would like to write it with Open Closed principle from SOLID. Firstly I thought to add getMatcher() function straight to condition model and return different ConditionMatcher for each condition type, but some of ConditionMatchers need some other services passed in contructor, so it would force me to inject them also in Condition models which is bad pratice.
Maybe using Contextual binding in ServiceProvider could resolve this but how?
I have no idea how to couple Model to to the right one ConditionMatcher to then use it freely like this:
foreach ($destination->conditions as $condition){
$isMatched = $this->conditionMatcher->match($condition, $request);
}
To always have correct ConditionMatcher under $this->conditionMatcher.
I hope someone understood my not very clear message.
I had an idea overnight to make service which keeps all the matchers. The only downside to this solution is injecting every matcher.
class ConditionResolver implements ConditionResolverInterface
{
/** #var ConditionMatcherInterface[] */
private $conditionMatchers = [];
public function __construct(
DateConditionMatcher $dateConditionMatcher,
TimeConditionMatcher $timeConditionMatcher,
LocationConditionMatcher $locationConditionMatcher
) {
$this->conditionMatchers[DateCondition::class] = $dateConditionMatcher;
$this->conditionMatchers[TimeCondition::class] = $timeConditionMatcher;
$this->conditionMatchers[LocationCondition::class] = $locationConditionMatcher;
}
}
so now I am able to use correct matcher to given condition in this way(simplified):
foreach ($model->conditions as $condition)
{
$this->conditionMatchers[get_class($condition)]->match($condition, $request);
}
It allows me to inject various of services into that matchers in AppServiceProvider.
$this->app->singleton(LocationServiceInterface::class, function($app){
return new LocationService();
});
$this->app->singleton(DateConditionMatcher::class, function($app){
return new DateConditionMatcher();
});
$this->app->singleton(TimeConditionMatcher::class, function($app){
return new TimeConditionMatcher();
});
$this->app->singleton(LocationConditionMatcher::class, function($app){
return new LocationConditionMatcher($app->make(LocationServiceInterface::class));
});
Overall I think I miessed something and it would be done in more elegant way, but for now I treat it as an answer. If you have better solution, please share :)

How to toggle laravel trashed filter off temporarily?

Say I've got an A, which has a B that has a C that has a D. I want to go from A to D, but any one (or all) of the objects might have been deleted. So I have to do this:
$d = $a->b()->withTrashed()->first()->c()->withTrashed()->first()->d()->withTrashed()->first()
Which is horrible. I would really rather do this:
turnOffTrashedFilter();
$d = $a->b->c->d;
Does laravel have such an ability?
Note that this is just an example - the situation that prompted this question is actually a lot more complicated, with various calls nested in other calls such that it's not practically possible to use withTrashed as above. I need to turn off the filter for the duration of the request, without having to modify huge swathes of code to incorporate two parallel paths.
No built-in, but it can be done
There is no built in way to disable the automatic soft delete filtering. However, it is possible. The soft delete filter is a global scope, added to the boot method of the class. It can be removed like so:
\Event::listen('eloquent.booted:*', function($name) {
$name = substr($name, 17); // event name is "eloquent.booted: some/class"
$class = new \ReflectionClass($name);
$prop = $class->getProperty('globalScopes');
$prop->setAccessible(true);
$scopes = $prop->getValue();
foreach ($scopes as $c => &$s) {
unset($s['Illuminate\Database\Eloquent\SoftDeletingScope']);
}
$prop->setValue($scopes);
});
This hooks into the booted event, which is fired immediately after the global scope gets added to the class. It then opens the (private static) attribute globalScopes, which is a list of the attached global scopes, and removes the soft deleting one. This will prevent the softdelete scope from being attached to any models, provided their static boot method is called after the event listener is added.
Instead of this you can use withTrashed() in your relations:
public function aTrashed()
{
return $this->hasOne(A::class)->withTrashed();
}
public function bTrashed()
{
return $this->hasMany(B::class)->withTrashed();
}
public function cTrashed()
{
return $this->belongsToMany(C:class)->withTrashed();
}
// Then use it
$d = $z->aTrashed->bTrashed->cTrashed;

Configure langs in Laravel Translatable BootForms

I'm new to Laravel Translatable BootForms, and I was wondering something.
When I use this code :
{!!
TranslatableBootForm::text('Nom', 'name')
->required()
!!}
The render is as follows :
I don't know where this language list comes from.
I only want to list some languages specified in my database, as I do with this workaround :
#foreach($availableLangs as $availableLang)
{!!
TranslatableBootForm::text('Nom', 'name')
->renderLocale($availableLang['locale'])
!!}
#endforeach
Which gives me this :
My two questions are :
Where does this language list come from ?
How can I replace it by my own language list ?
Answering the first question may lead to an automatic answer for the second, though)
In Laravel, you should always try to read the Service Providers, they provide important clues about the project structures. Let's try to follow the trail of the function calls:
TranslatableBootForm is a facade and it resolves to and instance of translatable-bootform from the Service Container according to this line:
protected static function getFacadeAccessor() { return 'translatable-bootform'; }
Now, in the file TranslatableBootFormsServiceProvider.php we can see that translatable-bootform is an instance of TranslatableBootForm. So when you call TranslatableBootForm::text, you will be using the Facade which resolves to an instance of TranslatableBootForm
Opening the TranslatableBootForm class, we cannot find the text method, so there should be a __call method. The __call method always returns whatever is returned from the method render. So that's where the action is happening.
Reading the code there, you will find that it gets the locales from a method called locales and it will intersect it with the func_get_args() function to get whatever languages you pass to it. So renderLocale or simply render will do the same thing.
The method locales just returns an array which is by default empty in the class. If we return back to the TranslatableBootFormsServiceProvider we will see that there's an important line:
$formBuilder->setLocales($this->getLocales());
Which gets the locales from Translatable\TranslatableWrapper which is just a wrapper around this file in another package: https://github.com/dimsav/laravel-translatable/blob/master/src/Translatable/Translatable.php
Looking at the configuration file in the laravel-translatable package, we can see the languages:
https://github.com/dimsav/laravel-translatable/blob/master/src/config/translatable.php
Solutions
Now, you can simply copy the file translatable.php in your config folder and set your locales.
Or, you create a new service provider MyTranslatableBootFormsServiceProvider
class MyTranslatableBootFormsServiceProvider extends TranslatableBootFormsServiceProvider
{
/**
* Get Translatable's locales.
*
* #return array
*/
protected function getLocales()
{
// You can return a config key
// return config('yourconfig.locales');
// Or directly the array containing the languages
return ['en', 'fr', 'nl'];
}
}
Then, you will use this provider in your config/app.php instead of the original TranslatableBootFormsServiceProvider
Disclaimer:
I didn't try the code, you might have a bug, but you get the idea now how to find your way around Laravel packages.

Laravel accessing one to one relationship from the views

The code below one runs and the other doesn't. The code runs inside a foreach loop.
Does anyone know why the first one doesn't run?
{{ Status::find($workorder->statuses_id)->name }} //this doesn't
{{ Status::find(1)->name }} //this works
Assuming you have your relationship defined like this...
class Workorder extends Eloquent {
public function status() {
return $this->hasOne('Status');
}
}
You would need to do:
{{ Status::find($workorder->status->id)->name }}
This works for me no issues.
Alternatively, if you want to use the syntax you originally provided you could define a method on the Workorder class like this:
public function getStatusesIdAttribute() {
return $this->hasOne('Status')->first()->id;
}
...but that is a little awkward and likely not the best approach.

Resources