Laravel automatic resolution with parameters - laravel

I have a class like this:
class PostValidator
{
public function __construct(Validator $validator, $data)
{
$this->validator = $validator;
$this->data = $data;
}
}
I read Laravel doc about IoC automatic resolution, it gives an example:
class FooBar {
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
}
$fooBar = App::make('FooBar');
Is it possible to use App::make only without App::bind (with closure) to instantiate my class above which also passing parameter $data?

No, you can't do that.
The idea is that you pass only the dependencies to the constructor, and obviously data is not one. Validator works with the data, but does not depend on the data.
Instead use setter for the data.
class PostValidator
{
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function setData($data)
{
$this->data = $data;
}
}
and simply call it explicitly:
$validator = App::make('PostValidator');
$validator->setData($data);
// or in the controller, which is what you're doing most likely
public function __construct(PostValidator $validator)
{
$this->validaotr = $validator;
}
public function update($id)
{
$data = Input::only([ input that you need ]);
$this->validator->setData($data);
// run the validation
...
}
edit: as per comment, this is what 2nd argument $parameters does:
// Foo class with Eloquent Models as dependencies
public function __construct(User $user, Category $category, Post $post)
{
$this->user = $user;
$this->category = $category;
$this->post = $post;
}
then IoC container will resolve the dependencies as newly instantiated models:
$foo = App::make('Foo');
$foo->user; // exists = false
$foo->category; // exists = false
$foo->post; // exists = false
but you can do this if you want:
$user = User::first();
$cat = Category::find($someId);
$foo = App::make('Foo', ['category' => $cat, 'user' => $user]);
$foo->user; // exists = true, instance you provided
$foo->category; // exists = true, instance you provided
$foo->post; // exists = false, newly instantiated like before

Related

laravel update() is not working on some models

I am trying to update the database record but Laravel update() function is not working. I have fillable array in the model. but still, it is not working.
The Property Model:
class Property extends Model
{
use HasFactory;
protected $table = 'properties';
protected $primaryKey = 'proID';
public $timestamps = false;
protected $fillable = [ 'proID', 'ProStatus', 'ProPurpose', 'ProType', 'SubType', 'basePrice', 'unitPrice', 'Width', 'Length', 'LandArea','PropertyNumber', 'water', 'electricity', 'gas', 'severage', 'fk_Street', 'createdBy', 'delete'];
public function streets(){
return $this->belongsTo(Street::class,'fk_Street');
}
public function hasInstallments(){
return $this->hasMany(Installments::class,'proID');
}
The PropertyController:
public function destroy($id)
{
$property = Property::find($id);
$property->delete = true;
if($property->save()){
return response()->json(['success'=>true]);
}
}
the $property->update() always returns true but record does not update in database.
The method update() is for mass update wich require an array of attributes and bypass mutators.
public function destroy($id)
{
$property = Property::find($id);
$property->update(['delete' => 1]);
}
You might want to use save() instead
public function destroy($id)
{
$property = Property::find($id);
$property->delete = 1;
$property->save();
}
Both will update the record, you'll need to implement your method's return logic on top of this code but as for updating the record, I think you get the idea.
Your property table primary key is "proID"
public function destroy($id)
{
$property = Property::where('proID', $id)->first();
if($property->update(['delete' => 1])) {
return response()->json(['success' => true]);
}
}

Laravel Notification asserting the data passed down to mail markdown view

I am working on a Laravel project. I am writing integration/ feature tests for my application. I am now writing a test where I need to assert the data passed to the email notification and the data passed to its view. I found this link to do it, https://medium.com/#vivekdhumal/how-to-test-mail-notifications-in-laravel-345528917494.
This is my notification class
class NotifyAdminForHelpCenterCreated extends Notification
{
use Queueable;
private $helpCenter;
public function __construct(HelpCenter $helpCenter)
{
$this->helpCenter = $helpCenter;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new MailMessage())
->subject("Help Center registration")
->markdown('mail.admin.helpcenter.created-admin', [
'helpCenter' => $this->helpCenter,
'user' => $notifiable
]);
}
}
As you can see in the code, I am passing data to mail.admin.helpcenter.created-admin blade view.
This is my test method.
/** #test */
public function myTest()
{
$body = $this->requestBody();
$this->actingAsSuperAdmin()
->post(route('admin.help-center.store'), $body)
->assertRedirect();
$admin = User::where('email', $body['admin_email'])->first();
$helpCenter = HelpCenter::first();
Notification::assertSentTo(
$admin,
NotifyAdminForHelpCenterCreated::class,
function ($notification, $channels) use ($admin, $helpCenter) {
$mailData = $notification->toMail($admin)->toArray();
//here I can do some assertions with the $mailData
return true;
}
);
}
As you can see my comment in the test, I can do some assertions with the $mailData variable. But that does not include the data passed to the view. How can I assert or get the data or variables passed to the blade view/ template?
As you can see here, there is a viewData property on the MailMessage class which contains all the data passed to the view, no need to turn the notification into an array.
$notification->toMail($admin)->viewData
So it would be something like this in your case:
/** #test */
public function myTest()
{
$body = $this->requestBody();
$this->actingAsSuperAdmin()
->post(route('admin.help-center.store'), $body)
->assertRedirect();
$admin = User::where('email', $body['admin_email'])->first();
$helpCenter = HelpCenter::first();
Notification::assertSentTo(
$admin,
NotifyAdminForHelpCenterCreated::class,
function ($notification, $channels) use ($admin, $helpCenter) {
$viewData = $notification->toMail($admin)->viewData;
return $admin->is($viewData['user']) && $helpCenter->is($viewData['helpCenter']);
}
);
}

