Laravel 4 update slug - php

I have it so that when a new branch is created the name gets turned into a slug for lookup, but when I update the record the slug variable stays the same, is there a way to have it automatically update whenever the record is changed?
class Branch extends \Eloquent {
public static $rules = [
'name' => 'required'
];
protected $fillable = ['name', 'slug'];
protected function setNameAttribute($name)
{
$this->attributes['name'] = $name;
$this->attributes['slug'] = Str::slug($name);
}
}
and in my controller...
public function update($slug)
{
$branch = Branch::whereSlug($slug);
$validator = Validator::make($data = Input::except('_method', '_token'), Branch::$rules);
if ($validator->fails())
{
return Redirect::back()->withErrors($validator)->withInput();
}
$branch->update($data);
return Redirect::route('branches.index');
}

First off, the model event should kick in to the mutator if it's the Eloquent model you're dealing with - the Fluent query builder won't. I think that Branch::whereSlug($slug) returns a Fluent query builder - just check to see if Branch::whereSlug($slug)->firstOrFail() works. I think it probably will.
Failing that, there are two options that I'd recommend - first off, Colin Viebrock has an excellent package for generating slugs which is super easy to use.
If you'd rather homebrew it, I'd just put it in the boot method using a model event:
public static function boot() {
static::saving( function( $model ) {
$model->name = $model->name; // force the slug to be rebuilt
} );
}

Related

Laravel 5.7 - Eager loading with morphMany relationship and custom attribute getter

So I have the following models:
class TemplateEntity extends Model {
protected $table = "TemplateEntities";
const UPDATED_AT = null;
const CREATED_AT = null;
public function element() {
return $this->morphTo("element", "entity_type", "id_Entity");
}
public function getEntityTypeAttribute($entity_type) {
return 'App\\' . $entity_type;
}
}
class Template extends Model {
protected $table = "Template";
const UPDATED_AT = null;
const CREATED_AT = null;
public function entities() {
return $this->hasMany("App\TemplateEntity", "id_Template");
}
}
class TemplateEntity extends Model {
protected $table = "TemplateEntities";
const UPDATED_AT = null;
const CREATED_AT = null;
public function element() {
return $this->morphTo("element", "entity_type", "id_Entity");
}
public function getEntityTypeAttribute($entity_type) {
return 'App\\' . $entity_type;
}
}
I want to eager load template entity elements using Eloquent ORM's ::with() method, however whenever I do this I get an error:
//$template_id is defined as a controller param
$template = Template::with("entities", "entities.element")->where("id", "=", $template_id)->get()
"Class 'App\' not found"
I did some debugging and when I echo $entity_type in TemplateEntity's GetEntityTypeAttribute() method I get an empty value. However, my models generally work fine if I don't use eager loading, but I would like to add it to my application if possible to make it more efficient.
Any help you all can provide would help!
edit: fixed a typo, should have been Template::with instead of $template::with
Part of the problem might be a blank class in that variable. Suggest you use the class name when calling get(). So \App\Template:: instead of $template::.
Another item to help may be the way you are calling the relationship's eager load. Perhaps try to call through the function. This might work better for you:
\App\Template::with(['entities' => function($query){
$query->with('element');
}])->get();
The accessor function might be interfering with the Laravel morph function. I realise you want to use the shortened name of the class in the DB. To do this without the use of the getter (and globally), I suggest using a morphMap.
In AppServiceProvider inside the boot() method:
\Illuminate\Database\Eloquent\Relations\Relation::morphMap([
'MyTemplate' => \App\MyTemplate::class,
'Section' => \App\Section::class,
// etc.
]);
This will allow you to add only 'Section' to the DB and remove the accessor function from your class.

Laravel 5.6 not saving field in `created` model event

