full access log in laravel 5.8 with session (error bag + flash) data - laravel

I'm trying to implement a full access log in a Laravel 5.8 project and that's what I've come up so far: a table named access_logs in a secondary database so than non-relevant log data won't make the db bulky during backup, and custom primary key with no AUTO_INCREMET so can be purged old data without worrying about resetting incremental id while new data is being created. the model is following:
class AccessLog extends Model
{
public $timestamps = true;
public $incrementing = false; // custom id, purgeable
protected $connection= 'mysql2';
protected $fillable = [
'id', // custom id, purgeable
'auth_guard',
'auth_id',
'url',
'method',
'referer',
'request',
'session',
'additional_data',
'ip',
];
}
and the DB writing part in the middleware is following:
$logData = [
'id' => join('_', [
time(),
Auth::user()->id ?? 0,
rand(100000, 999999),
]),
'ip' => json_encode(request()->ips() ?? ''),
'auth_guard' => auth()->getDefaultDriver() ?? null,
'auth_id' => auth()->user()->id ?? null,
'url' => url()->current(),
'method' => request()->method(),
'referer' => url()->previous(),
'request' => json_encode([
$this->removeKeys(request()->all(), [
'password',
]),
$_FILES ?? [],
request()->headers->all(),
]),
'session' => json_encode(session()->all()),
];
AccessLog::create($logData);
the middleware is registered in the kernel file under protected $middlewareGroups['web'].
now my question is I'm trying to log form validation data (ErrorBag?) as well as flash messages, but it seems those do not exists or saved with the session data (session()->all()), how to include/save those with the log?

Related

Laravel FormRequest is modifying the input

I am having trouble using the Laravel Validator to validate some data. The validator is modifying properties of the input to null.
The data passed to be validated is a mix of array and objects (in this case, a model instance).
Just for clarification: I know how to use FormRequest in controllers, I am full aware that Laravel would inject the FormRequest in the methods, and FormRequest is primaly to be used to validate user data, etc, etc. The point is why the validator need to modify the data I sent to validation?
Here's an example that you can directly paste in a php artisan tinker session:
$rules = [
'users' => [
'required',
'array',
'min:1',
],
'users.*' => [
'required',
],
'users.*.name' => [
'required',
'string',
'max:255',
],
'users.*.age' => [
'required',
'integer',
],
'users.*.best_friend' => [
'required',
],
];
$data = [
'users' => [
(new \App\Models\User)->forceFill([
'name' => 'USER #1',
'age' => 30,
'best_friend' => (new \App\Models\User)->forceFill(['name' => 'User X'])
]),
],
];
echo 'BEFORE: ' . data_get($data, 'users.0.name'); // USER #1
$validator = Validator::make($data, $rules);
echo 'AFTER: ' . data_get($data, 'users.0.name'); // NULL
dd($data);
OK, the data PASSES. But the problem is that the validation modified the variable $data, setting null to the fields with these patterns: users.*.name, users.*.age and users.*.best_friend.
If I dare to validate any model attribute, it sets to null.
I debugged and I reached the source of the modification:
/vendor/laravel/framework/src/Illuminate/Validation/ValidationData.php:42:
/**
* Gather a copy of the attribute data filled with any missing attributes.
*
* #param string $attribute
* #param array $masterData
* #return array
*/
protected static function initializeAttributeOnData($attribute, $masterData)
{
$explicitPath = static::getLeadingExplicitAttributePath($attribute);
$data = static::extractDataFromPath($explicitPath, $masterData);
if (! str_contains($attribute, '*') || str_ends_with($attribute, '*')) {
return $data;
}
// here some debug info:
// $explicitPath="users"
// $attribute="users.*.name"
// $data=User
return data_set($data, $attribute, null, true);
}
I know data_set modifies by reference.
But I could not understand why the code modifies the data if there is data already there. Should not it check for data before setting to null?
The validator is making the validated properties of my model to be null. Why and how to fix?
Maybe a different approach? Maybe this could be considered an bug/improvement for the Illuminate lib?
Any help would be apreciated.
VERSIONS:
Laravel Framework 9.33.0
PHP 8.1.2
Laravel transforms the keys from your validation rules: name and age. But that didn't work as expected because users are objects. To solve that you need to call toArray() after forceFill
$data = [
'users' => [
(new \App\Models\User) -> forceFill([
'name' => 'USER #1',
'age' => 30,
'best_friend' => (new \App\Models\User) -> forceFill(['name' => 'User X'])
])->toArray(),
]
];
If you need validation for best_friend.name you need to call toArray() on that too. But without validation you will get the object as it is.

