I'm converting a Laravel app that was using Backpack across to Laravel Nova.
One of my models Images allows the user to add multiple images with a base set of information from the initial form. The form in this instance asks how many images are in the series via a dropdown and then has a number of relevant fields that will be used for all of the new images being added. When saving, in the controller, I'm using the following eloquent feature to run a number of tasks and insert the required number of rows:
public function store(StoreRequest $request){
//Get some info
//Make some tweaks
//Use for loop to save multiple records
for ($k = 0; $k < $addim; $k++){
//Do some stuff
parent::storeCrud(${"request"});
}
}
This works perfectly and inserts however many records are required.
In Laravel Nova, I can't see a way to use this same approach. Using an event listener in the model doesn't seem like the right way to save multiple records and I can't find any reference to a controller function I can use to achieve this.
I would really appreciate some thoughts and guidance on the best way to complete this part.
If someone has stumbled upon this type of problem.
You can use Laravel Observers.
In order to restrict the event to be fired only when resource is created and only using nova you can declare Observer in NovaServiceProvider.php as follows
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
parent::boot();
Nova::serving(function () {
Image::observe(ImageObserver::class);
});
}
Above code will only be triggered when your image object is modified using nova system only.
For ref Observer code will look like following
<?php
namespace App\Observers;
use App\Models\Document;
class ImageObserver
{
/**
* Handle the Image "created" event.
*
* #param \App\Models\Image $image
* #return void
*/
public function created(Image $image)
{
//logic to create multiple records
}
}
You can use the saving event in an observable
https://laravel.com/docs/5.8/eloquent#events
Related
I'm integrating APIs that will be connected to an E-Commerce platform. I'm supposed to add an option to track orders, how do I do that?
Also, there's no frontend yet so I'm using postman to test all the API's.
the orders go through different locations on their way to the buyer, and I'd like the customer to now at which station their item is
php artisan make:observer OrderObserver
then something in the observer like this:
/**
* Handles the correct fields when updating
* #param Order $order
* #return void
*/
public function updating(User $user)
{
if($order->isDirty('some_column_you_use_to_change_status')) {
$someStatusTable = SomeStatusTable::where('track_id', $order->track_id)->first();
$someStatusTable->status = "whateverYOUwant";
}
}
I really don't know your logic but i think this could be something.
by the wat ->isDirty() watches if columns were changed.
you can also get the $order->getOriginal(('some_column_you_use_to_change_status')
I use this for example history of a Model to register history or even set new versions in my models
I'm building a Laravel app, which has a number of various features. I want to be able to enable or disable them depending on a particular domain's requirement. Currently, I have in my config a series of flags such as:
'is_feature_1_enabled' => true,
'is_feature_2_enabled' => false,
... and so on.
Then in my controllers and views, I check those config values to see whether or not I should be displaying something, allowing certain actions, etc. My app is starting to get polluted with these kinds of checks everywhere.
Is there a best practice method of managing features in a Laravel app?
This is technically called feature flags - https://martinfowler.com/articles/feature-toggles.html
depends on your requirements, flags in config/database, rollout, etc...
But it's basically if's in code and cannot be clean.
Laravel packages:
https://github.com/alfred-nutile-inc/laravel-feature-flag
https://github.com/francescomalatesta/laravel-feature
Some services:
https://launchdarkly.com/
https://bullet-train.io/
https://configcat.com/
Also look at https://marketingplatform.google.com/about/optimize/ for frontend.
I've encountered the same problem when I tried to implement multiple hotel providers.
What I did was using service container.
first you will create class for each domain With his features:
like Doman1.php ,Domain2.php
then inside each one of those you will add your logic.
then you gonna use binding in your app service provider to bind the domain with class to use.
$this->app->bind('Domain1',function (){
return new Domain1();
});
$this->app->bind('Domain2',function (){
return new Domain2();
});
Note you can use general class that holds the features goes with all domains then use that general class in your classes
Finally in your controller you can check your domain then to use the class you gonna use
app(url('/'))->methodName();
Look like you are hard coding things based on config values to enable or disable certain features. I recommend you to control things based on named routes rather than config value.
Group all the route as a whole or by feature wise.
Define name for all routes
Control the enable/disable activity by route name and record in database
Use Laravel middleware to check whether a particular feature is enabled or disabled by getting the current route name from request object and matching it with the database..
so you will not have the same conditions repeating every where and bloat your code..
here is a sample code show you how to retrieve all routes, and you can match the route group name to further process to match your situation.
Route::get('routes', function() {
$routeCollection = Route::getRoutes();
echo "<table >";
echo "<tr>";
echo "<td width='10%'><h4>HTTP Method</h4></td>";
echo "<td width='10%'><h4>Route</h4></td>";
echo "<td width='80%'><h4>Corresponding Action</h4></td>";
echo "</tr>";
foreach ($routeCollection as $value) {
echo "<tr>";
echo "<td>" . $value->getMethods()[0] . "</td>";
echo "<td>" . $value->getPath() . "</td>";
echo "<td>" . $value->getName() . "</td>";
echo "</tr>";
}
echo "</table>";
});
and here is a sample middleware handler where you can check whether a particular feature is active by matching with what you have already stored in your database..
public function handle($request, Closure $next)
{
if(Helper::isDisabled($request->route()->getName())){
abort(403,'This feature is disabled.');
}
return $next($request);
}
Assuming that those features are only needed for HTTP requests.
I would create a default Features base class with all the default flags:
Class Features {
// Defaults
protected $feature1_enabled = true;
protected $feature2_enabled = true;
public function isFeature1Enabled(): bool
{
return $this->feature1_enabled;
}
public function isFeature2Enabled(): bool
{
return $this->feature2_enabled;
}
}
Then I would extend that class for each Domain and set the overrides that are needed for that domain:
Class Domain1 extends Features {
// override
protected $feature1_enabled = false;
}
Then create a Middleware to bind the Features Class to the container:
class AssignFeatureByDomain
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
switch ($_SERVER['HTTP_HOST']) {
case 'domain1':
app()->bind(Features::class, Domain1::class);
break;
default:
abort(401, 'Domain rejected');
}
return $next($request);
}
}
Don't forget to attach this middleware to your routes: to a group or for each route.
After this you can TypeHint your Features class in your controllers:
public function index(Request $request, Features $features)
{
if ($features->isFeature1Enabled()) {
//
}
}
Laravel is great with this, you can even store your features in db, and create a relation between the domain.
I would recommend to use Gates and Policies, which will give you better control in your controllers and blade templates. This means you register the gates from your db or hard code them.
For example if you have export products feature with a button in your system and you want to make that feature available to some users you can register gates with business logic.
//Only admins can export products
Gate::define('export-products', function ($user) {
return $user->isAdmin;
});
Then you can do the following in the controllers
<?php
namespace App\Http\Controllers;
use App\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ProductsController extends Controller
{
/**
* Export products
*
* #param Request $request
* #param Post $post
* #return Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function export(Request $request)
{
$this->authorize('export-products');
// The current user can export products
}
}
Here is an example for your blade templates:
#can('export-products', $post)
<!-- The Current User Can export products -->
#endcan
#cannot('export-products')
<!-- The Current User Can't export products -->
#endcannot
more information available at https://laravel.com/docs/5.8/authorization
Interesting case you have here. It might be interesting to look into a Feature interface or abstract class that contains a few methods you generally need.
interface Feature
{
public function isEnabled(): bool;
public function render(): string;
// Not entirely sure if this would be a best practice but the idea is to be
// able to call $feature->execute(...) on any feature.
public function execute(...);
...
}
You could even devide these into ExecutableFeature and RenderableFeature.
Further on some kind of factory class could be made to make life easier.
// Call class factory.
Feature::make('some_feature')->render();
...->isEnabled();
// Make helper method.
feature('some_feature')->render();
// Make a blade directives.
#feature('some_feature')
#featureEnabled('some_feature')
What I did in my case was creating a new table on the database, you could call it Domains for instance.
Add all the specific features, those which could be shown on some domains but not in the rest, as columns for that table as bit for boolean values. Like, in my case, allow_multiple_bookings, use_company_card... whatever.
Then, consider creating a class Domain and its respective repository, and just ask these values on your code, trying to push as much as possible that logic into your domain (your model, application services, etc).
For instance, I would not do the check on the controller method for RequestBooking if the domain which is requesting a booking can request only one or more.
Instead I do it on a RequestBookingValidatorService which can check if the booking datetime has passed, the user has an enabled credit card, ... or the Domain which this action comes from is allowed to request more than one booking (and then if it already has any).
This adds the convenience of readability, as you have pushed this decision to your application services. Also, I find that whenever I need a new feature I can use Laravel (or Symfony) migrations to add that feature on the table and I could even update its rows (your domains) with the values I want on the same commit I coded.
I want to send some variable in every views which contains data from database. I have written the following code in base controller because it is extended by all of the controller:
public function __construct()
{
$opening_hours = OpeningHours::first();
$social_media = SocialMedia::first();
$website = Website::first();
view()->share('opening_hours', $opening_hours)
->share('social_media', $social_media)
->share('website', $website);
}
Also I have also called parent::__construct(); in all of my controllers. But, I am still getting undefined variable $opening_hours in view file when I try to debug it. How can I send website data (website logo, contact, email) that has to be included in every views file?
Laravel provides us some features like this. You can try View Composers. These are very useful if we want some data on every screen. But we want to place this on separate place instead of writing code in every controller.
https://laravel.com/docs/master/views#view-composers
That will help us.
You can try this way
Create a one middleware and add this code into middleware and use middle where you want this data and data will be available on that view.
$opening_hours = OpeningHours::first();
$social_media = SocialMedia::first();
$website = Website::first();
view()->share('opening_hours', $opening_hours)
->share('social_media', $social_media)
->share('website', $website);
You are a file called AppServiceProvider.php inside of app/Providers folder, In there you can do the following:
<?php
namespace App\Providers;
use View;
use App\OpeningHours;
use App\SocialMedia;
use App\Website;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
public function boot()
{
$contact_details = [
'opening_hours' => OpeningHours::first(),
'social_media' = SocialMedia::first(),
'website' => Website::first(),
];
View::share('contact_details', $contact_details);
}
}
Updated and added a guess to the namespace of the models being used.
So I've run into this issue a few times and now I've decided that I want to find a better solution.
For examples sake, I have two models, Order & Product. There is a many to many relation so that an order can have multiple products and a product can of course have multiple orders. Table structure looks like the below -
orders
id
more fields...
products
id
more fields...
product_orders
order_id
product_id
So when an order is created I run the following -
$order = Order::create($request->validated())
$order->products()->attach([1,2,3,4...]);
So this creates an order and attaches the relevant products to it.
However, I want to use an observer, to determine when the order is created and send out and perform related tasks off the back (send an order confirmation email, etc.) The problem being, at the time the order created observer is triggered, the products aren't yet attached.
Is there any way to do the above, establishing all the many to many relationships and creating the order at the same time so I can access linked products within the Order created observer?
Use case 1
AJAX call hits PUT /api/order which in turn calls Order::place() method. Once an order is created, an email is sent to the customer who placed the order. Now I could just put an event dispatch within this method that in turn triggers the email send but this just feels a bit hacky.
public static function place (SubmitOrderRequest $request)
{
$order = Order::create($request->validated());
$order->products()->attach($request->input('products'));
return $order;
}
Use case 2
I'm feature testing to make sure that an email is sent when an order is created. Now, this test passes (and email sends work), but it's unable to output the linked products at this point in execution.
/**
* #test
**/
public function an_email_is_sent_on_order_creation()
{
Mail::fake();
factory(Order::class)->create();
Mail::assertSent(OrderCreatedMailable::class);
}
Thanks,
Chris.
I think the solution to your problem could be transaction events as provided by this package from fntneves.
Personally, I stumbled upon the idea of transactional events for another reason. I had the issue that my business logic required the execution of some queued jobs after a specific entity had been created. Because my entities got created in batches within a transaction, it was possible that an event was fired (and the corresponding event listener was queued), although the transaction was rolled back because of an error shortly after. The result were queued listeners that always failed.
Your scenario seems comparable to me as you don't want to execute your event listeners immediately due to missing data which is only attached after the model was actually created. For this reason, I suggest wrapping your order creation and all other tasks that manipulate the order within a transaction. Combined with the usage of said package, you can then fire the model created event as the actual event listener will only be called after the transaction has been committed. The code for all this basically comes down to what you already described:
DB::transaction(function() {
$order = Order::create($request->validated());
$order->products()->attach($request->input('products'));
});
In your model, you'd simply define an OrderCreated event or use an observer as suggested in the other answer:
class Order
{
protected $dispatchesEvents = [
'created' => OrderCreated::class,
];
}
class OrderCreated implements TransactionalEvent
{
public $order;
/**
* Create a new event instance.
*
* #param \App\Order $order
* #return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
You can redefine boot method in your model, if product ids is static
class Order extends Eloquent {
protected static function boot() {
parent::boot();
static::saving(function ($user) {
$this->products()->attach([1,2,3,4...]);
});
}
}
Or use observers
class OrderObserver
{
public function created($model)
{
//
}
}
And register this
class EventServiceProvider extends ServiceProvider
{
public function boot(DispatcherContract $events)
{
parent::boot($events);
Order::observe(new OrderObserver());
}
}
Where and how I am overriding the save method in Joomla 3.0 custom component ?
Current situation:
Custom administrator component.
I have a list view that displays all people stored in table.
Clicking on one entry I get to the detailed view where a form is loaded and it's fields can be edited.
On save, the values are stored in the database. This all works fine.However, ....
When hitting save I wish to modify a field before storing it into the database. How do I override the save function and where? I have been searching this forum and googled quiet a bit to find ways to implement this. Anyone who give me a simple example or point me into the right direction ?
Thanks.
Just adding this for anyone who wants to know the answer to the question itself - this works if you explicitly wish to override the save function. However, look at the actual solution of how to manipulate values!
You override it in the controller, like this:
/**
* save a record (and redirect to main page)
* #return void
*/
function save()
{
$model = $this->getModel('hello');
if ($model->store()) {
$msg = JText::_( 'Greeting Saved!' );
} else {
$msg = JText::_( 'Error Saving Greeting' );
}
// Check the table in so it can be edited.... we are done with it anyway
$link = 'index.php?option=com_hello';
$this->setRedirect($link, $msg);
}
More details here: Joomla Docs - Adding Backend Actions
The prepareTable in the model (as mentioned above) is intended for that (prepare and sanitise the table prior to saving). In case you want to us the ID, though, you should consider using the postSaveHook in the controller:
protected function postSaveHook($model, $validData) {
$item = $model->getItem();
$itemid = $item->get('id');
}
The postSaveHook is called after save is done, thus allowing for newly inserted ID's to be used.
You can use the prepareTable function in the model file (administrator/components/yourComponent/models/yourComponent.php)
protected function prepareTable($table)
{
$table->fieldname = newvalue;
}