Cannot get attribute in laravel accessor (October CMS) - php

I'm currently working on a project with October CMS, and trying to use taglist with relation data.
And I'm using custom accessor to get full name from two columns, seems like a very common case.
But I just couldn't get the model's attributes in the accessor.
Here's my code...
class NameOfClass extends Model
{
/** ALL THE CODE GENERATED BY OCTOBER CMS PLUGIN BUILDER */
public function getFullNameAttribute()
{
return $this->firstname." ".$this->lastname;
}
}
And call the accessor in field.yml file...
fieldName:
label: FieldName
descriptionFrom: description
type: taglist
mode: relation
nameFrom: full_name
customTags: false
I can see the accessor gets called just fine, since I can get the value by changing the returned value to plain string.
I've been spent a lot of time researching for solution... any idea?
I'm developing with the official octobercms docker image latest version.

Its not possible due to internal code. so I suggest do not try to solve it. its not solvable :) try alternative
WHY ??
Because from code taglist is designed in a such way that it will work with TAGS.
it will allow to create new tags if there are no selected tag and if tags are existing then it will attache to given record. and this all will work with real attributes.
so its not design to work with virtual attributes.
For more details this is the code how it generates tags
public function getFieldOptions()
{
$options = $this->formField->options();
if (!$options && $this->mode === static::MODE_RELATION) {
$options = RelationBase::noConstraints(function () {
$query = $this->getRelationObject()->newQuery();
// Even though "no constraints" is applied, belongsToMany constrains the query
// by joining its pivot table. Remove all joins from the query.
$query->getQuery()->getQuery()->joins = [];
return $query->lists($this->nameFrom); // <==== LOOK HERE
});
}
return $options;
}
You can see this nameFrom is directly passed to query and query/sql do not know about our virtual field so it wont work.
Alternatively you can use RelationController Behaviors
ref: https://octobercms.com/docs/backend/relations#introduction
if any doubt please comment.

Related

ApiPlatform/Symfony 6 - add searchfilter on custom getter (not in database)

I'm working on a project whit api platform 2.6 and symfony 6.
I have an Entity based on a table from my database.
My API exposes the fields of my table.
but I have a specific need to add information that does not exist in the database.
I create a custom getter, I integrated it into the normalization group, and it shows fine when responding from API.
But I would like to add a filter on this field.
ApiFilter(
SearchFilter::class, properties: [
'customInformation' => 'exact',
It does not work.
How to make Api-Platform take it into account as if it were a field of my database ?
Thank !
William
Duplicate of How to apply an ApiFilter(SearchFilter: class) to a dynamic getter of an entity? [doctrine-orm, api-platform, graphql]
ApiPlatform filters only work on properties for now.
You could probably accomplish that with a custom filter but i find it hard to imagine a case where you need to filter on data that does not exist.
There is probably a change on your conception that will do the job.
Update based on your comment :
I strongly advise you to change this
function getEmergency(): string
{
if ( $this->createdby == 'admin' and createdAt > days + 3) {
return 'urgent';
}
else
{
return 'not urgent';
}
}
To something like this
function isUrgent(): bool
{
return $this->createdby === 'admin' and createdAt > days + 3;
}
Much simplier method since there is only 2 case possible.
Then for your problem with api platform why dont you simply filter like this for example
&createdBy=admin&createadAt[before]=20220105
And inside your class
#[ApiFilter(SearchFilter::class, properties: ['createdBy' => 'exact'])]
#[ApiFilter(DateFilter::class, properties: ['createdAt'])]
class foo {}
Look at this Syntax on the link bellow to better understand date filter with api platform: ?property[<after|before|strictly_after|strictly_before>]=value
https://api-platform.com/docs/core/filters/#date-filter

Eager load model using with but giving it another name - Laravel 5.2

Is it possible to use eager loading using the with method but giving it another name? Something like:
->with('documents as product', 'documents.documents as categories')
I have a documents table that can be product or categories, eager loading is working but not that friendly to retrieve the documents by just the document name instead of what it really is.
This feature is currently not supported in any Laravel version. Your best bet is to duplicate your relations and name them according to your needs. E.g.:
class Post extends Model
public function documents() {
return $this->hasMany(Document::class);
}
public function products() {
return $this->hasMany(Document::class)
->where('type', 'product'); // Scope the query, if necessary
}
public function categories() {
return $this->hasMany(Document::class)
->where('type', 'category'); // Would a Document really be of type 'category', though? Looks like a code smell.
}
}
$postsWithAllDocs = Post::with('documents')->get();
$postsWithProductsOnly = Post::with('products')->get(); // Only eager load Documents of type 'product'
On a side note, you mention that a Document can be a product or category, which logically doesn't seem to make much sense. Part of the issue could probably be resolved by rethinking the structure of your database and relations.
Eager loading tells "load also this relationship data", so next you can access subject->relation without further queries
if you want to rename the relationship maybe you should do it renaming the relationshp in the model, not in the eager loading
you can also bypass this by adding virtual attributes:
function getProductAttribute(){
return $this->document;
}
leaving eager loading on original document
resulting in product attribute that is the same as document:
$subject->product === $subject->document
I asked myself the same question, and since I didn't find a satisfying answer online, here is what I did.
I had:
$o->load('X');
but I wanted the $o object to have attribute Y with the value of X relation. Since I already had the Y relation defined for $o, I couldn't rename X to Y and finish the job. So I did
$o['Y'] = $o->X();
I know this is not the best solution, but it works for my case :)
Note: load and with produce exactly the same number of sql queries - you need to choose the one which is more appropriate for your situation.