dynamic mail configuration using Laravel

In my Laravel application, I am trying to send mail notification based on the company_id of the logged in user:
I have this:
$mail=DB::table('mail_settings')->first();
$config = array(
'driver' => $mail->driver,
'host' => $mail->host,
'port' => $mail->port,
'from' => array('address' => $mail->from_address, 'name' => $mail->from_name),
'encryption' => $mail->encryption,
'username' => $mail->username,
'password' => $mail->password,
'sendmail' => '/usr/sbin/sendmail -bs',
'pretend' => false
);
Config::set('mail',$config);
Models
class Company extends Model
{
protected $table = 'companies';
protected $fillable = [
'id',
'organization_name'
];
}
class User extends Authenticatable
{
protected $fillable = [
'name',
'company_id',
'email',
];
}
Is there any way to override default mail configuration (in app/config/mail.php) on-the-fly (e.g. configuration is stored in database) before mailer transport is created?
Thanks
Is there any way to recreate laravel swiftmailer transport so it can pick up updated config values?
The Mailer class is created in the Illuminate\Mail\MailManager class's resolve() method. If you want to dynamically create a mailer, you need to adapt this function in your Controller to use your $config array and return a Mailer from which you could chain the usual methods.
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
}
// Once we have created the mailer instance we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$name,
$this->app['view'],
$this->createSwiftMailer($config),
$this->app['events']
);
if ($this->app->bound('queue')) {
$mailer->setQueue($this->app['queue']);
}
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since these will be sent to a single email address.
foreach (['from', 'reply_to', 'to', 'return_path'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
}

How to use relationships in laravel 8

my question has two parts
Firstly, My if statement is not working. My if statement is as followed:
if ($request->is_published) {
$resources_page->published_at = now();
}
This is stored in my controller, I have a model for this and it is as followed:
public function is_published()
{
return $this->published_at !== null;
}
It is meant to check whether my checkbox is checked and return the timestamp, I have it cast in my model like followed:
protected $casts = [
'published_at' => 'datetime',
];
And in my view:
#include('components.form.input-checkbox', [
'label' => 'Publish?',
'form_object' => 'page',
'name' => 'is_published'
])
Could anyone elude to the answer?
Secondly, when trying to sync, it is not storing to my resources_category_resources_page table
In my controller, i have the following code
$resources_page->resources_categories()->sync(
ResourcesCategory::whereIn('slug', $request->resources_categories)->pluck('id')
);
In my model I have the relationships declared properly, so I don't know why its not storing?

How in feature testing use data from factory?

In laravel 5.8 app with tests I make posting data with some dummy data, like:
$newVoteCategoryRow= [
'id' => null,
'name' => $new_vote_category_row_name,
'meta_description' => 'vote category meta_description on ' . now(),
'meta_keywords' => [ 'vote category meta_description on ' . now(), 'meta_keywords' ],
'active' => true,
'in_subscriptions' => true,
];
$response = $this->actingAs($loggedUser)->post('/admin/vote-categories', $newVoteCategoryRow);
$this->assertCount( $vote_categories_count+1, VoteCategory::all() );
it works ok, but actually I have factory for VoteCategory table in /database/factories/VoteCategoryFactory.php, defined :
<?php
use Faker\Generator as Faker;
use \Cviebrock\EloquentSluggable\Services\SlugService;
use App\VoteCategory;
$factory->define(App\VoteCategory::class, function (Faker $faker) {
$name= 'Vote category ' . $faker->word;
$slug = SlugService::createSlug(VoteCategory::class, 'slug', $name);
return [
'name' => $name,
'slug' => $slug,
'active' => true,
'in_subscriptions' => false,
'meta_description' => $faker->text,
'meta_keywords' => $faker->words(4),
];
});
and my question is if there is a way in post request instead of $newVoteCategoryRow array use my factory, not adding row in database but
reading data from factory for post request ?
to achieve that you just need to use your factory within the test case method:
to create VoteCategory u have to methods, the first one is make and this one will create an instance of VoteCategory without persisting it within the database, and the create method will persist the new VoteCategory within the database.
in your case, you want to create a new instance without adding it to the database, for that you just need to use make:
$newVoteCategoryRow = factory('App\VoteCategory')->make(); // add this line to your test case method.
$response = $this->actingAs($loggedUser)->post('/admin/vote-categories', $newVoteCategoryRow->toArray());
$this->assertCount( $vote_categories_count+1, VoteCategory::all());
for more information, you can check the doc Laravel 5.8: using-factories

