I have a question maybe a little silly. I'm trying to replicate and store a model instance in a variable to call the loadMissing method. The idea is to call the loadMissing method without being affected by the original model. Let me explain.
I have the following method in my User model:
class User extends Authenticatable
{
...
public function myCustomMethod()
{
$cloned = $this->replicate()
->loadMissing(['roles', 'roles.permissions'])
->getAttribute('roles')
->flatMap(function ($role) {
return $role->permissions
});
return $cloned;
}
The problem is that in this way the original model instance is affected, and it loads in the original instance all the relations that I am loading only in this method.
My question is: is there a way to load and isolate relationships in a replica of the model instance without affecting the original model?
Thanks in advance.
EDIT 1
I have also tried cloning the instance and trying to use the loadMissing method on the cloned instance, but the original instance is also affected. For example:
class User extends Authenticatable
{
...
public function myCustomMethod()
{
$original = $this;
$cloned = clone $original;
$cloned->loadMissing('roles.permissions');
dump($original);
dump($cloned);
die();
}
But in this way, I get the following results. Notice that both the original and the cloned instance are loading the relationships when I'm just trying to load the relationships in the cloned instance:
$original
$cloned
I was wondering if this is normal behavior. If it is a normal behavior, which I suppose it is not, I was wondering if there is any way to use the loadMissing method on a copy, replica or clone of an instance without modifying the original instance. Thank you very much in advance.
EDIT 2
My controller looks like simple like this:
class HomeController extends Controller
{
public function index()
{
dd(User::find(1)->myCustomMethod());
}
}
SOLUTION
After several days looking for documentation, doing tests and pulling my hair a little, I have found the solution to my problem and share it in case someone comes here looking for similar information in the future.
Basically the answer that helped me find the right way was this:
https://stackoverflow.com/a/29787693/13066106
Which is documented in the official PHP documentation:
https://www.php.net/manual/en/language.oop5.cloning.php
I copy and paste verbatim the most relevant here:
When an object is cloned, PHP will perform a shallow copy of all of the object's properties. Any properties that are references to other variables will remain references.
When I got to this point, I understood why it wasn't working ... I was cloning a collection of type Illuminate\Database\Eloquent\Collection and this collection contained multiple objects of type Role, but according to the documentation, it is normal behavior that when I modify any property in the cloned instance, the changes will be reflected back to me in the original instance.
From here, I looked for more documentation about it and I found an article that definitely helped me solve my problem:
Don't clone your php objects, DeepCopy them
The article is quite descriptive and basically what it does is advise the use of the package DeepCopy
Fortunately, the package is already a Laravel dependency by default, so I didn't have to install it with composer. I just used the deep_copy method to clone the instance.
Thank you very much to everyone who helped me in some way. I hope that the links to the documentation that I have shared will be of use to those who come here looking for similar information.
Why don't you just get the permissions collection (outside of model):
$userId = $user->id;
$premissions = Permissions::whereHas('users', function($userQuery) use ($userID) {
$userQuery->where('id',$userId);
})
->get();
Or do the flatmap:
$result = Roles::with('permissions')
->whereHas('users', function($userQuery) use ($userID) {
$userQuery->where('id',$userId);
})
->get()
->flatMap(function ($role) {
return $role->permissions
});
Related
I am creating an API. In this API I am accessing a (permissions) table from a database multiple times, in middleware as well as in controllers. I was thinking, instead of accessing the database multiple times, why don't I call it once and use it multiple times. After calling it once, I could store it in the cache within a service provider. But I am not sure if it is a good way to go because API routes don't load all the services like session.
There are other ways like storing data into the config. Or create a class and make a facade for it and then call it when ever it is needed. But I am curious if the cache would work in API routes and would it be a good idea?
Okay with the advice of #lagbox I created a dead simple class.
namespace App\Helpers;
use App\Permission;
class Provide
{
public $permissions = [];
function __construct() {
$this->permissions = Permission::whereNotNull('route_name')->get();
}
}
This may vary, it's just a class that will keep some collection data in it. I named it provide to keep it generic, just in case that I could need other data than permission in the future. Of course this class could be more detailed but just for storing and returning permissions it is enough.
Then I bound it as a singleton in my AppServiceProvider to run it only once.
public function register()
{
$this->app->singleton('App\Helpers\Provide', function ($app) {
return new \App\Helpers\Provide();
});
}
and when I need it I call it like
$provide->permissions->toArray()
All the features of the collection are available everywhere from the beginning to the end. Yes that may look like an overkill or an abuse of IoC but this über simple approach is in my case a superb solution.
I have a guitar lessons site using Laravel 5.3. It has lessons that are then broken down into exercises.
I decided today to add in the backend the ability to mark lessons or exercises as published. I simply added a 'published' column in the respective tables which contains 0 or 1.
There are many places in my site where exercises or lessons are accessed in some shape or form, including a lesson view, exercise view, lessons partial view (a tabular listing), exercises partial view (also a tabular listing), search/query views. There are also routes that perform functions such as incrementing hit count for a lesson or exercise, adding a lesson or exercise to one's favorites.
Given above, I am not sure how to prevent access to unpublished lessons or exercises using minimal code edits. I started looking at the controller files but then realized the controller might not be a catch all, such as on query result pages. Also controller files contain multiple methods and I don't want to edit them separately. I suspect there is a "laravel way" to do what I need globally, without me having to modify a bunch of queries in multiple files. Maybe it amounts to middleware or something?
In any case, I am new to laravel and if there is a tried and true way to control routes for content that is marked as unpublished, I would be very interested to know about it.
thanks
Take a look at global scopes, laravelest way to do this
As an aside, I prefer to use timestamps for things like published states on models. It means I can set a future timestamp for that content to become available, i.e. if I’m going on holiday.
In terms of your actual problem, a query scope would be the way to go:
class Lesson extends Model
{
public function scopePublished($query)
{
return $query->wherePublished(true);
}
}
Alternatively, if you did use a timestamp instead, you could do:
class Lesson extends Model
{
public function scopePublished($query)
{
return $query->where('published', '<=', $this->freshTimestamp());
}
}
You will then need to update your front-end code to use this scope:
public function LessonController extends Controller
{
public function index()
{
$lessons = Lesson::published()->latest()->paginate();
return view('lesson.index', compact('lessons'));
}
}
I am not sure if I am going about this the right way but here is what I am attempting to do, if there is a better way please let me know.
I am using a service provider that pulls some data from a config file. the problem is that if I use Config::set to change one of the settings after calling a function that uses that service provider it will not update. I thought that because I am using app->bind instead of app->share that it would re instantiate the class every time. here is my code:
service provider:
public function Register() {
$app = $this->app;
$app->bind('\path\to\MyInterface', function() use($app) {
$server = $app['config']->get('myconfig.server');
$client = $app['config']->get('myconfig.client');
$key = $app['config']->get('myconfig.key');
$version = $app['config']->get('myconfig.version');
return new MyService(new Instance($server, $client, $key, $version));
});
$this->app->booting(function() {
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('MyServiceFacade', '\path\to\MyFacade');
});
}
Facade class:
class MyServiceFacade extends Facade {
protected static function getFacadeAccessor() { return '\path\to\MyInterface'; }
}
route for testing:
Route::get('test', function() {
$nodes = MyServiceFacade::allNodes();
\Config::set('myconfig.server', 'new server name');
$nodes2 = MyServiceFacade::allNodes();
var_dump($nodes->getContent());
var_dump($nodes2->getContent());
}
);
I am getting the same results from both. shouldn't this be using the update config since I am making a new instance of the controller?
Skip to update 2 below for a stab at the answer
Your question doesn't quite make sense. In your testing route you're saying
App::make('MyController');
This is you asking Laravel to make an instance of the MyController service and/or class. However, you never define a MyController service and/or class.
You bind a \pathto\Interface identifier here
$app->bind('\pathto\Interface', ...
and alias MyService to that identifier here
$loader->alias('MyService', '\pathto\Facade');
but there's no place you bind or alias a MyController identifier anywhere. There's nothing in your code samples that tie MyController to the service you have bound.
Because of that it's not 100% clear what you're asking.
Update: Your question still doesn't quite make sense, and I think this not-sense-masking is what's leading the the unexpected behavior. i.e., you're doing something that "works", as in PHP doesn't complain with an error, but what you think is happening behind the scenes is not happening.
You've refereed to MyService as a facade -- however, you haven't told us what the "facade accessor" string the MyService facade points to (via its getFacadeAccessor method). Also, you appear to be directly instantiating a class from that facade class (new MyService), which isn't how Laravel facades work.
Update 2: The code samples provided are still a little sketchy, and I suspect they don't accurately reflect the actual application. The context from the comments are that MyServiceFacade::allNodes is a call to a facade. However, the facade defined in the code samples is named MyFacade and there's no class MyServiceFacade. I'm going to take a stab based on something mentioned in a comment as to the problem, but based on what I've seen above the problem still might be an incorrect application of service providers, services, and facades.
Binding a service with bind ensures the application container will always return a new instance of the service. I bet if you tried something like the following
$app = app();
$object = $app['\path\to\MyInterface'];
You'd find your object is instantiated anew every time. Adding some basic var_dump debugging to the bound closure and/or service class constructor is a good way to confirm that.
However, Laravel facades are a little different. They're not, technically, a part of the application container system. Facades are a second system built on top of the application container.
In addition to providing a convenient alias for accessing a service class, the facade implementation also forces the service object into a single-instance/singleton irrespective of how you've bound it into the container. This happens in the base facade class here
#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) return $name;
if (isset(static::$resolvedInstance[$name]))
{
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
The base facade class keeps an instance cache in static::$resolvedInstance. The specifics of how Laravel gets here are a bit long for a StackExchange answer, but my Unraveling Laravel Facades article (part of a longer series) is a good place to start.
The problem here (again, based on the incomplete information provided) appears to be a misunderstanding of facades. The main takeaway is a facade always forces a service object to be a singleton.
When you are using code igniter you load a library or model like so
$this->load->library('mylibrary');
$this->load->model('mymodel');
So, in a real world example, lets say you have a controller called user.
So, to login a user you send them to http://example.com/user/login
Now the login function loads a form that submits to http://example.com/user/login_do
You do some simple checks, and then send it over to your model to do the database check for you.
So you call
$this->load->model('user');
if($this->user->validate($email, $pass)){...}
UH OH!
Fatal error: Cannot redeclare class
User in
/var/www/unity/src/application/models/User_Model.php
on line ...
So what happened? Well, Code igniter does not segregate the classes, so your model now conflicts with your controller,
sure you can use
$this->load->model('user_model', '', 'user_m');
if($this->user_m->validate($email, $pass)){...}
So, Onto my question.
Why does code igniter not segregate the classes,
e.g. so you would call
$this->load->model('user');
if($this->model->user->validate($email, $pass)){...}
Sure it's slightly longer, but hell it would make things heaps nicer to user.
is it possible to extend code igniter so it works in this way?
It's not exactly the solution you're asking for (or a great idea), but there's nothing stopping you from doing this:
class Users extends CI_Controller {
private $model;
private $m;
public function __construct()
{
parent::__construct();
$this->load->model('user_model');
$this->model->users = $this->user_model;
$this->m = $this->user_model;
}
function index()
{
// Here's that syntax you wanted
$this->model->users->get_many();
// Even shorter
$this->m->get_many();
}
}
You can really just assign anything to any property of the controller you want, as long as it's not the name of a loaded class or property (session, router, etc.). It can save you some typing if your model names are really long, but otherwise it's pointless and may conflict with things in the future.
Here's what I do if I'm not using *_model for model names:
Controller name: Users (plural)
Model name: User (singular)
No conflict, short syntax, sensible naming, it just doesn't work for some names like "news".
As I mentioned, it would be nice to see controller names using something like Controller_User or User_Controller to clear up the namespace issues a bit for the classes that we actually do have to call frequently, like models and libraries, but keep our urls as normal. I'm sure it can be done, something for a rainy day...
You missing the main point, PHP doesn't allow two classes with the same name, basically that's what Cannot redeclare class User says.
Why won't my model load the encryption library?
class User_model extends Model {
function User_model() {
parent::Model();
$this->check_login();
}
function check_login() {
$this->load->library('encrypt');
$email = $this->encrypt->decode($email);
....
}
}
This giving me a PHP error: Call to a member function decode() on a non-object on line X -- where X is the $this->encrypt->decode($email); line?
Edited to show that the problem was that check_login was called from the constructor
You don't need to load the library in the MODEL, MODELS are always called from the CONTROLLERS so you just have to load the Libraries in the Controller, and the functions will be available in the models called from him!
Regards,
Pedro
Libraries should automatically be assigned to the Model instance so it should work fine.
Remember if you can't access the super-global you can always use $ci =& get_instance() to grab it at no extra cost to your memory.
But still... your code example should work >.<
I was calling check_login from within the constructor, and that was causing the problems.
The solution is to call $this->_assign_libraries(); right after loading a library in a constructor.
Thanks to this codeignitor forum thread:
http://codeigniter.com/forums/viewthread/145537/
I have tried many of them, but in the end, what I did is this in a model:
$this->load->library('mylib');
$mylib= new Mylib();
$mylib->somemethod();
This works for me.
you might want to change the name of the object for the library you are loading
beacause CI also has got the encrypt class
just do
$this->load->library('encrypt',NULL,'myencryptobj');
$this->myencryptobj->yourfunction();
Hope this helps
i was also facing issue about facebook api, then I tried required_once the lib file of facebook in model. it worked for me.
require_once "application/libraries/facebook.php";
then make its object if you need.