Check if session variable is defined - laravel

I need to count the number of views. I am using a session variable to avoid duplicate count. I need to check if session view_count isset and then it is set to false and increment the view count
$currentPost = Post::where('slug',$slug)->first();
$comments = \App\Comment::where('post_id',$currentPost->id)->get();
if(\Session::get('view_count')) {
\Session::put('view_count', false);
$currentPost->view_count = $currentPost->view_count + 1;
$currentPost->save();
}

Can't you just do
if (Session::has('your_key'))
{
//your code here
}
Update answer
Determining If An Item Exists In The Session
To determine if an item is present in the session, you may use the has method. The has method returns true if the item is present and is not null:
if ($request->session()->has('users')) {
//
}
To determine if an item is present in the session, even if its value is null, you may use the exists method. The exists method returns true if the item is present:
if ($request->session()->exists('users')) {
//
}

I am assuming you want to check whether a visitor has viewed a certain blog post, in which case, I'd probably do something like this.
$currentPost = Post::where('slug', $slug)->first();
// You should also probably set up your relationship with Comments
$comments = \App\Comment::where('post_id', $currentPost->id)->get();
if(! in_array($currentPost->id, session()->get('posts_viewed', []))) {
session()->push('posts_viewed', $currentPost->id);
// Your increment could also be simplified as follows
$currentPost->increment('view_count');
}
In your particular case, you will only be able to track whether the user has viewed one particular blog post. However, if you use an array and keep pushing viewed blog posts into it, you would be able to track views across many blog posts.

Related

Best Practice - Laravel Controller Eloquent merge