Laravel json response returns encrypted data

I'm using an Encryptable trait to encrypt my data for the a Room model.
RoomController (/rooms) returns the decrypted data but ApiRoomController (/api/rooms) does not.
How could I make it returns the decrypted data?
Encryptable Trait
trait Encryptable
{
public function getAttribute($key)
{
$value = parent::getAttribute($key);
if (in_array($key, $this->encryptable) && $value !== '' && $value !== null ) {
$value = Crypt::decrypt($value);
}
return $value;
}
public function setAttribute($key, $value)
{
if (in_array($key, $this->encryptable)) {
$value = Crypt::encrypt($value);
}
return parent::setAttribute($key, $value);
}
}
RoomController index function
public function index()
{
$rooms = Room::select('id', 'name')->get()->sortBy('name')->values()->all();
return view('rooms.index')->withRooms($rooms);
}
ApiRoomController index function
public function index()
{
$rooms = Room::select('id', 'name')->get()->sortBy('name')->values()->all();
return response()->json($rooms);
}
I found a way using API Resources:
php artisan make:resource Rooms --collection
Then in your app/Http/Resources/Rooms.php file:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
// more fields here
];
}
Then in your ApiRoomController.php file:
use App\Http\Resources\Rooms;
public function index()
{
$rooms = Room::select('id', 'name')->get()->sortBy('name')->values()->all();
return Rooms::collection($rooms);
}
Seems like #emotality came up with a good solution for this already...
However, the reason for this not working as you expected is because the underlying Model's toArray() / toJson() methods do not call the getAttribute() method in your trait.
This is important because the response()->json() method maps the given collection and calls the toJson() method on each model in order to prepare it for a response.
Therefore, you can also solve this by overwriting the toArray method in your model.
class Room extends Model
{
use Encryptable;
public function toArray()
{
return [
'id' => $this->id,
'name' => $this->name,
// ...
];
}
}

How load() works in EAV model in Magento 1.9.2?