How to properly hydrate and extract Doctrine Entities from Zend Forms

I'm just starting out with Doctrine and was rewriting some code to use Doctrine entities in some Forms.
I have an Entity Business which has some 1:n relations with addresses, employees, emails etc. the Setup is really basic and working fine.
To add new Businesses i created a BusinessForm and Fieldsets for each of my entities. Here the constructor of the form:
public function __construct($scenario='create', $entityManager = null) {
parent::__construct('business_form');
$this->scenario = $scenario;
$this->entityManager = $entityManager;
$this->setAttribute('method', 'post');
$businessFieldset = new BusinessFieldset($this->entityManager);
$businessFieldset->setUseAsBaseFieldset(true);
$this->add($businessFieldset);
$hydrator = new DoctrineHydrator($this->entityManager, new Business());
$this->setHydrator($hydrator);
$this->addElements();
$this->addInputFilter();
}
addElements just adds a Submit and CSRF input.
And here the Controller action:
public function addAction(){
$form = new BusinessForm('create', $this->entityManager);
if ($this->getRequest()->isPost()) {
$data = $this->params()->fromPost();
$form->setData($data);
if($form->isValid()) {
// save Object
return $this->redirect()->toRoute('subcontractor', ['action'=>'index']);
}
}
return new ViewModel([
'form' => $form
]);
}
The form validates and i can get the Data from the form with $form->getData(). But i cant figure out how to get a populated Object from the form using the form's hydrator. When I use setObject(new Business()) at the start of the controller i get an error while $form->isValid() is running :
Zend\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()
Isnt that the wrong hydrator being called ?
If i dont setObject() but instead use $form->bind(new Business()) after the validation i get an empty Object from $form->getObject(). If i get the data and hydrate a new Object with the form's hydrator like so : $form->getHydrator()->hydrate($data['business], new Business()) i do get the populated Object i was expecting. (Business being the name of the base fieldset)
So my question is, how to i get the result of the last call from just using $form->getObject() after the validation?
[EDIT]
The Problem seems to be with the Collections of Fieldsets used as sub-fieldsets in the businessfieldset. If i validate the form without using the collections i do get the expected Business Object from $form->getData()
Here an example how i add the collection (in the business fieldset):
$this->add([
'name' => 'emails',
'type' => 'Zend\Form\Element\Collection',
'attributes' => [
'id' => 'business_emails',
],
'options' => [
'label' => 'Emails',
'count' => 1,
'should_create_template' => true,
'template_placeholder' => '__index__',
'allow_add' => true,
'target_element' => [
'type' => 'LwsSubcontractor\Form\EmailFieldset',
],
'target_class' => 'LwsSubcontractor\Entity\Email'
],
]);
and here the EmailFieldset:
public function __construct() {
parent::__construct('email');
$this->setObject(new Email());
$this->addElements();
}
protected function addElements() {
$this->add([
'name' => 'email',
'type' => 'Zend\Form\Element\Email',
'attributes' => [
'placeholder' => 'E-Mail (z.B. email#muster-email.de)',
'class' => 'form-control',
'required' => true,
'size' => 50,
],
'options' => [
'label' => 'Email',
],
]);
}
}
If using the Collections i get the Error message from above. So after adding a hydrator to each Fieldset i was fine.
Although i was under the impression that setting the hydrator for the form would result in the used fieldsets to inherit that hydrator.Or was this because of using the fieldsets as collections and not directly ?
You have to add the hydrator to all your fieldsets, personally I use DoctrineModule\Stdlib\Hydrator\DoctrineObject for doctrine entities.
I would also look at using the init() method to initialize your forms and add elements then register and retrieve your form and fieldsets through the FormElementManager, $serviceLocator->get('FormElementManager')->get(yourFieldsetorForm::class). The form can than be injected into your controller.
I hope this helps.

Resources