I have a scope on my Supplier model that returns results where active = true.
This works great when creating new entries, as I only want the user to see active suppliers.
Current entries may have an inactive supplier; When I edit it, I want to see all active Suppliers, plus the current supplier (if it is inactive)
I have this code in my controller:
$suppliers = Supplier::active()->get();
if (!$suppliers->contains('id', $record->supplier->id))
{
$suppliers->add(Supplier::find($record->supplier->id));
}
Two questions: Is this the correct way to do this? Should this code be in my controller or should I have it somewhere else? (perhaps a scope but I wouldn't know how to code that).
Edit:
Thanks for the help guys. I have applied advice from each of the answers and refactored my code into a new scope:
public function scopeActiveIncluding($query, Model $model = null)
{
$query->where('active', 1);
if ($model && !$model->supplier->active)
{
$query->orWhere('id', $model->supplier->id);
}
}
What you've written will work, but the Collection::contains function can potentially be pretty slow if the collection is large.
Since you have the id, I would probably make the following change:
$suppliers = Supplier::active()->get();
$supplier = Supplier::find($record->supplier->id);
if (!$supplier->active) {
$suppliers->add($supplier);
}
Of course, the downside to this is that you may be making an unnecessary query on the database.
So you have to consider:
is the record's supplier more likely to be active or inactive?
is the size of the collection of active suppliers large enough to justify another (potentially wasted) call to the database?
Make the choice that makes the most sense, based on what you know of your application's data.
As for the second question, if you will only need this specific set of suppliers in this one part of your application, then the controller is a good place for this code.
If, however, you will need this particular set of suppliers in other parts of your application, you should probably move this code elsewhere. In that case, it might make sense to create a function on the the related model (whatever type $record is...) that returns that model's suppliers set. Something like:
public function getSuppliers()
{
$suppliers = Supplier::active()->get();
$supplier = $this->supplier;
if (!$supplier->active) {
$suppliers->add($supplier);
}
return $suppliers;
}
I saw #Vince's answer about 1st question, and I'm agree with him.
About 2nd question:
Write scope in Supplier model like this:
public function scopeActive($query){
$query->where('active', 1); // for boolean type
}
For good practice, you need to write the logic parts in services like "App\Services\SupplierService.php". And there write the function you want:
public function activeSuppliersWithCurrent($record) {
$suppliers = Supplier::active()->get();
$supplier = Supplier::find($record->supplier->id);
if (!$supplier->active) {
$suppliers->add($supplier);
}
}
In your SupplierController's constructor inject the instance of that service and use the function, for example:
use App\Servives\SupplierService;
protected $supplierService = null;
public function __construct(SupplierService $supplierService) {
$this->supplierService = $supplierService;
}
public function getActiveSuppliersWithCurrent(...) {
$result = $this->supplierService->activeSuppliersWithCurrent($record);
}
As you can see, later you will not need to change anything in controller. If you'll need to change for example the query of suppliers selection, you will just have to change something only in service. This way will make your code blocks separated and shorter.
Also the sense for this pattern: you don't need to access the models from controller. All logic related with models will implemented in services.
For other projects you can grab only services or only controllers, and implement another part differently. But in that case if you had all codes in controller, that will prevent you to grab the portions of necessary codes, cuz may you don't remember what doing each blocks...
You could add a where clause to the query to also find that id.
$suppliers = Supplier::active()->orWhere('id', $record->supplier->id)->get();
You could potentially slide this into the active scope by passing the 'id' as an argument.
public function scopeActive($query, $id = null)
{
$query->where('active', true);
if ($id) {
$query->orWhere('id', $id);
}
}
Supplier::active($record->supplier->id)->get();
Or make another scope that does this.

Eloquent - edit table rows and return new result data - old data retrieved

I have a form where the user can edit, create or delete shipping methods.
The user sends the form and the data is updated.
I want to return the user's shipping methods after they are edited.
But I seem to get the old data back, instead of the updated data.
$user = \App\User::where('user_id', $user->id)->first();
$user->shipping_methods->each(function($method) {
$method->delete();
});
$methods = [];
foreach ($request->input('methods') as $method) {
$methods[] = new \App\ShippingMethod($method);
}
$user->shipping_methods()->saveMany($methods);
return response()->json($user->shipping_methods->toArray());
(at the moment the code just deletes the old shipping methods and replaces them with the new ones). I am using eloquent relations to get the shipping methods.
So when I do:
return response()->json($user->shipping_methods->toArray());
how come I don't get the new results, instead I get the results from before the update? Is it using the results from the first $user->shipping_methods at line 3? Should I "refresh" the query somehow?
You have to reload the relationship:
return response()->json($user->load('shipping_methods')->shipping_methods->toArray());
You can also simplify the whole line:
return $user->load('shipping_methods')->shipping_methods;
The saveMany method of \Illuminate\Database\Eloquent\Relations\HasMany return instace of \Illuminate\Database\Eloquent\Collection and you must manually set relations
$shipping_methods = $user->shipping_methods()->saveMany($methods);
$user->setRelation('shipping_methods', $shipping_methods);
return response()->json($user->shipping_methods->toArray());

Laravel - Filtering previously returned results

I have a page with a form, that lets you select an exercise that I have logged data for. When you submit the form, it uses a getExerciseLog method in my LogController which takes the input name, and gets all instances of that exercise from the database and shows it in a table. The table displays fine, but now I need to filter these by newest and oldest etc, using a select field.
I have created the form for this, which posts to a "filter" method in LogController. So far I have this code:
public function getExerciseLog()
{
$exercises = Exercise::lists('exercise_name', 'exercise_name');
$exercise = Input::get('exercise');
$exercise_logs = Workout::where('exercise_name', '=', $exercise)->orderBy('created_at','desc')->get();
return view('log')->with(array(
'exercise'=>$exercise,
'exercises'=>$exercises,
'exercise_logs'=>$exercise_logs,
));
}
public function filter()
{
$filter = Input::get('filter');
if($filter == "oldest") {
$exercise_logs = Workout::where('exercise_name', '=', $exercise)->orderBy('created_at','asc')->get();
}elseif($filter == "newest") {
$exercise_logs = Workout::where('exercise_name', '=', $exercise)->orderBy('created_at','desc')->get();
}
}
Obviously this is not finished, as I'm stuck on how to return back to the 'log' view with the results filtered. At the moment, in the filter method, the $exercise_log won't return anything as $exercise is not being set, as the exercise input is not being submitted (that is the original select field for choosing the exercise you want to see data for).
Any ideas how I can get this working? Is it possible to pass the $exercise data from the getExerciseLog into the filter log, so I can use it within that method?
Thanks
Recommended way would be to sort the table using Javascript, it avoids the need of another request and full page refresh so it is much faster.
However in your case you can save the $excercise in the session like this
$request->session()->put('excercise', $excercise);
and
$exercise=session('excercise');
More info in here https://laravel.com/docs/5.2/session#basic-usage

Using Active Record, how can I save child's detail information through its parent?

I'm using parent->child (master->detail) relation in Yii2 Active Record
When I want to create a child, I have to manually fill its parent info like this:
Relation: Client (1) ---> (n) Comments
class ClientController extends \yii\web\Controller
{
public function actionAddComment() {
$comment = new Comment;
if ($comment->load(Yii::$app->request->post())) {
$comment->client = $this->id; // Client id
$comment->save();
}
return $this->render('view', ['comment'=>$comment]);
}
}
I've optimized it, creating a Comment method to do that:
class Comment extends ActiveRecord {
public function newComment($client) {
$comment = new Comment;
$comment->client = $client; // Client id
return $comment;
}
}
And I have gone through beforeSave in the Comment model, but still not sure if there is a better way.
Is there anything like:
$comment = new Comment(Yii::$app->request->post());
$client->save($comment); // Here the parent is writing his information to the child
Or one-liner shortcut:
$client->save(new Comment(Yii::$app->request->post());
Without having to create this logic in beforeSave?
Yes, I recommend to use the built in link() and unlink() methods provided by Active Record which you can use in your controller to relate or unrelate 2 models either they share many-to-many or one-to-many relationship.
It even has an optional $extraColumns attribute for additional column values to be saved into a junction table if using it link( $name, $model, $extraColumns = [] )
So your code may look like this :
$comment = new Comment;
if ($comment->load(Yii::$app->request->post())) {
$comment->link('client', $this);
}
check docs for more info.
Now about where to use this code to relate models, it depend on how your app is structured. I'm not sure if doing that through a triggered event would be a good practice, you need to remember that errors may happens and
you may need to evaluate certain scenarios or logic before throwing exceptions. So in my case, I prefer to use that code into my Controllers.
Sometimes you need to build a specific action like you did actionAddComment(), In certain other cases like when your Post request is meant to update the Parent model and also update its related child models at once, the Parent's Update Action ClientController::actionUpdate() may be a good place to do so, maybe something like this will do the job :
$params = Yii::$app->request->post();
$client->load($this->params, '');
if ($client->save() === false && !$client->hasErrors()) {
throw new ServerErrorHttpException('Failed to update the object for unknown reason.');
}
foreach ($params["comments"] as $comment) {
// We may be sure that both models exists before linking them.
// In this case I'm retrieving the child model from db so I don't
// have to validate it while i just need its id from the Post Request
$comment = Comment::findOne($comment['id']);
if (!$comment) throw new ServerErrorHttpException('Failed to update due to unknown related objects.');
// according to its documentation, link() method will throw an exception if unable to link the two models.
$comment->link('client', $client);
...

How can I check if an order with a given increment id already exists in magento?

I am new to Magento.
What's the proper way to check if an order with a given increment id already exists ?
The obvious way:
$order = Mage::getModel('sales/order')->loadByIncrementId($reservedOrderId);
if ($order) {
Mage::log('already have order with id ' . $reservedOrderId);
return $order;
}
does not work, because I get a new and empty model instance back.
What's the correct way in magento to see if I have no such model for that id ?
The most common approach I've seen in core code just load()s a model and checks if there was a primary key assigned. In your case this would look like the following - note the very slight adjustment to the logical condition ($object->getId() vs. $object):
$order = Mage::getModel('sales/order')->loadByIncrementId($reservedOrderId);
if ($order->getId()) {
Mage::log('already have order with id ' . $reservedOrderId);
return $order;
}
It's a simple mistake, but remember that a call to load data on a Magento data model will always return the object instance. It's only if there is a result from the storage backend that the object would be decorated with data and therefore a primary key.
In my experience there are two ways to do this:
if ($order->hasData()) {
// order already exists
}
or, by using a collection;
$collection = Mage::getModel('sales/order')->getCollection()->addFieldToFilter('increment_id', $reservedOrderId);
if ($collection->count()) {
// order already exists
}
In your case, probably best to use the first one.
There's multiple ways to approach this. First, since you know the increment ID to expect, you could check for it after you get your model back
$increment_id = '100000002';
$order = Mage::getModel('sales/order')->loadByIncrementId($increment_id);
if($order->getIncrementId() == $increment_id)
{
var_dump("Increment IDs match, that means there's an order");
}
else
{
var_dump("Increment IDs don't match, that means there's no order");
}
Similarly, although there's a model returned even if there's no match, you could check that model's data — an empty array means nothing was loaded
$increment_id = '100000002';
$order = Mage::getModel('sales/order')->loadByIncrementId($increment_id);
if($order->getData())
{
var_dump("Data array means there's an order");
}
else
{
var_dump("Empty data array means there's no order");
}
Finally, you can load a collection with an increment id filter, and check how many items it contains
$increment_id = '100000002';
$c = Mage::getModel('sales/order')->getCollection()
->addFieldToFilter('increment_id',$increment_id);
if(count($c) > 0)
{
var_dump("A collection with more than zero items means the order exists");
}
else
{
var_dump("An empty collection means it does not");
}
I prefer the last approach for a simple "does/does-not" exists check, as a collection doesn't trigger a model's after load method which means it's theoretically more performant. That said, no approach is more valid than the other — just try to use the same technique everywhere for more readable code.

Resources