Laravel - Polymorphic relationship not working - php

So I have the following code:
class PageSection extends Model {
protected $table = "PageSection";
const TYPE_CURATED = 0;
const TYPE_AUTOMATED = 1;
public function list() {
return $this->morphTo('list', 'entity_type', 'id_Entity');
}
}
then in AppServiceProvider.php I have the following:
use App\PageSection;
use App\PageSectionGroup;
use App\PageListEntry;
use App\RSSFeed;
use App\Shortcut;
use App\RSSEpisode;
use App\PageList;
use App\AutomatedList;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
Relation::morphMap([
'Section' => PageSection::class,
'SectionGroup' => PageSectionGroup::class,
PageSection::TYPE_CURATED => PageList::class,
PageSection::TYPE_AUTOMATED => AutomatedList::class,
PageListEntry::TYPE_FEED => RSSFeed::class,
PageListEntry::TYPE_SHORTCUT => Shortcut::class,
PageListEntry::TYPE_EPISODE => RSSEpisode::class
]);
}
Then I have a test route in my api routes that checks to see if the list is being loaded, and it returns null: (Yes, I've verified that the section itself exists)
Route::get('/test', function() {
$section = PageSection::with(['list', 'type'])->find(1);
// this returns null
return $section->list;
});
My database schema for PageSection is such that entity_type tells what the model is, and id_Entity is the foreign key for that model, which is named 'id' on the referenced table.
The other relations defined in morphMap are working properly, yet for some reason the list() relationship in PageSection is not. I'm not sure what I'm doing wrong here.. any help would be appreciated.

Ok, so I figured out what was going on. It's probably a bug with Laravel's morphMap. I was using 0 for the PageSection::TYPE_CURATED constant, which is a falsey value. When I switched to:
Relation::morphMap([
'PageList' => PageList::class,
'AutomatedList' => AutomatedList::class,
'Section' => PageSection::class,
'SectionGroup' => PageSectionGroup::class,
PageListEntry::TYPE_FEED => RSSFeed::class,
PageListEntry::TYPE_SHORTCUT => Shortcut::class,
PageListEntry::TYPE_EPISODE => RSSEpisode::class
]);
it all worked fine. Seems like Laravel doesn't like the value 0.

Related

Laravel Spark, Swap/Interact, and Private Variables

Using Laravel Spark, if I wanted to swap in a new implementation for the configureTeamForNewUser, at first it looks like it's possible because of the Spark::interact call here
#File: spark/src/Interactions/Auth/Register.php
Spark::interact(self::class.'#configureTeamForNewUser', [$request, $user]);
i.e. the framework calls configureTeamForNewUser using Spark::interact, which means I can Spark::swap it.
However, if I look at the configureTemForNewUser method itself
#File: spark/src/Interactions/Auth/Register.php
public function configureTeamForNewUser(RegisterRequest $request, $user)
{
if ($invitation = $request->invitation()) {
Spark::interact(AddTeamMember::class, [$invitation->team, $user]);
self::$team = $invitation->team;
$invitation->delete();
} elseif (Spark::onlyTeamPlans()) {
self::$team = Spark::interact(CreateTeam::class, [
$user, ['name' => $request->team, 'slug' => $request->team_slug]
]);
}
$user->currentTeam();
}
This method assigns a value to the private $team class property. It's my understanding that if I use Spark::swap my callback is called instead of the original method. Initial tests confirm this. However, since my callback can't set $team, this means my callback would change the behavior of the system in a way that's going to break other spark functionality.
Is the above a correct understanding of the system? Or am I missing something, and it would be possible to swap in another function call (somehow calling the original configureTeamForNewUser)?
Of course, you can swap this configureTeamForNewUser method. Spark create a team for a user at the registration. You have to add the swap method inside the Booted() method of App/Providers/SparkServiceProvider.php class.
in the top use following,
use Laravel\Spark\Contracts\Interactions\Auth\Register;
use Laravel\Spark\Contracts\Http\Requests\Auth\RegisterRequest;
use Laravel\Spark\Contracts\Interactions\Settings\Teams\CreateTeam;
use Laravel\Spark\Contracts\Interactions\Settings\Teams\AddTeamMember;
In my case I want to add new field call "custom_one" to the teams table. Inside the booted() method, swap the method as bellow.
Spark::swap('Register#configureTeamForNewUser', function(RegisterRequest $request, $user){
if ($invitation = $request->invitation()) {
Spark::interact(AddTeamMember::class, [$invitation->team, $user]);
self::$team = $invitation->team;
$invitation->delete();
} elseif (Spark::onlyTeamPlans()) {
self::$team = Spark::interact(CreateTeam::class, [ $user,
[
'name' => $request->team,
'slug' => $request->team_slug,
'custom_one' => $request->custom_one,
] ]);
}
$user->currentTeam();
});
In order to add a new custom_one field, I had to swap the TeamRepository#createmethod as well. After swapping configureTeamForNewUser method, swap the TeamRepository#create method onside the booted(),
Spark::swap('TeamRepository#create', function ($user, $data) {
$attributes = [
'owner_id' => $user->id,
'name' => $data['name'],
'custom_one' => $data['custom_one'],
'trial_ends_at' => Carbon::now()->addDays(Spark::teamTrialDays()),
];
if (Spark::teamsIdentifiedByPath()) {
$attributes['slug'] = $data['slug'];
}
return Spark::team()->forceCreate($attributes);
});
Then proceed with your registration.
See Laravel Spark documentation

localization Faker\Factory does not work in laravel

I have a Post Model with these fields :
post_id
post_title
post_content
post_content_full
author
Now I want to use laravel sedders and model factories to create fake fa_IR localized data and insert to posts table.
For that I wrote this in database/factories/ModelFactory.php:
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'post_title' => $faker->sentence,
'post_content' => $faker->paragraph,
'post_content_full' => $faker->paragraph(3),
'author' => $faker->name
];
});
Then I created a PostsTableSeeder class like this :
use Illuminate\Database\Seeder;
class PostsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run ()
{
factory(App\Post::class, 5)->create();
}
}
And in AppServiceProvider.php added below codes to register function :
$this->app->singleton(FakerGenerator::class, function () {
return FakerFactory::create('fa_IR');
});
But After running the seed , laravel uses default locale (en_US) and ignores fa_IR.
I do not know what else to do.
Update:
Even I changed in DEFAULT_LOCALE const vendor/fzaninotto/faker/src/Faker/Factory.php to fa_IR Nothing changed.
Not all faker methods are supported in every language, from what a quick lookup of the documentation says, the Company and Address provider are supported in the fa_IR localization
Try this way
$factory->define(App\Post::class, function () {
$faker = Faker\Factory::create('fa_IR');
return [
'post_title' => $faker->sentence,
'post_content' => $faker->paragraph,
'post_content_full' => $faker->paragraph(3),
'author' => $faker->name
];
});
You need to change the faker locale in your app config file.
First run this
php artisan make:factory PostFactory
Do like this
$faker = \Faker\Factory::create();
Then use like this
$sub_g->name = $faker->name();
$sub_g->country = $faker->country();
$sub_g->state = $faker->state;
Thank me later.