I am trying to save a field from the created model event but for some reason the stripe_coupon_id column is never being saved. The created event does run as I've tested by trying a dd inside it and it does fire the event but just does not save that column.
class DiscountRate extends Model
{
public $table = "discount_rates";
public $primaryKey = "id";
public $timestamps = true;
public $fillable = [
'id',
'name',
'rate',
'active',
'stripe_coupon_id'
];
public static function boot()
{
parent::boot();
self::created(function ($discountRate) {
$coupon_id = str_slug($discountRate->name);
$discountRate->stripe_coupon_id = $coupon_id;
});
}
}
In my controller I simply call a service function which calls the default Laravel model create function:
public function store(DiscountRateCreateRequest $request)
{
$result = $this->service->create($request->except('_token'));
if ($result) {
return redirect(route('discount_rates.edit', ['id' => $result->id]))->with('message', 'Successfully created');
}
}
discount_rates table:
The created event is triggered after your model is created. In this case, you need to to call $discountRate->save() in the end in order to update the model which you just created.
As alternative, you can use creating event instead. In this case you don't need to call save() in the end because the model is not yet saved in your database.
A big difference in creating event is that the model does not have an id yet if you use auto-incrementing which is default behavior.
More info about events you can find here.
you have to set stripe_coupon_id before creating. So replace static::creating instead of self::created in boot method of DiscountRate model.

Laravel 5 requests: Authorizing and then parsing object to controller

I am not sure if I am using this correctly, but I am utilising the requests in Laravel 5, to check if the user is logged in and if he is the owner of an object. To do this I need to get the actual object in the request class, but then I need to get the same object in the controller?
So instead of fetching it twice, I thought, why not just set the object as a variable on the request class, making it accessible to the controller?
It works, but I feel dirty? Is there a more appropriate way to handle this?
Ex.
Request Class
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::find(Input::get('comment_id'));
$user = Auth::user();
if($this->comment->user == $user)
return true;
return false;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Ex. Controller:
public function postDeleteComment(DeleteCommentRequest $request) {
$comment = $request->comment;
$comment->delete();
return $comment;
}
So what is my question? How do I best handle having to use the object twice when using the new Laravel 5 requests? Am I possibly overextending the functionality of the application? Is it ok to store the object in the application class so I can reach it later in my controller?
I would require ownership on the query itself and then check if the collection is empty.
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::where('id',Input::get('comment_id'))->where('user_id',Auth::id())->first();
if($this->comment->is_empty())
return false;
return true;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Since you're wanting to use the Model in two different places, but only query it once I would recommenced you use route-model binding.
In your RouteServiceProvider class (or any relevant provider) you'll want to bind the comment query from inside the boot method. The first parameter of bind() will be value that matches the wildcard in your route.
public function boot()
{
app()->router->bind( 'comment_id', function ($comment_id) {
return comment::where('id',$comment_id)->where('user_id',Auth::id())->first();
} );
}
Once that's set up you can access the Model from your DeleteCommentRequest like so
$this->comment_id
Note: The variable is Comment_id because that's what matches your route, but it will contain the actual model.
From your controller you just inject it like so
public function postDeleteComment(Comment $comment, DeleteCommentRequest $request) {
$comment->delete();
return $comment;
}

Laravel call method once model loaded

