I'm implementing a LOG of the events that occurs on the system. In other words, detect when an object is created, and save the data that was created.
When an update comes, I need to store the previous object state and the new one. As I'm using API resources with custom data implementations, I'm reusing them to gather all the needed information.
public function update(Request $request, Attendant $attendant)
{
$attendantData = $request->input('attendant');
$prevResource = new AttendantResource($attendant);
$attendant = AttendantService::Update($attendant, $attendantData);
$resource = new AttendantResource($attendant);
$this->createLog($prevResource, $resource);
return $resource;
}
In the previous code, create a new resource before the Attendant is modified. But at the end, $prevResource and $resource have the same data. The information from $prevResource is update toO.
Is there a way to clone the Model? or instead, Is there a way to prevent the reference update from the $prevResource?
Use laravel's model observers(https://laravel.com/docs/5.8/eloquent#observers) to observe events on models and log what you need to.
You can observe created, creating, updated, updating, deleted, deleting, saved, saving events.
In my opinion this is not the way to handle this type of problem (in the controller).
What you actually want to do is Register an event listener that does the logging when your eloquent model is updated. There are also model "observers".
There is a library that already handles the specifics of how to make all of this work together that at very least can act as an example of how to set everything up, but is already capable of doing what you want.
Here is the specific documentation on "Logging Model Events."
The nice thing about using the Spatie Logger is that it's been manifested as a simple trait you add to your model. Here's some example code from the documentation:
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
class NewsItem extends Model
{
use LogsActivity;
protected static $logAttributes = ['*'];
protected static $logAttributesToIgnore = ['text'];
protected static $logOnlyDirty = true;
}
This illustrates a few different customizations including the use of $logOnlyDirty that will only log changed items, vs the default of providing a complete before/after of the entity. You can also ignore certain attributes you don't care about (timestamps or calculated fields for example.
Related
I'm trying to create an application where I have two related models: "User" and "Debtor".
User is a doctrine entity that is persisted in my database.
Debtor is a model that is stored on an external system and accessed through an API. I have already created a repository for accessing Debtors with find(), findAll() and findByUserId().
If "Debtor" was a Doctrine Entity, i would have a #OneToMany relationship on the User model in order to access debtors. Something like this:
/**
* #Entity
*/
class User {
/**
* #OneToMany
*/
private $debtors;
public function getDebtors() {
return $this->debtors;
}
public function setDebtors($debtors) {
$this->debtors = $debtors;
return $this;
}
}
One solution would be to call DebtorRepository::findByUserId($this->id) in the getDebtors() method, but I don't think that is the right way to go.
I also thought about have a #PostLoad method that loads the users debtors into the $debtors property.
I'm not much for having that much logic inside my models, but i don't know what other choices I have?
Furthermore, I also need to be able to access various other models through the Debtor model and so i have the issue again.
If resource for your API, I think you should have service which will take care of loading that (as probably you need to check for errors/authentication/...). So I would load it on demand from service.
But if you really want to have it accessible in the entity, then you can use Entity Listener, hook to the postLoad method and inject it there. In this case your entity will still stay thin, because this heavy logic will be in external service.
let say that I have something like this:
class User{
/**
* Object that is lazy loaded
*/
private $statistic; //object with some stored data and some calculated data
}
Some of the $statistic's properties are stored in the DB but some other of them are calculated by analyzing the user activity (querying data records).
the thing is that I got a $user and when I run $user->getStatistic() as spected, I get the stored $statistic data and I need to add more data using sql queries and I don't know where to program this functionality.
¿overriding the Repository? I try overriding the find() method but it doesn't work
I know that if I use the active record pattern this can be done with no problem giving that I can access the DB in the construct method or the getters maybe, etc.
but I don't know how this could be done with doctrine standard behavior.
I believe that there must be a way to ensure that every instance of the Statistic Class have this calculated data on it.
I'm using symfony... maybe a service or something...
There are a number of ways to solve your problem.
Doctrine listener
This is probably the easiest one. Use Doctrine postLoad event to fill out data you need on your User model.
This is a completely valid approach, but has a couple of drawbacks:
This will be ran every time doctrine fetches an User entity instance from database. If it fetches a list of 100 users, it will be ran 100 times. This could make a performance problem if you do some time-consuming tasks in there.
They are hard to debug: if an error is encountered, events usually make code flow a lot less clear and therefore make debugging harder. If you do simple stuff, and don't overuse them, then it's probably fine, otherwise think about other options.
Abstracting away doctrine
I'm strongly in favor of this approach and I use it in almost every project.
Even though I'm one of the people who try to have the least amount of layers and indirection necessary, I do think that wrapping data persistence into your own services is a good idea. It isolates rest of your system from having to know how your data is stored.
I suggest not using Doctrine repositories/Entity manager directly. Instead, wrap them in your own services.
This makes your persistence API squeaky clean and obvious, while giving you ability to manipulate your models before they reach your business logic.
Here is an example of how I would approach your problem:
# src/AppBundle/Repository/UserRepository.php
class UserRepository
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function findById($userId)
{
$user = $this->em->getRepository(User::class)->find($userId);
$this->calculateUserStatistics($user);
return $user;
}
public function save(User $user)
{
$this->em->persist($user);
$this->em->flush();
}
// ...
private function calculateUserStatistics(User $user)
{
// calculate and set statistics on user object
}
}
This approach has a number of advantages:
Decoupling from Doctrine
Your business code is no longer coupled to Doctrine, it doesn't know that Doctrine repositories/entity manager exist at all. If need arises, you can change UserRepository implementation to load users from remote API, from file on disk....from anywhere.
Model manipulation
It allows you to manipulate your models before they get to business logic, allowing you to calculate values not persisted as a field in database. Eg, to fetch them from Redis, from some API or other...
Cleaner API
It makes it really obvious what abilities your system has, making understanding easier and allowing easier testing.
Performance optimisation
It doesn't suffer from performance issues as first approach. Take the following example:
You have $eventsCount field on your User model.
If you load list of 100 users and use first approach, you would need to fire 100 queries to count number of events belonging to each user.
SELECT COUNT(*) FROM events WHERE user_id = 1;
SELECT COUNT(*) FROM events WHERE user_id = 2;
SELECT COUNT(*) FROM events WHERE user_id = 3;
SELECT COUNT(*) FROM events WHERE user_id = 4;
...
If you have your own UserRepository implementation, however, you can just make method getEventCountsForUsers($userIds) which would fire one query:
SELECT COUNT(*) FORM events WHERE user_id IN (:user_ids) GROUP BY user_id;
You can implement your own repository to include your own sql queries, Symfony have documented it pretty well in their documentation, see here.
Here's how I've done it previously using annotations (this can be done via yaml too, just check the link above)...
Entity:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
// ...
private $statistic;
// ...
}
User Repository:
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function getUserStats()
{
// Use query builder to build your query for stats here
}
}
Since your custom repository is extending the EntityRepository, you will still have access to Doctrines lazy load methods (find, findBy, findAll etc...)
Maybe you don't need exactly User instance.
You can use NEW() syntax in DQL.
class UserStatDTO
{
private $user;
private $statistic;
private $sum;
public function __construct(User $user, $statistic, $sum)
{
$this->user = $user;
$this->statistic = $statistic;
$this->sum = $sum;
}
public function getUser()
{
return $this->user;
}
public function getSum()
{
return $this->sum;
}
public function getStatistic()
{
return $this->statistic;
}
}
class UserRepository
{
public function getUsersWithCalculatedStat()
{
return $this->getEntityManager()->createQuery('
SELECT NEW UserStatDTO(
u, u.statistic, u.count1 + u.count2
) FROM User
')->getResult();
}
}
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#new-operator-syntax
I'm making a website where users can create albums.
I would like to create a default album per user.
I would like to create an Album entity and to persist it in the constructor of my User class.
Is it possible ?
I just know that the entityManager is not accessible from an Entity... That's why it's a problem for me.
Even though this technically IS possible I would strongly recommend you not to do this.
To answer your question, it is possible and it would be done like this:
class User extends FOSUser
{
/**
* #ORM\OneToMany(targetEntity="Album", cascade={"persist"})
*/
private $albums;
public function __construct()
{
$this->albums = new ArrayCollection();
$this->addAlbum(new Album());
}
public function addAlbum(Album $album)
{
$this->albums[] = $album;
}
public function getAlbums()
{
return $this->albums:
}
}
With setup like this whenever you create a new user and save it, a related album will be created together with it. I have to repeat, even though it's possible, don't do it like this.
Good solutions
There are few strategies that can be used to achieve what you want.
FOSUserBundle master
If you're not using 1.3.x version of FOSUserBundle but master, you can see that RegistrationController fires a few events. The one you're interested in is FOSUserEvents::REGISTRATION_INITIALIZE. You should create an event listener and add album to user in your listener.
FOSUserBundle 1.3.x
If you're using one of older versions, these events don't exist unfortunately and you can do it two ways.
Extend FOSUserBundle UserManager and override createUser method. You can add your album adding logic there. I would prefer this approach.
Override FOSUserBundle RegistrationController::registerAction. It can be viable option sometimes but in your case I think option 1 is better.
I am working on building a lightweight MVC, mainly for the learning process but I would like it to be good enough to use eventually.
Below is a basic example/demo of how a basic controller might would look, let's assume the URI has been processed and routed to this controller and these 2 methods.
1) I need to get data from database/cache/etc... inside my Model classes, I just need help on how I should load my models into my example controller below, you can see that I have added this below $profileData = $this->model->getProfile($userId) that is just made up and does not exist's, how could I get something like that to work though? Or should I load the model into the class a different way?
2) A lot of pages will require a user to be logged into the site. SHould I process that part below in the controller to check if a user is logged in, example, before building the profile page, check if user is logged in, if not then build a login page instead and add these checks inside of each controller method/page?
/**
* Example Controller
*/
class User_Controller extends Core_Controller {
// domain.com/user/id-53463463
function profile($userId)
{
//GET data from a Model
$profileData = $this->model->getProfile($userId);
$this->view->load('userProfile', $profileData);
}
// domain.com/user/friends/
function friends()
{
//GET data from a Model
$friendsData = $this->model->getFriendlist();
$this->view->load('userFriends', $friendsData);
}
}
core
abstract class Core_Controller {
protected $view;
protected $model;
function __construct(DependencyContainer $dependencyContainer){
$this->view = new Core_View();
//$this->view = $dependencyContainer->get(view);
}
}
There are probably tons of ways to accomplish what you are trying.
The "easiest" is probably to just override the constructor and instantiate the model directly.
in User_Controller:
public function __construct(DependencyContainer $dc) {
parent::__construct($dc);
$this->model = new User_Model();
}
I'm guessing that you are looking for something a little more automated though. If you want the Model to have the same name as the controller minus "_Controller", just use get_class($this) in the constructor and use PHP's string functions to parse out what you want. Once you have that in a variable, you can use that variable to instantiate the model:
in Core_Controller:
public function __construct(DependencyContainer $dc) {
$this->view = new Core_View();
// $model_class should be 'User_Model' now
$model_class = str_replace('_Controller', '_Model', get_class($this));
// now instantiate the model
$this->model = new $model_class();
}
I haven't actually worked with any framework that can only have one model associated with each controller (except may CakePHP? I can't remember). With Symfony, the models and controllers are completely decoupled so you can use any model with any controller. You just instantiate the model as need. Symfony use the Doctrine ORM so for example, in a controller action, if you needed a model you would do something like this:
$model = Doctrine::getTable('User');
It might be worthwhile to consider a design more like that in order to promote a decoupled design and I promise that you will want more than one model in some controller at some point.
2.) As far as authentication. Something that seems to be fairly common is to have some sort of setting (whether in a config file or a member variable) that says whether or not the current action needs the user to be authenticated. This is processed each time the action runs (Yii calls these kinds of things filters). If the user needs to be logged in, it stores the page that they are trying to access, and then redirects them to a log in page (you should only ever have to create one). Once they properly authenticate, it will redirect them back to where they were originally heading.
i have created a controller in which the default index action is being used to display a login form and then authenticate the user. However i have ended up having to add functions into the controller which i feel will clutter up the controller.
for example i have functions like:
protected function _process($values)
{
// Get our authentication adapter and check credentials
$adapter = $this->_getAuthAdapter();
$adapter->setIdentity($values['username']);
$adapter->setCredential($values['password']);
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($adapter);
if ($result->isValid()) {
$user = $adapter->getResultRowObject();
$auth->getStorage()->write($user);
return true;
}
return false;
}
protected function _getAuthAdapter() {
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
$authAdapter->setTableName('users')
->setIdentityColumn('username')
->setCredentialColumn('password')
->setCredentialTreatment('SHA1(CONCAT(?,salt))');
return $authAdapter;
}
What would you recommend to do, maybe create another directory called custom_classes and include the file into my controller this way?
Given your example I would put this into some sort of ACL centered Zend_Controller_Plugin class and register this class in your bootstrap to ensure it is always run.
Also, depending on what you are doing, the logic could go into one of your Models.
Models should represent a collection of information that make up an entity of some sort, the Model class should also be responsible for reading, updating, removing and adding new Models.
For example a User Model could represent a User in a table in your database. It might contain functions like updateFailedLogins(), updateLogins() and specific functions related to the log in process for that particular User Model.
If the methods you add are necessary for the controller to handle the input it receives from the User Interface, then it's fine to have them there. If it's a different concern, identify which responsibility it is and add the method there.
If you find the logic in the methods is useful in many controllers, consider making the logic into a Zend_Controller_Action_Helper. If you find this is that has to run on every request but is not directly related to a Controller Action, make it into a Zend_Controller_Plugin.
In case of authenticating users, you might want to create a Zend_Controller_Plugin that authenticates the user before the actual Controller Action is called.