Symfony Model is returning null instead of class

When I attempt to load my database results, it doesn't work, until I switch:
$id === null
to
$id !== null
.. then, everything loads as expected. What am I missing?
Here's my code:
config.php
'services' => array(
'models' => array(
'serv.widget.model.widget' => array(
'class' => 'ServPlugin\BundleNameBundle\Model\ClassModel'
)
)
),
DefaultController.php
namespace ServPlugin\BundleNameBundle\Controller;
use Serv\CoreBundle\Controller\FormController;
use ServPlugin\BundleNameBundle\Entity\Class;
/** #var \ServPlugin\BundleNameBundle\Model\ClassModel $classModel */
$classModel = $this->getModel('class');
/** #var \ServPlugin\BundleNameBundle\Entity\Class $class */
$class = $classModel->getEntity($formId);
//html here
echo 'Total Rows: '.$class->getCount();
ClassModel.php
namespace ServPlugin\BundleNameBundleBundle\Model;
use Serv\CoreBundle\Model\FormModel;
use ServPlugin\BundleNameBundleBundle\Entity\Class;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
class ClassModel extends FormModel
{
public function getEntity($id = null)
{
if ($id === null) {
return new Class();
}
return parent::getEntity($id);
}
function getEntity is what I am struggling with.
When I view the cache, I can see the model is loading appropriately, but I cannot access it until I go backwards on the null value.
(It turns out, $classModel is loading nothing; -- so when my code moves to $class->getCount() -- the $formId is working as it should (which is a number); the model should be loading FIRST with ServPlugin\BundleNameBundle\Model\ClassModel with ID #7, right?)
This is the error I get when I try to call it as posted above:
Call to a member function getCount() on null - in file /var/www/plugins/BundleNameBundle/Controller/DefaultController.php - at line 299 [] []
Line 299:
$class->getCount()

Symfony Nested Validation Constraints

I came accross curious problem. Lets say we want to validate some id. Validation should pass 10 different conditions(constraints) and we have to do it in 10 different places. I thought I can save myself writing unnessecary code by nesting one validation in another.
Here's what I did:
I've created new, custom validation constraint called IdParameter
I've registered IdParameterValidator.php file as service and injected validator service to it
I've put there another validation process(in our example 10 constraints which I will have to use in 10 different places) - I used Constraints\Collection to do it, so it looks kinda like this:
<?php
namespace Awesome\BlogBundle\Validator\Constraints;
use Symfony\Component\Validator;
class IdParameterValidator extends Validator\ConstraintValidator
{
private $_data = array();
private $_validator;
public function __construct(Validator\Validator\RecursiveValidator $validator)
{
$this->_validator = $validator;
}
public function validate($value, Validator\Constraint $constraint)
{
/* Preparing object of constraints */
$postIDConstraints = new Validator\Constraints\Collection(array(
'postId' => array(
new Validator\Constraints\Type(array(
'type' => 'integer',
'message' => 'This ain\'t no integer man!'
)),
new Validator\Constraints\Range(array(
'min' => 1,
'minMessage' => 'Post id is not valid'
))
)
));
/* Validating ID */
$this->_data['errors'] = $this->_validator->validate(array('postId' => $value), $postIDConstraints);
/* Checking validation result */
if(count($this->_data['errors']) > 0) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
So now I can use as many constraint as I whish and still have a clean service code:
$postIDConstraints = new Validator\Constraints\Collection(array(
'postId' => array(
new myValidator\Constraints\IdParameter()
)
));
/* Validating postID */
$this->_data['errors'] = $this->_validator->validate(array('postId' => (int)$postID), $postIDConstraints);
I'm wondering if it's correct approach?
Best Regards,
R.
P.S
I always comment my code - I didn't put comments here to keep it clean.

How to use model attribute value in behavior config Yii

I want create ImageBehavior for uploading and saving images. My behavior have two fields: imagePath and imageField. In my model i'm wrote:
public function behaviors(){
return array(
'imageBehavior' => array(
'class' => 'ImageBehavior',
'imagePath' => 'images/avatar-pics/'.$this->user->username,
'imageField' => 'avatar',
),
);
}
but this doesn't worked - i receive path -
images/avatar-pics//image.png
What a solution? Create in behavior field imageFolder and add to config 'imageFolder' => 'user->username'? Thanks.
As a suggestion:
Change the way you use your behavior and add some codes to your models. Take a look at the following example:
For example, your behavior:
class ImageBehavior extends CBehavior {
public $imagePath;
public $imageField;
public function getImagePath() {
return $this->imagePath;
}
}
Your model:
class TestModel extends CFormModel {
private $imagePath = '/home/x/y';
public function setImagePath($imagePath) {
$this->imagePath = $imagePath;
$this->attachBehaviors(array(
array(
'class' => 'ImageBehavior',
'imagePath' => $this->imagePath
)
));
}
public function init() {
$this->setImagePath($this->imagePath);
parent::init();
}
}
Now, take a look at tests and results:
$model=new TestModel();
CVarDumper::dump($model->getImagePath()); //output: /home/x/y
$model->setImagePath('/home/x/path2');
CVarDumper::dump($model->getImagePath()); //output: /home/x/path2
$model->setImagePath('/home/x/path3');
CVarDumper::dump($model->getImagePath()); //output: /home/x/path3
By this way, if you do not set any imagePath it uses model's default value. In front, each time you change the imagePath your path will change in your behavior.
Note: This is just a suggestion. By this, you can customize your setImagePath method to get the value from everywhere you want(another model, session and so on...).
Sorry for answering own question, but almost two years later build correct vision to this issue:
1) don't use line
'images/avatar-pics/'.$this->user->username
in your behavoir - it throws a notice "Trying to get property of non-object" - good application doesn't thows notices and warnings.
2) all you need is use
'images/avatar-pics/' . CHtml::value($this, 'user.username')
instead. If $this->user is null, value method returns null, so be sure that you have related user to your model.
//inside the behavior:
$this->owner->user->username;
$this->owner - it's your model. Behavior is initialized before filling attributes, and the attribute value can be get only after the initialization.

Categories