Laravel - Good practice to put query in custom request? - laravel

I use a custom query, and I add a custom rule to it too, and I wanted to know if putting eloquent queries in the rule is a "good practice"?
For me it's pretty dirty, but I don't really see how else to do it, and I haven't found any answers...
I have to make in the rule, parameters...
Edit (add code)
<?php
namespace App\Rules;
use App\Model\Laravel\Api;
use App\Model\Laravel\Package;
use App\Model\Laravel\User;
use Illuminate\Contracts\Validation\Rule;
class ApiMax implements Rule
{
protected $user;
/**
* Create a new rule instance.
*
* #param $user
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$maxApi = $this->getPackageInfo($this->user)->max_api - $this->sumApis($this->user);
if($value > $maxApi)
return false;
return true;
}
public function getPackageInfo(User $user)
{
return Package::where('id', $user->getPackageId())->first();
}
public function sumApis(User $user) {
return Api::where('user_id', $user->getId())->count();
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'You have reached the maximum number of apis with your current offer.';
}
}
Thank you in advance for your tips

Related

Laravel 8 Fortify - 2FA only when the user logs in from a new device

I am implementing two-factor authentication (2FA) in my Laravel 8 application.
The 2FA is applied every time the user logs in. However, I don't really feel that 2FA is necessary every time, I even find it annoying. As a solution I am thinking of applying it only when the user connects from a new device. Is there someone who has already done it or who can give me a hint of the changes that would be necessary?
I have got it. Here are the steps I have followed:
In the config file fortify.php I have added
'pipelines' => [
'login' => [
App\Actions\Fortify\RedirectIfTwoFactorAuthenticatable::class,
Laravel\Fortify\Actions\AttemptToAuthenticate::class,
Laravel\Fortify\Actions\PrepareAuthenticatedSession::class,
]
]
I have added the field two_factor_cookies to the User class.
I have customized the RedirectIfTwoFactorAuthenticatable class of
Fortify:
<?php
namespace App\Actions\Fortify;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable as DefaultRedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\TwoFactorAuthenticatable;
class RedirectIfTwoFactorAuthenticatable extends DefaultRedirectIfTwoFactorAuthenticatable
{
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
$user = $this->validateCredentials($request);
if (optional($user)->two_factor_secret &&
in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user)) &&
$this->checkIfUserDeviceHasNotCookie($user)) {
return $this->twoFactorChallengeResponse($request, $user);
}
return $next($request);
}
/**
* This checks if the user's device has the cookie stored
* in the database.
*
* #param \App\Models\User\User $user
* #return bool
*/
protected function checkIfUserDeviceHasNotCookie($user)
{
$two_factor_cookies = json_decode($user->two_factor_cookies);
if (!is_array($two_factor_cookies)){
$two_factor_cookies = [];
}
$two_factor_cookie = \Cookie::get('2fa');
return !in_array($two_factor_cookie,$two_factor_cookies);
}
}
In the FortifyServiceProvider I have added a customized TwoFactorLoginResponse.
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Http\Responses\FailedPasswordResetLinkRequestResponse;
use App\Http\Responses\FailedPasswordResetResponse;
use App\Http\Responses\LockoutResponse;
use App\Http\Responses\LoginResponse;
use App\Http\Responses\LogoutResponse;
use App\Http\Responses\PasswordResetResponse;
use App\Http\Responses\RegisterResponse;
use App\Http\Responses\SuccessfulPasswordResetLinkRequestResponse;
use App\Http\Responses\TwoFactorLoginResponse;
use App\Http\Responses\VerifyEmail;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse as FailedPasswordResetLinkRequestResponseContract;
use Laravel\Fortify\Contracts\FailedPasswordResetResponse as FailedPasswordResetResponseContract;
use Laravel\Fortify\Contracts\LockoutResponse as LockoutResponseContract;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Laravel\Fortify\Contracts\LogoutResponse as LogoutResponseContract;
use Laravel\Fortify\Contracts\PasswordResetResponse as PasswordResetResponseContract;
use Laravel\Fortify\Contracts\RegisterResponse as RegisterResponseContract;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse as SuccessfulPasswordResetLinkRequestResponseContract;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerResponseBindings();
}
/**
* Register the response bindings.
*
* #return void
*/
protected function registerResponseBindings()
{
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
$this->app->singleton(LogoutResponseContract::class, LogoutResponse::class);
$this->app->singleton(TwoFactorLoginResponseContract::class, TwoFactorLoginResponse::class);
$this->app->singleton(RegisterResponseContract::class, RegisterResponse::class);
$this->app->singleton(LockoutResponseContract::class, LockoutResponse::class);
$this->app->singleton(SuccessfulPasswordResetLinkRequestResponseContract::class, SuccessfulPasswordResetLinkRequestResponse::class);
$this->app->singleton(FailedPasswordResetLinkRequestResponseContract::class, FailedPasswordResetLinkRequestResponse::class);
$this->app->singleton(PasswordResetResponseContract::class, PasswordResetResponse::class);
$this->app->singleton(FailedPasswordResetResponseContract::class, FailedPasswordResetResponse::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Fortify::ignoreRoutes();
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::twoFactorChallengeView('auth.two-factor-challenge');
Fortify::confirmPasswordView(function (Request $request) {
if ($request->ajax()) {
return view('auth.confirm-password-form');
} else {
return view('auth.confirm-password');
}
});
Fortify::requestPasswordResetLinkView(function () {
return view('auth.forgot-password');
});
Fortify::resetPasswordView(function ($request) {
return view('auth.reset-password', ['request' => $request,'token' => $request->route('token')]);
});
Fortify::registerView(function () {
return view('auth.register');
});
Fortify::verifyEmailView(function () {
return view('auth.verify');
});
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
/*RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->email.$request->ip());
});*/
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}
Finally, the TwoFactorLoginResponse:
<?php
namespace App\Http\Responses;
use Illuminate\Http\JsonResponse;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
class TwoFactorLoginResponse implements TwoFactorLoginResponseContract
{
/**
* Create an HTTP response that represents the object.
*
* #param \Illuminate\Http\Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function toResponse($request)
{
$user = \Auth::user();
$this->storeCookieIfNotInDB($user);
$role = $user->role;
if ($request->wantsJson()) {
return new JsonResponse('', 204);
}
if ($role == "0") {
return redirect()->route('user.home');
} else {
return redirect()->route('admin.home');
}
}
/**
* Store the cookie if it is not in the database.
*
* #param \App\Models\User\User $user
* #return void
*/
protected function storeCookieIfNotInDB($user)
{
$two_factor_cookies = json_decode($user->two_factor_cookies);
if (!is_array($two_factor_cookies)){
$two_factor_cookies = [];
}
$two_factor_cookie = \Cookie::get('2fa');
if (!in_array($two_factor_cookie,$two_factor_cookies)) {
$two_factor_cookie = md5(now());
$two_factor_cookies[] = $two_factor_cookie;
if (count($two_factor_cookies) > 3) {
array_shift($two_factor_cookies);
}
$user->two_factor_cookies = json_encode($two_factor_cookies);
$user->save();
$lifetime = 60 * 24 * 365; //one year
\Cookie::queue('2fa',$two_factor_cookie,$lifetime);
}
}
}
Upon login, it will look for the cookie 2fa. If its content is stored in the database, it will not be necessary to enter the code again. To prevent unlimited cookie content from being saved in the DB you can add a maximum limit (I have set it 3).
Thanks to Maarten Veerman for the inital help.
According to this line: https://github.com/laravel/fortify/blob/82c99b6999f7e89f402cfd7eb4074e619382b3b7/src/Http/Controllers/AuthenticatedSessionController.php#L80
you can create a pipelines.login entry in your fortify config file.
The solution would be to:
create the config entry
copy the pipeline setup in the above file, line 84.
create a custom AttemptToAuthenticate class, make sure the pipeline config points to your new class.
make the new class extend the default fortify AttemptToAuthenticate class.
overwrite the handle function, add your logic in the new function, where you check for a cookie on the device.

Can I use dynamic model on middleware?

I have 2 routes that requires a person to be tagged to access the discussions.
http://localhost:8000/api/fieldReports/{fieldReport}/discussions
http://localhost:8000/api/agendas/{agenda}/discussions
Currently, I have created this middleware, but instead of pointing right to a specific model, and duplicate it for each model with the exact same functionality, I want it to be more reusable.
Middleware\ForbidUntaggedUser.php
class ForbidUntaggedUser
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$user = $request->user();
$report = $request->report; // <-- I hardcoded the model, I want this to be dynamic
// The `taggedUsers` remains the same (identical) for each model that has tagging system on it.
if (!$report || !$report->taggedUsers->contains($user->id)) {
return response()->json('Your action is unauthorized.', 403);
}
return $next($request);
}
}
I've tried to use Policy but it doesn't work, so I think I need a middleware for this.
Policies\FieldReportDiscussionPolicy.php
class FieldReportDiscussionPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* #param \App\Models\User $user
* #return mixed
*/
public function viewAny(User $user, FieldReport $fieldReport)
{
return $user->can('view any fieldReportDiscussion')
&& $fieldReport->taggedUsers->contains($user->id);
}
... // and so on..
}
Controllers\FieldReportDiscussionController.php
class FieldReportDiscussionController extends Controller
{
protected $model;
/**
* Create new instance.
*
* #return void
*/
public function __construct()
{
$this->authorizeResource(
FieldReportDiscussion::class,
['fieldReportDiscussion', 'fieldReport'] // This gave me error "Array to string conversion"
);
$this->model = new FieldReportDiscussion;
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(FieldReport $fieldReport)
{
$discussions = $this->model->registries($fieldReport)->paginate(100);
return response()->json($discussions);
}
}
I need the dependency injection on the controller because the route is nested with each model as the parent, like this one..
routes\api.php
Route::apiResource('fieldReports', FieldReportController::class);
Route::apiResource('fieldReports.discussions', FieldReportDiscussionController::class)->except(['update'])->parameter('discussions', 'fieldReportDiscussion');
So, what's the solution for this? Can I make it dynamic (the first request object)?
I think you're on the right track with using middleware, although you'll need some conditional checks, something along the lines like:
class ForbidUntaggedUser
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$instance = null;
if ($request->report !== null) {
$instance = $request->report;
} else if ($request->agenda !== null) {
$instance = $request->agenda;
}
if (!$instance || !$instance->taggedUsers->contains(auth()->id())) {
return response()->json('Your action is unauthorized.', 403);
}
return $next($request);
}
}
If you have named correctly your params in your controller i.e. in a resoruce controller
<?php
namespace App\Http\Controllers;
use App\Models\Agenda;
class AgendaController extends Controller
{
public function show(Agenda $agenda)
{
....
}
}
The first parameter of your request will be the named model, so you can get the model with the getModel() function, if you are not sure, you can search it with findModel() function.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
class MyMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$model = $this->getModel($request);
$tryGetModel = $this->findModel($request);
}
private function getModel(Request $request) : Model|null
{
$modelParameterName = $request->route()->parameterNames()[0];
return $request->$modelParameterName ?? null;
}
private function findModel(Request $request) : Model|null
{
foreach($request->route()->parameters() as $param)
{
if($param instanceof Model)
return $param;
}
return null;
}
}

