#Laravel Gates
228 messages · Page 1 of 1 (latest)
$validator = Validator::make([$attribute => $value], [
$attribute => 'unique:' . $this->databaseTable . ',' . $this->column
]);```
hi Krayvok
Hi 🙂
So let's take the project back to basics, and help me understand it more fully
You have a website (we'll call it a project herein)
The project has multiple Customers (e.g. Companies)
Each Company's data should only be avaialble to their "users"
We'll call a Company's users "company staff" to keep it all simple
All data is stored in a Single DB
Many users can do as they please within the application.
Yes, All data in a single db -- There are boot methods associated to most if not all models that require the company_id to be appended to all calls to limit them into their respective company data for a given feature.
Can we assume that the application is realistically "SaaS" / Software as a Service"
Before we continue - is there a reason you discounted the tenancy approaches?
There are a few packages out there for SaaS applications that allow you to split the data more smoothly, I'm familiar with one package that actually splits each customer into their own physical database for example
If you've discounted them, then that's not a total blocker, just wanted to ask the question
Yeah, Ive developed for about 10 years, but over the last 6 years I have grown expontentionally as a developer -- Originally when I started this application I was afraid of complexity things like multiple databases and felt the single was the best design approach for my wheel house.
For example, I use the "Stancl" - "Tenancy" package, and each one of my "tenants" (Companies) has its own database, which are all populated etc from the "core" migrations
then they all use the same codebase
Yeah, something like that would be awesome
and solve a lot of issues for me when it comes to raw sql queries etc
https://tenancyforlaravel.com/ <- It's a free package
And you can determine the tenancy by a number of methods (e.g domain/subdomain/request parameter)
because I have to remember to put in these boot methods when I cannot handle it through eloquent natively.
I am curious how a Gate would stop a user from company A alterting a user 2 from company A by injecting the id during form submission and that form request validation check the validity of the ID before its allowed to 'pass'
It’s possible to use a single database, too. I have an application that does this. I use a Website model as my “tenant” model. Because I use a route group, the Website model instance that gets injected into each controller, and I can query models through it as an almost aggregate root:
Route::group('{website:domain}')->group(function () {
Route::get('articles', [ArticleController, 'index');
});
namespace App\Http\Controllers\Website;
class ArticleController extends Controller
{
public function index(Website $website)
{
return view('website.articles.index')->with([
'articles' => $website->articles()->paginate(),
]);
}
}
Effectively the way that it works is:
You create your core website etc
In Tenancy, you specify that you want individual databases per-tenant
You add the relevant migrations to the "tenant" folder in your migrations, that should be run whenever a new tenant is created.
When you create a new "Tenant" (Company), a new database is created, and the "tenant" folder migrations are run
EVERYTHING is then run in the tenant context
Yep, definitely possible to use a single database for it too, however, I know that 90%+ of "Customers" with mature security approaches LOVE it when there's separation of data at a physical level
I've written some improved segregation approaches for v4 Stancl Tenancy, just waiting for him to review them and then either he'll add them as documents, or I'll end up writing some blog posts about it
Yeah because the risk of data leak is exponentially lowered.
Typical Example:
"Customer" will be used in place of "Tenant"
Each Customer uses the same codebase
Each Customer has their own database (which is determined automatically by the Package at runtime)
A user with "Full Admin" in Tenancy A, will NEVER be able to see the data in Tenancy B
You can put gates in-line in services if you so choose!
or in providers/AppServiceProvider
its up to you where you define them
I dont neccesary plan on different ORM, however I do wish to get on the ios/android wagon with the application so I could see repositories helping with the ORM attachments
I'd really, really, strongly advise for your use case that you take a more tenant approach to the data, at least plan on that for the future
in the meantime, how are you authenticating your Android/iOS user?
Im not but I wish to get eventually get to that point.
I originally asked martin if I should split my controllers over to API only
and he pointed me towards services
which got me on gates
because services from my new understanding can handle both
it does through form requests
and it checks if the user exsits in the db
but it doesnt validate that its not a bad actor injecting a valid id
under that company
idk how open you are to goign to a pm and doing a screenshare
Services would just be a way of using the same code in both web and API controllers, instead of copy-and-pasting code.
Okay, so you shared your route of:
Route::get('dispatch/employee/event/create/{id}', [DispatchCalendarEmployeeEventController::class,'create'])
->name('user.dispatch.calendar.employee.event.create');
So let's say I submit a request to:
YourDomain/dispatch/employee/event/create/12345
What validation are you doing here as to who/what I am?
yeah i understand that but how do you maintain consistency between the two controllers?
Consistency in what sense?
What Martin's saying is, if you use either services or traits, you can re-use methods across both API and Request, where it makes sense to do so
the Controller will access methods within traits/services
the service holds the logic? and its applied to the api controller and mvc controller?
Yeah. So using your example, you may have an EventService. It’d be a class with methods for retrieving and persisting events. So the class might have a createForEmployee method. So you could use that class and method in both a web controller and an API controller, and even a console command, because the service doesn’t care how it’s used; it just exists to encapsulate some bit of logic, and then your application uses that logic where it needs to.
Exactly that, you can do similar with Traits if you want to re-use basic logic across Controllers/Components etc
Yep, but you can use traits in your Controllers too
and went through all my models and removed duplicated relationships, or methods that are applicable to other models and wrote traits around it
I wasnt aware you could use traits in your controller
would make life simple for simple crud actions lol
I end up just C&P that stuff sadly
Traits aren't Laravel specific, they're a core PHP thing
So you can use them in Laravel, core PHP, Livewire Components etc
slightly different to a helper method, a helper method you can define as a core include
and reuse everywhere
a Trait, is just a collection of methods/attributes, that you want to use in a Class
so for example, I have a collection of Traits that relate to Models, which contain things relating to Relationships, or core Methods that I want to call on a Model (e.g. a scope)
got it
So back to Gates
how does a gate check that a user pushing the form request is the user and not injecting a valid id?
That's where you go back to - how have you authenticated the user on the API Controller
So I have no API atm.
So everythings MVC
{
$attemptResult = Auth::guard('user')->attempt(['username' => $request->username, 'password' => $request->password], $request->remember);
if ($attemptResult)
{
$user = User::where('id', Auth::guard('user')->user()->id)->first();
$user->update(['updated_at' => carbon::now()]);
if ($user->isAccountOwner()) {
if ($user->hasValidSubscription())
{
return redirect()->intended(route('user.dashboard'));
}
return redirect()->route('user.company.subscription.index');
}
if ($user->account_type === 1) {
if (!$user->onboard_complete) {
return redirect()->intended(route('user.onboard'));
}
return redirect()->route('user.company.subscription.index');
}
if ($user->account_type === 4)
{
if ($user->isValidPiggybackSubscription())
{
return redirect()->intended(route('user.dashboard'));
}
return redirect()->route('user.company.subscription.notice');
}
$request->session()->flush();
$request->session()->regenerate();
Session::flash(AlertsEnum::ERROR, 'Your account has been disabled. Please contact your account manager.');
return redirect()->back();
}
Session::flash(AlertsEnum::ERROR, 'We could not verify your account. Please check your username and password then try your request again.');
return redirect()->back()->withInput($request->only('username', 'remember'));
}```
Is my trashy user login 😛
So are you looking at
Using the API purely internally, never allowing any 3rd party access
or
Allowing some 3rd party access potentially in the future
Allowing potentionally some aspects of it, but would like to get the ability to get an ios/android app up and going so my understanding is ill need a mix of both?
Okay, you should read up on Fortify vs Sanctum, in terms of using a key based authentication mechanism, both have their pros/cons
My preference is Passport, as it’s OAuth-based, which is an actual standardised protocol, unlike Sanctum.
Or that!
As you'll need to use a 3rd party auth mechanism for authenticating the API, unless you want to re-invent the wheel
Get that working as a basic thing, put a basic Controller in your API namespace, and add the relevant route, test it using postman or similar.
Once you have that working smoothly, you can then integrate that into the Google & Apple apps, there are docs/tutorials around for this
yeah i just played with laravel api starter app
What this will mean, is that a user of the app is "authenticated" with your app
that does oauth2
was good experience playin around
what kidna elad me to the checking if I should start refactoring things to be api driven
ive been refactoring things away from seperate blade views for each action; like edit,update, etc and using a lot of javascript to do that without page loads
for a better client exp
short cutting the 2-3 page loads to update a simple boolean type thing
QoL fixes
imo
no
native javsacript
I was tlaking to someone about react on the portability
since I was comfortable writing javascript
So you're using
Controller -> View/Blade (with native JS)
yes
some pages dont even load the controller data
page loads, and ajax calls happen to give page loads instantly
I mean, for what you're doing, I'd really consider Livewire
rather than 1-3 second page loads compiling the data
which is a Laravel package
and basically JS wrapper
allowing things to be "live"
It cuts out a lot of the pain you're probably going through in terms of duplicating your code
yeah
So originally id duplicate
then I figured out ways to eliminate that
Now ive been duplicating javascript, now Im about to go through every page and start moving them to javascript classes, or external files to be brought into each page applicable.
Ive been doing a UI update and migrating to current bootstrap 5, and newer UI components
was on bootstrap 3 and im near finished on that
probably another 2-3 weeks before im fully finished
Bs3 is wholly unsupported now btw
I'm not sure how you've ended up with so many blades to be honest
I own a construction company, and I wrote this software specifically for my construction company
so I am the only developer
or why it's so difficult to update them all
are you using any blade components for example?
Don't have time today for a screenshare, plus the missus will undoubtedly kick me in the head if I start speaking, but if you have a GH repository, or can put a sample into there, then I can give you some pointers
I suspect the hole you've fallen into is not using blade components at all
<div id="layout-wrapper">
@include('user.v2.layouts.topbar')
@include('user.v2.layouts.sidebar')
<!-- ============================================================== -->
<!-- Start right Content here -->
<!-- ============================================================== -->
<div class="main-content">
<div class="page-content">
<div class="container-fluid">
@yield('content')
</div>
<!-- container-fluid -->
</div>
<!-- End Page-content -->
@include('user.v2.layouts.footer')
</div>
<!-- end main content-->
</div>
<!-- END layout-wrapper -->
<!-- Right Sidebar -->
@include('user.v2.layouts.right-sidebar')
<!-- /Right-bar -->
<!-- JAVASCRIPT -->
@include('user.v2.layouts.vendor-scripts')```
is out of my master
so I am usin sections?>
using sections is fine
ah yeah components
Now let's say, I want toi see your "user edit" blade
yeah
I m not
@section('content')
<div class="page animsition">
<div class="page-header">
<h1 class="page-title">Edit User</h1>
<ol class="breadcrumb">
<li><a href="{{ route('user.dashboard') }}">Dashboard</a></li>
<li><a href="#">Administrative</a></li>
<li><a href="{{ route('user.administrative.management.index') }}">User Management</a></li>
<li class="active">Edit User</li>
</ol>
</div>
<div class="page-content">
<div class="row">
<div class="col-lg-4 col-lg-push-4">
<div class="panel">
<form method="POST" action="{{ route('user.administrative.management.update') }}">
@csrf
<div class="panel-body">
<div class="form-group col-sm-12 col-md-12 col-lg-12">
@if ($errors->has('name'))
<p class="alert alert-danger">{{ $errors->first('name') }}</p>
@endif
<label class="control-label">Full Name</label>
<input type="text" class="form-control" name="name" value="{{ old('name', $user->name) }}"
autocomplete="off">
</div>
<div class="form-group col-sm-12 col-md-12 col-lg-12">
@if ($errors->has('email'))
<p class="alert alert-danger">{{ $errors->first('email') }}</p>
@endif
<label class="control-label">Email Address</label>
<input type="text" class="form-control" name="email"
value="{{ old('email', $user->email) }}" autocomplete="off">
</div>
</div>
<div class="panel-footer">
<input type="hidden" name="user_id" value="{{ $user->id }}">
<button type="submit" class="btn btn-block btn-success">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('extra_css')
@endsection
@section('extra_js')
@endsection
And that is where you're creating more work for yourself than you ever possibly want to
I have to go to each blade and edit stuff
Yep, which I'm sure you're finding totally painful?
very
lmao
between hundreds of controllers
and hundreds of blades
making changes, or refactoring is a task
So using a blade component, what you do is pass in the relevant items, and the rest is taken care of by the blade
ie a widget
Not a widget, just a blade component
Like you have a compoonent
of a widget
that shows a user count
you feed the number
on 10 pages
same code, but its not c&P?
ie @bladeshowcomponent(usercount, 5)
or w/e
Let's say you have a "text entry" field
Follow me!: Follow @EricTheCoder_ I publish regularly here on Dev.to, click follow if you want to...
I understand that
I am not doing that
I know there are several areas where I am falling on my face
blades, javascript, and some of thsoe controllers that have create/{id}
and gates/validatng a user can do an action
Yep, I think you're missing a massive trick in terms of your blade components
Remember you don't have to create the actual View Component PHP for it
it can just be a blade
Now this won't work off-the-bat as I've stolen the code out of one of my projects which is Livewire oriented
views/components/text-input.blade.php
@props(['errors', 'name', 'value', 'label'])
<div class="form-group col-sm-12 col-md-12 col-lg-12">
@if ($errors->has($name))
<p class="alert alert-danger">{{ $errors->first({{ $name }} ) }}</p>
@endif
<label class="control-label">{{ $label }}</label>
<input type="text" class="form-control" name="email" value="{{ $value }}" autocomplete="off">
</div>
Then I call
<x-text-input :$errors name="'email'" :$value label="'User Email'" />
when I want to update what a text input looks like, I'm doing it in one place only. You can start to add in things like merged classes/attributes etc
For example, I have around 150 forms in one of my projects, I can:
- Use the standard "text input" on any text input in any of them
- Use a "wide" text input on any text input in any of them
- Override the default classes/styles on any text input in any of them
In reality, mine looks a tad different to that as I use Livewire, so I've had to quickly swap out a few items there to make it make more sense for you
Then - I do the same for the form itself
So my "form" blade looks like
<x-default-form>
<x-text-input :$errors name="'username'" value="{{ $user_name}}" label="'User Name'" />
<x-text-input :$errors name="'email'" value="{{ $user_email}} " label="'User Email'" />
</x-default-form>
And my default-form has no PHP code/view component, it's just a default-form.blade.php file
This bit will save you ENORMOUS time in terms of updating
You can chunk of massive amounts of the code you're using into separate blades too
e.g
this:
<div class="page-header">
<h1 class="page-title">Edit User</h1>
<ol class="breadcrumb">
<li><a href="{{ route('user.dashboard') }}">Dashboard</a></li>
<li><a href="#">Administrative</a></li>
<li><a href="{{ route('user.administrative.management.index') }}">User Management</a></li>
<li class="active">Edit User</li>
</ol>
</div>
Can go into a
views/components/page-header.blade.php
Then replace with something like the below, noting that you could further extrapolate it out into "page header breadcrumt items"
<div class="page-header">
<h1 class="page-title">Edit User</h1>
<ol class="breadcrumb">
<li @class([
'active' => url()->current() == route('user.dashboard')
]>
<a href="{{ route('user.dashboard') }}"Dashboard</a>
</li>
<li @class([
'active' => url()->current() == route('user.administrative.management.index')
]>
<a href="{{ route('user.administrative.management.index') }}">User Management</a>
</li>
</ol>
</div>
ah
Effectively, 95% of my classes are contained within blade components
yeah
Hardly any of them have corresponding PHP code
I dont have that dynamic feature of showing current url page
and throwing active/not on it
without actually watning that and doing it manually
lol
yeah I need to clean up my blades
figured if I could get it to bootstrap 5, and get the UI portion atleast up to current speed
then it would be advantageous to do that
For the blade issue, you absolutely should take a step back, and look at commonalities
yeah
What are you re-using more than half a dozen times
like 90% of my app is common
just little variations being showed
ie showing customer/property/job data
If I want to change the background of 100% of my forms, I need to change 3 blades
gonna have similar widgets, or ui components
but will maybe show different context
yeah id have to change 200-300 blades lmao
The other advantage is, once you componentise everything, you can start to use the $attributes->merge approach
I mean, I contribute to a very popular (free!) Livewire Table Component. What that allows you to do is display a Model (with relations that you incldue) as a table, with filters/search/sorting with a few lines of code.
You can then, just add attributes to different fields/rows dynamically.
Using a similar approach, you'd be able to allow your customers to specify the background colours etc.
If you keep down the route you're on of not using components, you will have oh-so-much pain, when you want to change something
yeah I wish
I'd suggest that you pick two Models (User is normally more complex, so pick an easier target)
Compare the two side by side
Look at where you can extract the code into a blade component, and create the blade components (don't worry about creating a App/View/Components etc, just create the component in resources/views/components)
For example I track
customer Payments, Credits, Refunds, or Write offs
They all share same controller code
bascially duplicated between 4 controllers
The models are near mirror
outside of the 'name'
Ignoring the controller/PHP element of it, and focusing just on your blades, I bet all of those have a lot of the same structure in each blade
Yeah
This is where using a blade component will come into its own, massively
How disgusted by Livewire are you... ha