$product = Mage::getModel('catalog/product')->load($this->getProductId());
load() will go Mage_Core_Model_Abstract inside load() function,
public function load($id, $field=null)
{
$this->_beforeLoad($id, $field);
$this->_getResource()->load($this, $id, $field);
$this->_afterLoad();
$this->setOrigData();
$this->_hasDataChanges = false;
return $this;
}
Now this should go to abstract class Mage_Catalog_Model_Resource_Abstract extends Mage_Eav_Model_Entity_Abstract where load process is defined:
public function load($object, $entityId, $attributes = array())
{
Varien_Profiler::start('__EAV_LOAD_MODEL__');
/**
* Load object base row data
*/
$select = $this->_getLoadRowSelect($object, $entityId);
$row = $this->_getReadAdapter()->fetchRow($select);
...
if (empty($attributes)) {
$this->loadAllAttributes($object);
} else {
foreach ($attributes as $attrCode) {
$this->getAttribute($attrCode);
}
}
$this->_loadModelAttributes($object);
...
}
But I can't understand how
$this->_getResource()->load($this, $id, $field); in Mage_Core_Model_Abstract
connects to abstract class Mage_Catalog_Model_Resource_Abstract???
Because load($this, $id, $field) is going to abstract class Mage_Core_Model_Resource_Db_Abstract extends Mage_Core_Model_Resource_Abstract :
public function load(Mage_Core_Model_Abstract $object, $value, $field = null)
{
if (is_null($field)) {
$field = $this->getIdFieldName();
}
$read = $this->_getReadAdapter();
if ($read && !is_null($value)) {
$select = $this->_getLoadSelect($field, $value, $object);
$data = $read->fetchRow($select);
if ($data) {
$object->setData($data);
}
}
$this->unserializeFields($object);
Please help me out.
There is the _construct method in the product model (catalog/product). This method is triggered when one creates an object. It shows which resource model to use:
$this->_init('catalog/product');
Next, in the Mage_Core_Model_Abstract in the load method, there is an expression $this->_getResource(). It can bring back the object of the class
Mage_Catalog_Model_Resource_Product (which, in turn, is inherited from Mage_Catalog_Model_Resource_Abstract).
Basically, every model can have its own resource model with its own class.

Routing zend request through a default controller when controller not found

Below is a function defined in my Bootstrap class. I must be missing something fundamental in the way Zend does routing and dispatching. What I am trying to accomplish is simple: For any request /foo/bar/* that is not dispatchable for any reason try /index/foo/bar/. The problem I'm having is when the FooController exists I get Action "foo" does not exist. Basically, the isDispatchable is always false.
public function run() {
$front = Zend_Controller_Front::getInstance();
$request = $front->getRequest();
$dispatcher = $front->getDispatcher();
//$controller = $dispatcher->getControllerClass($request);
if (!$dispatcher->isDispatchable($request)) {
$route = new Zend_Controller_Router_Route(
':action/*',
array('controller' => 'index')
);
$router = $front->getRouter();
$router->addRoute('FallBack', $route);
}
$front->dispatch();
}
So this seems to work, but is not the best answer as it simply drops all the params. I might try shortly doing a forward to /index/[original uri] within the plugin:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
protected function _initRoute() {
$front = Zend_Controller_Front::getInstance();
$routes = array(
'FallBack' => new Zend_Controller_Router_Route(
':controller/:action/*',
array('controller' => 'index', 'action' => 'index')
)
);
$router = $front->getRouter();
$router->removeDefaultRoutes();
$router->addRoutes($routes);
$front->setRouter($router);
return $router;
}
protected function _initPlugin() {
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new My_Controller_Plugin_FallBack());
}
}
class My_Controller_Plugin_FallBack extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
$front = Zend_Controller_Front::getInstance();
$dispatcher = $front->getDispatcher();
$router = $front->getRouter();
if (($router->getCurrentRouteName() == 'FallBack') &&
!$dispatcher->isDispatchable($request)) {
$request->setActionName($request->getControllerName());
$request->setControllerName('index');
}
}
}
if i understand your idea right
would you try to use __call magic method ??
then use $this->_redirect(); to your default action for example
more info are here http://php.net/manual/en/language.oop5.overloading.php
UPDATE
if you opened Zend/Controller/Action.php on line 480
public function __call($methodName, $args)
{
require_once 'Zend/Controller/Action/Exception.php';
if ('Action' == substr($methodName, -6)) {
$action = substr($methodName, 0, strlen($methodName) - 6);
throw new Zend_Controller_Action_Exception(sprintf('Action "%s" does not exist and was not trapped in __call()', $action), 404);
}
throw new Zend_Controller_Action_Exception(sprintf('Method "%s" does not exist and was not trapped in __call()', $methodName), 500);
}
what i meant to do is to extend this class and override __call function exactly to be
classs My_Controller_Action extends Zend_Controller_Action{
public function __call($methodName, $args)
{
///// do your magic here ......redirection or logging the request or what ever
}
}
and make sure your controller extend your newly created class
class FooController extends My_Controller_Action
{
public function indexAction()
{
// action body
}
}
so if some how you called inexistent action __call will run
this idea was about inexistent action only
it won't work if the controller doesn't exist

Resources