Laravel - Query scopes across models

In a nutshell, I want to create a function that my query scopes can use across multiple models:
public function scopeNormaliseCurrency($query,$targetCurrency) {
return $query->normaliseCurrencyFields(
['cost_per_day','cost_per_week'],
$targetCurrency
);
}
I have got my logic working within this scope function no problem, but I want to make this code available to all my models, as there are multiple currency fields in different tables and I don't want to be replicating the code in each query scope - only specify the columns that need attention.
So, where would I make my function normaliseCurrencyFields? I have extended the Model class as well as used the newCollection keyword to extend Collection but both result in Call to undefined method Illuminate\Database\Query\Builder::normaliseCurrencyFields() errors.
I have looked into Global Scoping but this seems to be localised to a Model.
Am I along the right lines? Should I be targeting Eloquent specifically?
Create an abstract base model that extends eloquent then extend it with the classes you want to have access to it. I do this for searching functions, uuid creation, and class code functions. So that all of my saved models are required to have to certain attributes and access to my searching functions. For instance I created a static search function getobjectbyid(). So that when extended I can call it like so:
$user = User::getobjectbyid('habwiifnbrklsnbbd1938');
Thus way I know I am getting a user object back.
My base model:
<?php
/**
* Created by PhpStorm.
* User: amac
* Date: 6/5/17
* Time: 12:45 AM
*/
namespace App;
use Illuminate\Database\Eloquent\Model as Eloquent;
abstract class Model extends Eloquent
{
protected $guarded = [
'class_code',
'id'
];
public $primaryKey = 'id';
public $incrementing = false;
public function __construct($attributes = array()) {
parent::__construct($attributes); // Eloquent
$this->class_code = \App\Enums\EnumClassCode::getValueByKey(get_class($this));
$this->id = $this->class_code . uniqid();
return $this;
}
public static function getObjectById($id){
$class = get_called_class();
$results = $class::find($id);
return $results;
}
public static function getAllObjects(){
$class = get_called_class();
return $class::all();
}
my user model:
<?php
namespace App;
use Mockery\Exception;
use Illuminate\Support\Facades\Hash;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use App\Model as Model;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'contact', 'username', 'email_address'
];
/**
* The column name of the "remember me" token.
*
* #var string
*/
protected $rememberTokenName = 'remember_token';
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'remember_token', 'active'
];
/**
* the attributes that should be guarded from Mass Assignment
*
* #var array
*/
protected $guarded = [
'created_at', 'updated_at', 'password_hash'
];
/**
* Define table to be used with this model. It defaults and assumes table names will have an s added to the end.
*for instance App\User table by default would be users
*/
protected $table = "user";
/**
* We have a non incrementing primary key
*
* #var bool
*/
public $incrementing = false;
/**
* relationships
*/
public function contact(){
// return $this->hasOne(Contact::class, 'id', 'contact_id');
return $this->hasOne(Contact::class);
}
public function customers(){
// return $this->hasOne(Contact::class, 'id', 'contact_id');
return $this->hasMany(Customer::class);
}
/**
* User constructor.
* #param array $attributes
*/
public function __construct($attributes = array()) {
parent::__construct($attributes); // Eloquent
// Your construct code.
$this->active = 1;
return $this;
}
/**
* #param $password string
* set user password_hash
* #return $this
*/
public function setPassword($password){
// TODO Password Validation
try{
$this->isActive();
$this->password_hash = Hash::make($password);
$this->save();
} catch(\Exception $e) {
dump($e->getMessage());
}
return $this;
}
/**
* Returns whether or not this use is active.
*
* #return bool
*/
public function isActive(){
if($this->active) {
return true;
} else {
Throw new Exception('This user is not active. Therefore you cannot change the password', 409);
}
}
public function getEmailUsername(){
$contact = Contact::getObjectById($this->contact_id);
$email = Email::getObjectById($contact->email_id);
return $email->username_prefix;
}
/**
* #return string
*
* getFullName
* returns concatenated first and last name of user.
*/
public function getFullName(){
return $this->first_name . ' ' . $this->last_name;
}
/**
* Get the name of the unique identifier for the user.
*
* #return string
*/
public function getAuthIdentifierName(){
return $this->getKeyName();
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier(){
return $this->{$this->getAuthIdentifierName()};
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword(){
return $this->password_hash;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken(){
if (! empty($this->getRememberTokenName())) {
return $this->{$this->getRememberTokenName()};
}
}
/**
* Set the token value for the "remember me" session.
*
* #param string $value
* #return void
*/
public function setRememberToken($value){
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
* Get the column name for the "remember me" token.
*
* #return string
*/
public function getRememberTokenName(){
return $this->rememberTokenName;
}
/**
* Get the e-mail address where password reset links are sent.
*
* #return string
*/
public function getEmailForPasswordReset(){
}
/**
* Send the password reset notification.
*
* #param string $token
* #return void
*/
public function sendPasswordResetNotification($token){
}
public function validateAddress(){
}
}
a TestController:
public function test(){
$user = User::getObjectById('USR594079ca59746');
$customers = array();
foreach ($user->customers as $customer){
$contact = Contact::getObjectById($customer->contact_id);
$name = PersonName::getObjectById($contact->personname_id);
$c = new \stdClass();
$c->id = $customer->id;
$c->name = $name->preferred_name;
$customers[] = $c;
}
$response = response()->json($customers);
return $response;
}
Take note on how getObjectById is extended and available to my other classes that extend my base model. Also I do not have to specify in my user model an 'id' or 'class_code' and when my user model is constructed it calls the parent constructor which is the constructor on my base model that handles 'id' and 'class_code'.

What is the best way for reusable values throughout the application in Symfony 3?

I want to have a file or list that I can update easily with values that might change throughout my application.
I don't really want to hard code text values into the templates. I prefer to have all of these values in one place and labelled correctly.
Examples of values that might get updated are:
Page title
Logo text
Brand or company name
I have thought about two options:
Add them to the twig config in config.yml. This is a bit messy and doesn't seem organised if I decide to put a lot of values there.
Make a database table for these and include the entity in each controller where I need to use the values. This might be creating too much work.
Are there any other options or are one of these more suitable?
Thank you.
You need to create a twig function and use it to return the value you want. For example:
namespace AppBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TwigExtension extends \Twig_Extension implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #var ContainerInterface
*/
protected $container;
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('parameter', function($name)
{
try {
return $this->container->getParameter($name);
} catch(\Exception $exception) {
return "";
}
})
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'app.twig.extension';
}
}
This will create a function called parameter and once you call it in twig {{ parameter('my.parameter') }} it will return the parameter. You need to load it as a service, which you can do by adding the following to your services.yml file:
app.twig.extension:
class: AppBundle\Twig\TwigExtension
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: twig.extension }
From personal experience people usually want to be able to change some of the parameters. This is why I usually prefer to create a Setting or Parameter entity which would look something like this:
/**
* Setting
*
* #ORM\Table(name="my_parameters")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ParameterRepository")
*/
class Parameter
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="parameter_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="value", type="text", nullable=true)
*/
private $value;
/**
* #param string|null $name
* #param string|null $value
*/
public function __construct($name = null, $value = null)
{
$this->setName($name);
$this->setValue($value);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Parameter
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set value
*
* #param string $value
*
* #return Parameter
*/
public function setValue($value = null)
{
$this->value = serialize($value);
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
$data = #unserialize($this->value);
return $this->value === 'b:0;' || $data !== false ? $this->value = $data : null;
}
}
Then I would add a CompilerPass which will help get all of the parameters from the database and cache them so that your app doesn't make unnecessary sql queries to the database. That might look something similar to the following class:
// AppBundle/DependencyInjection/Compiler/ParamsCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParamsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$settings = $em->getRepository('AppBundle:Parameter')->findAll();
foreach($settings as $setting) {
// I like to prefix the parameters with "app."
// to avoid any collision with existing parameters.
$container->setParameter('app.'.strtolower($setting->getName()), $setting->getValue());
}
}
}
And finally, in your bundle class (i.e. src/AppBundle/AppBundle.php) you add the compiler pass:
namespace AppBundle;
use AppBundle\DependencyInjection\Compiler\ParamsCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $builder)
{
parent::build($builder);
$builder->addCompilerPass(new ParamsCompilerPass(), , PassConfig::TYPE_AFTER_REMOVING);
}
}
Now you can create a DoctrineFixture template to load the parameters you use all the time. With the TwigExtension you will still be able to call the parameter from the twig template and you can create a web UI to change some of the parameters/settings.