Laravel exclude current id from query eloquent results

I am fairly new to laravel and I built a little "similar posts" section. So every post has a tag and I query all the id's from the current tag. And then I find all the posts with thoses id's. Now my problem is that the current post is always included. Is there an easy way to exclude the current id when querying?
I can't seem to find anything in the helper function on the laravel docs site
this is my function:
public function show($id)
{
$project = Project::findOrFail($id);
foreach ($project->tags as $tag){
$theTag = $tag->name;
}
$tag_ids = DB::table('tags')
->where('name', "=", $theTag)
->value('id');
$similarProjects = Tag::find($tag_ids)->projects;
return view('projects.show', ['project' => $project, 'similarProjects' => $similarProjects]);
}
An easy way to solve your issue would be to use the Relationship method directly instead of referring to it by property, which you can add additional filters just like any eloquent transaction.
In other words, you would need to replace this:
Tag::find($tag_ids)->projects
With this:
Tag::find($tag_ids)->projects()->where('id', '!=', $id)->get()
Where $id is the current project's id. The reason behind this is that by using the method projects(), you are referring your model's defined Relationship directly (most probably a BelongsToMany, judging by your code) which can be used as a Query Builder (just as any model instance extending laravel's own Eloquent\Model).
You can find more information about laravel relationships and how the Query Builder works here:
https://laravel.com/docs/5.1/eloquent-relationships
https://laravel.com/docs/5.1/queries
However, the way you are handling it might cause some issues along the way.
From your code i can assume that the relationship between Project and Tag is a many to many relationship, which can cause duplicate results for projects sharing more than 1 tag (just as stated by user Ohgodwhy).
In this type of cases is better to use laravel's whereHas() method, which lets you filter your results based on a condition from your model's relation directly (you can find more info on how it works on the link i provided for eloquent-relationships). You would have to do the following:
// Array containing the current post tags
$tagIds = [...];
// Fetch all Projects that have tags corresponding to the defined array
Project::whereHas('tags', function($query) use ($tagIds) {
$query->whereIn('id', $tagIds);
})->where('id', !=, $postId)->get();
That way you can exclude your current Project while avoiding any duplicates in your result.
I don't think that Tag::find($tag_ids)->projects is a good way to go about this. The reason being is that multiple tags may belong to a project and you will end up getting back tons of project queries that are duplicates, resulting in poor performance.
Instead, you should be finding all projects that are not the existing project. That's easy.
$related_projects = Project::whereNotIn('id', [$project->id])->with('tags')->get();
Also you could improve your code by using Dependency Injection and Route Model Binding to ensure that the Model is provided to you automagically, instead of querying for it yourself.
public function show(Project $project)
Then change your route to something like this (replacing your controller name with whatever your controller is:
Route::get('/projects/{project}', 'ProjectController#show');
Now your $project will always be available within the show function and you only need to include tags (which was performed in the "with" statement above)

How to make models use defaults in Phalcon PHP Framework?

If a table has defaults on certain fields and NULL is not allowed, one would expect the insert script to use those defaults, as MariaDB/MySQL usually does. For example, if the table products has an AI field "id", a required field "name" and two required fields "active" and "featured" which both default to 1, then the query
INSERT INTO products (name) VALUES ('someName');
automatically inserts 1 as the value of active and featured. However, when using Phalcon's models like so:
$product = new Products();
$product->setName('someName');
$product->save();
returns validation errors saying "active" and "featured" are required.
Is there a flag I should provide during model generation in order for Phalcon tools to harvest and input the defaults into Model classes, or another way to make Phalcon automatically use defaults if found? Best approach would be just ignoring the fields that weren't set, I reckon. Can I make the models do that?
You can use a raw database value to avoid that, in specific inserts:
<?php
use Phalcon\Db\RawValue;
$product = new Products();
$product->setName('someName');
$product->setType(new RawValue('default')); //use default here
$product->save();
Or, general before create/update for specific fields:
use Phalcon\Db\RawValue;
class Products extends Phalcon\Mvc\Model
{
public function beforeValidationOnCreate()
{
$this->type = new RawValue('default');
}
}
Or ignore these fields in every SQL INSERT generated:
use Phalcon\Db\RawValue;
class Products extends Phalcon\Mvc\Model
{
public function initialize()
{
$this->skipAttributesOnCreate(array('type'));
}
}
Although I find twistedxtra's answer fascinating from the aspect that Phalcon contains this wicked method to read the column default, I believe from a architectural point of view this might be the wrong approach as you rely on your database to define the defaults of the properties of your model.
I would set the default value when declaring the property and keep the logic in the application layer. But that's just me.
Use Like below
The skipAttributesOnCreate will make sure Phalcon does not attempt to put a a value in that column. The database will apply the default value.
public function initialize()
{
$this->setSource('table_name');
$this->skipAttributesOnCreate(['name_of_column']);
}

Propel: how to remove link made via many-to-many relation

(link to previous question just in case: Struggling with one-to-many relation in an admin form)
I have this many-to-many relation in my Symfony-1.3 / Propel-1.4 project between User and Partner. When the User is being saved, if it has certain boolean flag being true, I want to clear all the links to the partners. Here is what I do at the moment and it doesn't work:
// inside the User model class
public function save(PropelPDO $con = null) {
if ($this->getIsBlaBla()) {
$this->setStringProperty(NULL);
$this->clearUserPartners();
}
parent::save($con);
}
Setting the string property to NULL works; looking at the DB clearly shows it. Thing is however, the USER_PARTNER table still holds the relations between the users and the partners. So I figured I have to clear the links one by one, like this:
foreach($this->getUserPartners() as $user_partner) {
$user_partner->delete();
//UserPartnerPeer::doDelete($user_partner); // tried that too
}
Both don't do the trick.
As I mentioned in my previous question, I am just monkey-learning Symfony via trial and error, so I evidently miss something very obvious. Please point me in the right direction!
EDIT: Here is how I made it work:
Moved the code to the Form class, like so:
public function doSave(PropelPDO $con = null) {
parent::doSave($con);
if ($this->getObject()->getIsSiteOwner()) {
$this->getObject()->setType(NULL);
$this->getObject()->save();
foreach($this->getObject()->getUserPartners() as $user_partner) {
$user_partner->delete();
}
}
return $this->getObject();
}
public function updateObject($values = null) {
$obj = parent::updateObject($values);
if ($obj->getIsSiteOwner()) {
$obj->clearUserPartners();
}
return $this->object;
}
What this does is:
When the boolean flag `is_site_owner` is up, it clear the `type` field and **saves** the object (ashamed I have not figured that out for so long).
Removes all existing UserPartner many-to-many link objects.
Clears newly associated (via the DoubleList) UserPartner relations.
Which is what I need. Thanks to all who participated.
Okey so now you have a many-to-many relation where in database terms is implemented into three tables (User , Parter and UserPartner). Same thing happens on Symfony and Propel, so you need to do something like this on the doSave method that should declare in UserForm:
public function doSave($con = null)
{
parent::doSave($con); //First all that's good and nice from propel
if ($this->getValue('please_errase_my_partners_field'))
{
foreach($this->getObject()->getUserPartners() as $user_partner_relation)
{
$user_partner_relation->delete();
}
}
return $this->getObject();
}
Check the method name "getUserPartners" that should be declared on the BaseUser.class.php (lib/model/om/BaseUser.class.php)
If you are learning Symfony, I suggest you use Doctrine instead of Propel because, I think Doctrine is simplier and more "beautiful" than Propel.
For your problem, I think you are on the good way. If I were you, I will keep my function save() I will write an other function in my model User
public function clearUserPartners(){
// You have to convert this query to Propel query (I'm sorry, but I don't know the right syntax)
"DELETE FROM `USER_PARTNER` WHERE user_id = '$this->id'"
}
With this function, you don't must use a PHP foreach.
But I don't understand what is the attribute StringProperty...
UserPartnerQuery::create()->filterByUser( $userObject )->delete();
or
UserPartnerQuery::create()->filterByUser( $partnerObject )->delete();
Had the same problem. This is a working solution.
The thing is that your second solution, ie. looping over the related objects and calling delete() on them should work. It's the documented way of doing things (see : http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer#chapter_08_sub_saving_and_deleting_data).
But instead of bombing the DB with delete queries, you could just as well delete them in one go, by adding a method to your Peer class that performs the deletion using a simple DB query.

Categories