I am trying to check in the constructor of a model if the currently authenticated user is allowed to access the given model, but I am finding that $this from the constructor's context is empty. Where are the attributes assigned to a model in Laravel and how should I go about calling a method once all of the attributes have been loaded?
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
var_dump($this); // empty model
$this->checkAccessible();
}
Cheers in advance
As stated in the other answers & comments, there are better ways to achieve the aims of the question (at least in modern Laravel). I would refer in this case to the Authorization chapter of the documentation that goes through both gates and policies.
However, to answer the specific question of how to call a method once a models attributes have been loaded - you can listen for the Eloquent retrieved event. The simplest way to do this within a class is using a closure within the class booted() method.
protected static function booted()
{
static::retrieved(function ($model) {
$model->yourMethod() //called once all attributes are loaded
});
}
You can also listen for these events in the normal way, using listeners. See the documentation for Eloquent events.
you can use controller filter to check whether user logged in or not and than you call any model function.
public function __construct(array $attributes = []){
$this->beforeFilter('auth', array('except' => 'login')); //login route
if(Auth::user()){
$user_id = Auth::user()->user_id;
$model = new Model($attributes);
//$model = User::find($user_id);
}
}
Binding Attributes to Model from constructor
Model.php
public function __construct(array $attributes = array())
{
$this->setRawAttributes($attributes, true);
parent::__construct($attributes);
}
As it was mentioned by Rory, the retrieved event is responsible for that.
Also, it could be formed in a much cleaner and OOP way with Event/Listener approach, especially if you need to write a lot of code or have few handlers.
As it described here, you can just create an event for the Model like
protected $dispatchesEvents = [
'retrieved' => UserLoaded::class,
];
You need to create this class, eloquent event accepts the model by default:
class UserLoaded
{
protected User $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Then here is described how to declare listener for this event. It should be somewhere in the EventListenerProvider like this:
protected $listen = [
UserLoaded::class => [
UserLoadedListener::class
],
];
The listener should just implement method handle() (check article) like:
public function handle(UserLoaded $event)
{
// your code
}
Another possibility is to register model Observer, as it´s described here

Reading data using Anahkiasen/Polyglot returns error trying to get property of non-object

After adding the Anahkiasen/Polyglot package to my Laravel 4.2 project I tried to get this to work. I set up everything the way I think it's supposed to be (the documentation is kinda bad). Saving to the database doesn't seem to be a problem but when I want to read I get the following error:
Trying to get property of non-object (View: /Applications/MAMP/htdocs/*my view*.blade.php)
Models:
use Polyglot\Polyglot;
class Page extends Polyglot {
use SoftDeletingTrait;
protected $fillable = [
'lang',
'meta_title',
'meta_description',
'title',
'page_title',
'page_content',
];
protected $polyglot = [
'meta_title',
'meta_description',
'title',
'page_title',
'page_content',
];
// ...
}
class PageLang extends Eloquent {
public $timestamps = false;
protected $fillable = [
'page_id',
'lang',
'meta_title',
'meta_description',
'title',
'page_title',
'page_content',
];
}
My blade template:
$page->nl->title
/*
This is what's causing the error
$page->title doesn't produce errors but is, of course, empty
*/
Been stuck on this for a while now. Any help is greatly appreciated :-)
I'm not familiar with the library, but looking at the base Polyglot class, it looks like this abstraction works by using PHP's magic __get variable to inject n localization object when you access something like $page->nl
Looking at __get,
public function __get($key)
{
// If the relation has been loaded already, return it
if (array_key_exists($key, $this->relations)) {
return $this->relations[$key];
}
// If the model supports the locale, load and return it
if (in_array($key, $this->getAvailable())) {
$relation = $this->hasOne($this->getLangClass())->whereLang($key);
if ($relation->getResults() === null) {
$relation = $this->hasOne($this->getLangClass())->whereLang(Config::get('polyglot::fallback'));
}
return $this->relations[$key] = $relation->getResults();
}
// If the attribute is set to be automatically localized
if ($this->polyglot) {
if (in_array($key, $this->polyglot)) {
/**
* If query executed with join and a property is already there
*/
if (isset($this->attributes[$key])) {
return $this->attributes[$key];
}
$lang = Lang::getLocale();
return $this->$lang ? $this->$lang->$key : null;
}
}
return parent::__get($key);
}
there's a number of conditionals that, if failed, result in the parent's __get being called
return parent::__get($key);
In other words, the normal model behavior. That's probably what's happening above -- and since nl isn't a set object PHP complains when you try to call a method on it.
Of the three conditionals in __get, this seems like the most likely candidate for failure
if (in_array($key, $this->getAvailable())) {
$relation = $this->hasOne($this->getLangClass())->whereLang($key);
if ($relation->getResults() === null) {
$relation = $this->hasOne($this->getLangClass())->whereLang(Config::get('polyglot::fallback'));
}
return $this->relations[$key] = $relation->getResults();
}
If your specific case, it's going to look like
if (in_array('nl', $this->getAvailable())) {
//...
}
Looking at getAvailable()
protected function getAvailable()
{
return Config::get('polyglot::locales');
}
it looks for a configuration field named Config::get('polyglot::locales');. I'd check if calling that configuration field returns nl as a configured locale
var_dump(Config::get('polyglot::locales'));
My guess would be it doesn't, possibly because you didn't run the artisan command to publish the configuration for the package
php artisan config:publish anahkiasen/polyglot

Categories