Laravel polymorphic voting system with namespaces

I'm attempting to add what I hope is a simple voting module to posts and comments. A "Connection" is a type of post in my application. Users can vote up or down a Connection, or a Comment.
The issue I'm running into is when I attempt to attach a vote to a Connection. I receive this error: Class name must be a valid object or a string.
Here's the line of code in question:
$voteToCast = $vote->voteable()->associate($voteable);
I am certain the $voteable var is an instance of an Ardent/Eloquent model, so I can only presume the error lies within the way I am namespacing my models, or some pathetic typo I am too blind to see. Any help would be greatly appreciated.
Thanks!
Connection Model (type of post):
...
public function votes()
{
return $this->morphMany('Acme\Votes\Vote', 'voteable');
}
And the Votes Model:
/* Votes Model */
namespace Acme\Votes;
use Illuminate\Database\Eloquent\Model;
use LaravelBook\Ardent\Ardent;
class Vote extends Ardent {
protected $table = 'votes';
protected $fillable = [
'value',
'votable_id',
'voteable_type'
];
/**
* Establish the polymorphic relationship
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function voteable()
{
return $this->morphTo();
}
public function users()
{
return $this->belongsTo('Acme\\Users\\User');
}
/**
* Vote the item up
*
* #param Model $voteable
* #return mixed
*/
public static function up(Model $voteable)
{
return (new static)->cast($voteable, 1);
}
/**
* Vote the item down
*
* #param Model $voteable
* #return mixed
*/
public static function down(Model $voteable)
{
return (new static)->cast($voteable, -1);
}
/**
* Execute the vote
*
* #param Model $voteable
* #param int $value
* #return bool
*/
protected function cast(Model $voteable, $value = 1)
{
if (!$voteable->exists) return false;
$vote = new static;
$vote->value = $value;
$voteToCast = $vote->voteable()->associate($voteable);
$voteToCast->save();
}
/**
* Restrict the votes so the absolute value is 1
*
* #param $value
*/
public function setValueAttribute($value)
{
$this->attributes['value'] = ($value == -1) ? -1 : 1;
}
}
Votes Controller:
...
public function cast($connection)
{
$voteable = Connection::findOrFail($connection);
if (Input::get('value' < 1)){
return Vote::down($voteable);
}
return Vote::up($voteable);
}
After some more troubleshooting, this appears to be an issue with the way Ardent handles relationships. I was able to use Eloquent on my Vote model instead of Ardent and the voting mechanism now works flawlessly.

Resources