The default model "User" in laravel throws error when I tried to use it. What I tried is,
$user = new User();
$user->email = Input::get('email');
$user->password = Hash::make(Input::get('password'));
$user->name = "Blah Blah";
$user->access_type = "admin";
$user->access_status = 1;
$user->save();
and the error thrown is
Symfony \ Component \ Debug \ Exception \ FatalErrorException
Call to undefined method User::save()
What is the issue? I also tried User::all() to retrieve the values, which also throws error Call to undefined method User::all().
Update1:
Here is my model
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken()
{
return $this->remember_token;
}
/**
* Set the token value for the "remember me" session.
*
* #param string $value
* #return void
*/
public function setRememberToken($value)
{
$this->remember_token = $value;
}
/**
* Get the column name for the "remember me" token.
*
* #return string
*/
public function getRememberTokenName()
{
return 'remember_token';
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
Update2:
I tried writing another model names and class name as Users, which works perfectly. But for authentication, it must be the User table right?
The reason for the issue is autoloader load wrong "User" model. Please have a look at the "/vendor/composer/autoload_classmap.php" file. Inside the return array value for the key "User" must be $baseDir . '/app/models/User.php
return array(
...,
'User' => $baseDir . '/app/models/User.php',
...,
);
Maybe User is a reserved word, just give the model another name say UserTbl
Still didn't received any answer to my question. Anyways now I'm running it using the new model named Users with the same doubt in mind.
Related
I am new to OOP/MVC and I do have a basic understanding of a controller which interacts with an underlying model. Basically, a controller acts as a "CRUD gateway" to a model. However, consider an e-commerce marketplace object: Order.
An e-commerce order in a marketplace would interact with multiple tables and hence an order can be thought of as a join of multiple tables: orders, order_items, order_sellers, order_buyer (and more perhaps).
If I understand it correctly, each one of these tables would have a controller allowing CRUD operations (OrderInfoController, OrderItemController,OrderSellerController,OrderBuyerController etc.).
However, could I also create a controller for 'Orders' which then instantiates the Controller Object for each of the tables involved in an Order?
OrderController {
$this->orderInfo = OrderInfo Object;
$this->orderItems = array of Order Item Objects;
$this->orderSellers = array of Order Seller Objects;
$this->orderBuyer = OrderBuyer Object;
function create($arr_order)
//create the order object by calling each of the member controllers.
function get($orderId)
//get the complete order by order Id....
function update($orderId)
function delete($orderId)
}
I have gone through a few MVC docs but I have not come across a solution to this problem. My question is then: Is this the correct approach to write a controller which interacts with multiple tables?
To
If I understand it correctly, each one of these tables would have a controller allowing CRUD operations [...].
In a web MVC-based application, each request is, indeed, served by a controller (the "C" in "MVC").
Though, the controller delegates the whole processing of the request to one or more application services (e.g. use cases, e.g actions - see resources list below), as part of the service layer. These services interact with the model (the "M" in "MVC"), e.g. domain model, e.g. model layer, which, in turn, interact with the database.
The final result of the processing of the request data, e.g. the response object, is either returned to the controller, in order to be passed and printed on screen by the view (the "V" in "MVC"), or directly to the view, for the same reason.
After watching both videos in the resources list below, you will understand, that the model doesn't need to know anything about the database. So, the components of the model layer (mostly interfaces) should not know where and how the data passed to them by the services is saved. Therefore, the services and the controllers should also know nothing about the database.
All informations regarding the database should be located in data mappers only - as part of the infrastructure layer. These objects should be the only ones understanding the database API. Therefore, the only ones containing and beeing able to execute SQL statements.
To
Is this the correct approach to write a controller which interacts with multiple tables?
No. But it's not a problem. Just keep learning about MVC.
Resources:
Keynote: Architecture the Lost Years by Robert Martin.
Sandro Mancuso : Crafted Design
Here is some code of mine. At first sight, it's maybe a lot of it, but I'm confident, that it will help you to better understand.
For simplicity, follow the definition of the method getAllUsers in the view class SampleMvc\App\View\Template\Users\Users.
First of all, here is a not so important note (yet): In my code, the controller only updates the model layer, and the view only fetches data from the model layer. Only the response returned by the view is, therefore, printed. The controller and the view are called by a class RouteInvoker, like this:
<?php
namespace MyPackages\Framework\Routing;
//...
class RouteInvoker implements RouteInvokerInterface {
//...
public function invoke(RouteInterface $route): ResponseInterface {
$controller = $this->resolveController($route);
$view = $this->resolveView($route);
$parameters = $route->getParameters();
$this->callableInvoker->call($controller, $parameters);
return $this->callableInvoker->call($view, $parameters);
}
//...
}
The result ($response) of RouteInvoker:invoke is printed like this:
$responseEmitter->emit($response);
And from here follows an example of a code invoked by RouteInvoker:invoke:
A controller to handle the users:
<?php
namespace SampleMvc\App\Controller\Users;
use function sprintf;
use SampleMvc\App\Service\Users\{
Users as UserService,
Exception\UserExists,
};
use Psr\Http\Message\ServerRequestInterface;
/**
* A controller to handle the users.
*/
class Users {
/**
*
* #param UserService $userService A service to handle the users.
*/
public function __construct(
private UserService $userService
) {
}
/**
* Add a user.
*
* #param ServerRequestInterface $request A server request.
* #return void
*/
public function addUser(ServerRequestInterface $request): void {
$username = $request->getParsedBody()['username'];
try {
$this->userService->addUser($username);
} catch (UserExists $exception) {
//...
}
}
/**
* Remove all users.
*
* #return void
*/
public function removeAllUsers(): void {
$this->userService->removeAllUsers();
}
}
A view to handle the users:
Notice, that controller and view share the same UserService instance.
<?php
namespace SampleMvc\App\View\Template\Users;
use SampleMvc\App\{
View\Layout\Primary,
Service\Users\Users as UserService,
Components\Service\MainNavigation,
};
use Psr\Http\Message\{
ResponseInterface,
ResponseFactoryInterface,
};
use AlePackages\Template\Renderer\TemplateRendererInterface;
/**
* A view to handle the users.
*/
class Users extends Primary {
/**
*
* #param UserService $userService A service to handle the users.
*/
public function __construct(
ResponseFactoryInterface $responseFactory,
TemplateRendererInterface $templateRenderer,
MainNavigation $mainNavigationService,
private UserService $userService
) {
parent::__construct($responseFactory, $templateRenderer, $mainNavigationService);
}
/**
* Display the list of users.
*
* #return ResponseInterface The response to the current request.
*/
public function default(): ResponseInterface {
$bodyContent = $this->templateRenderer->render('#Templates/Users/Users.html.twig', [
'activeNavItem' => 'Users',
'users' => $this->getAllUsers(),
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($bodyContent);
return $response;
}
/**
* Add a user.
*
* #return ResponseInterface The response to the current request.
*/
public function addUser(): ResponseInterface {
$bodyContent = $this->templateRenderer->render('#Templates/Users/Users.html.twig', [
'activeNavItem' => 'Users',
'message' => 'User successfully added',
'users' => $this->getAllUsers(),
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($bodyContent);
return $response;
}
/**
* Remove all users.
*
* #return ResponseInterface The response to the current request.
*/
public function removeAllUsers(): ResponseInterface {
$bodyContent = $this->templateRenderer->render('#Templates/Users/Users.html.twig', [
'activeNavItem' => 'Users',
'message' => 'All users successfully removed',
'users' => $this->getAllUsers(),
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($bodyContent);
return $response;
}
/**
* Get a list of users.
*
* #return (string|int)[][] The list of users.
*/
private function getAllUsers(): array {
$users = $this->userService->findAllUsers();
$usersFormatted = [];
foreach ($users as $user) {
$usersFormatted[] = [
'id' => $user->getId(),
'username' => $user->getUsername(),
];
}
return $usersFormatted;
}
}
A service to handle the users:
<?php
namespace SampleMvc\App\Service\Users;
use SampleMvc\Domain\Model\User\{
User,
UserCollection,
};
use SampleMvc\App\Service\Users\Exception\UserExists;
/**
* A service to handle the users.
*/
class Users {
/**
*
* #param UserCollection $userCollection A collection of users.
*/
public function __construct(
private UserCollection $userCollection
) {
}
/**
* Find a user by id.
*
* #param int $id An id.
* #return User|null The found user or null.
*/
public function findUserById(int $id): ?User {
return $this->userCollection->findById($id);
}
/**
* Find all users.
*
* #return User[] The list of users.
*/
public function findAllUsers(): array {
return $this->userCollection->all();
}
/**
* Add a user.
*
* #param string|null $username A username.
* #return User The added user.
*/
public function addUser(?string $username): User {
$user = $this->createUser($username);
return $this->storeUser($user);
}
/**
* Remove all users.
*
* #return void
*/
public function removeAllUsers(): void {
$this->userCollection->clear();
}
/**
* Create a user.
*
* #param string|null $username A username.
* #return User The user.
*/
private function createUser(?string $username): User {
$user = new User();
$user->setUsername($username);
return $user;
}
/**
* Store a user.
*
* #param User $user A user.
* #return User The stored user.
* #throws UserExists A user already exists.
*/
private function storeUser(User $user): User {
if ($this->userCollection->exists($user)) {
throw new UserExists('Username "' . $user->getUsername() . '" already used');
}
return $this->userCollection->store($user);
}
}
An exception indicating that a user already exists:
<?php
namespace SampleMvc\App\Service\Users\Exception;
/**
* An exception indicating that a user already exists.
*/
class UserExists extends \OverflowException {
}
An interface to a collection of users:
Notice, that this is an interface.
Notice, that this interface is a component of the domain model!
Notice, that its implementation (e.g. SampleMvc\Domain\Infrastructure\Repository\User\UserCollection further down below) is not part of the domain model, but of the infrastructure layer!
<?php
namespace SampleMvc\Domain\Model\User;
use SampleMvc\Domain\Model\User\User;
/**
* An interface to a collection of users.
*/
interface UserCollection {
/**
* Find a user by id.
*
* #param int $id An id.
* #return User|null The found user or null.
*/
public function findById(int $id): ?User;
/**
* Get all users from the collection.
*
* #return User[] All users in the collection.
*/
public function all(): array;
/**
* Store a user.
*
* #param User $user A user.
* #return User The stored user.
*/
public function store(User $user): User;
/**
* Check if a user exists in the collection.
*
* #param User $user A user.
* #return bool True if the user exists, or false otherwise.
*/
public function exists(User $user): bool;
/**
* Remove all users from the collection.
*
* #return static
*/
public function clear(): static;
}
A collection of users:
<?php
namespace SampleMvc\Domain\Infrastructure\Repository\User;
use SampleMvc\Domain\Model\User\{
User,
UserCollection as UserCollectionInterface,
};
use SampleMvc\Domain\Infrastructure\Mapper\User\UserMapper;
/**
* A collection of users.
*/
class UserCollection implements UserCollectionInterface {
/**
*
* #param UserMapper $userMapper A user mapper.
*/
public function __construct(
private UserMapper $userMapper
) {
}
/**
* #inheritDoc
*/
public function findById(int $id): ?User {
return $this->userMapper->fetchUserById($id);
}
/**
* #inheritDoc
*/
public function all(): array {
return $this->userMapper->fetchAllUsers();
}
/**
* #inheritDoc
*/
public function store(User $user): User {
return $this->userMapper->saveUser($user);
}
/**
* #inheritDoc
*/
public function exists(User $user): bool {
return $this->userMapper->userExists($user);
}
/**
* #inheritDoc
*/
public function clear(): static {
$this->userMapper->deleteAllUsers();
return $this;
}
}
An interface to a user mapper:
Notice that this is the interface of a data mapper.
<?php
namespace SampleMvc\Domain\Infrastructure\Mapper\User;
use SampleMvc\Domain\Model\User\User;
/**
* An interface to a user mapper.
*/
interface UserMapper {
/**
* Fetch a user by id.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param int $id A user id.
* #return User|null The user or null.
*/
public function fetchUserById(int $id): ?User;
/**
* Fetch all users.
*
* #return User[] The list of users.
*/
public function fetchAllUsers(): array;
/**
* Save a user.
*
* #param User $user A user.
* #return User The saved user.
*/
public function saveUser(User $user): User;
/**
* Check if a user exists.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param User $user A user.
* #return bool True if the user exists, or false otherwise.
*/
public function userExists(User $user): bool;
/**
* Delete all users.
*
* #return static
*/
public function deleteAllUsers(): static;
}
A PDO user mapper:
Notice, that this component is the implementation of a data mapper.
Notice, that this component is the only one understanding the database API. Therefore, the only one containing and beeing able to execute SQL statements.
Notice, that this component is not part of the domain model, but of the infrastructure layer!
(1) Notice, that you can write any SQL statements that you want, including JOIN statements. So, the fetched data can come from multiple tables as well.
(2) Notice also, that the result of a method of this class could be a list of objects of a type defined by you (!), independent of the underlying table(s) data..
The conclusion from (1) and (2) above: The database structure does NOT affect in any way the way in which your application is structured.
<?php
namespace SampleMvc\Domain\Infrastructure\Mapper\User;
use SampleMvc\Domain\{
Model\User\User,
Infrastructure\Mapper\User\UserMapper,
};
use PDO;
/**
* A PDO user mapper.
*/
class PdoUserMapper implements UserMapper {
/**
*
* #param PDO $connection A database connection.
*/
public function __construct(
private PDO $connection
) {
}
/**
* #inheritDoc
*/
public function fetchUserById(int $id): ?User {
$sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';
$statement = $this->connection->prepare($sql);
$statement->execute([
'id' => $id,
]);
$dataArray = $statement->fetch(PDO::FETCH_ASSOC);
return ($dataArray === false) ? null : $this->convertDataArrayToUser($dataArray);
}
/**
* #inheritDoc
*/
public function fetchAllUsers(): array {
$sql = 'SELECT * FROM users';
$statement = $this->connection->prepare($sql);
$statement->execute();
$listOfDataArrays = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertListOfDataArraysToListOfUsers($listOfDataArrays);
}
/**
* #inheritDoc
*/
public function saveUser(User $user): User {
return $this->insertUser($user);
}
/**
* #inheritDoc
*/
public function userExists(User $user): bool {
$sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$data = $statement->fetch(PDO::FETCH_ASSOC);
return ($data['cnt'] > 0) ? true : false;
}
/**
* #inheritDoc
*/
public function deleteAllUsers(): static {
$sql = 'DELETE FROM users';
$statement = $this->connection->prepare($sql);
$statement->execute();
return $this;
}
/**
* Insert a user.
*
* #param User $user A user.
* #return User The user, with updated id.
*/
private function insertUser(User $user): User {
$sql = 'INSERT INTO users (username) VALUES (:username)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$user->setId($this->connection->lastInsertId());
return $user;
}
/**
* Update a user.
*
* #param User $user A user.
* #return User The user.
*/
private function updateUser(User $user): User {
$sql = 'UPDATE users SET username = :username WHERE id = :id';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
':id' => $user->getId(),
]);
return $user;
}
/**
* Convert the given data array to a user.
*
* #param array $dataArray A data array.
* #return User The user.
*/
private function convertDataArrayToUser(array $dataArray): User {
$user = new User();
$user
->setId($dataArray['id'])
->setUsername($dataArray['username'])
;
return $user;
}
/**
* Convert the given list of data arrays to a list of users.
*
* #param array[] $listOfDataArrays A list of data arrays.
* #return User[] The list of users.
*/
private function convertListOfDataArraysToListOfUsers(array $listOfDataArrays): array {
$listOfUsers = [];
foreach ($listOfDataArrays as $dataArray) {
$listOfUsers[] = $this->convertDataArrayToUser($dataArray);
}
return $listOfUsers;
}
}
As I understand using repositories restricts controller from accessing database layer, and all queries goes through repository. But can controller use model (laravel can inject model instead of ID in a controller) to pass it to repository or service - for example to make a transaction between users? Or better to send IDs to repository, to find users and apply business logic (do user have money, or is he banned).
And more generic question, can you use models outside of the repository, because if you change some tables from postgres or mysql to something else your models will change also. And this means your repository should have get method to send back some DTO object?
Note: This is a general perspective on the matter, appliable to any application based on MVC, not only to Laravel.
An application based on the MVC pattern should be composed of three parts:
delivery mechanism: UI logic (user request handling and server response creation),
service layer: application logic,
domain model: business logic.
Here are some graphical representations (of my own making):
As shown above (and described in detail in the resources below), the controllers and the views are part of the delivery mechanism. They should interact with the domain model only through the service layer objects (services). Consequently, they should have no knowledge of the domain model components (entities - also known as domain objects, data mappers, repositories, etc). More of it, the controllers should have only one responsibility: to pass the values of the user request to the service layer, in order for it to update the model.
So, to answer your first question: No, controllers should not be able to create any instances of elements of the domain model (so instances of what you're calling "models" - in respect of Laravel's Active Record), or even to pass such objects to other components (like repositories, services, etc). Instead, the controllers should just pass the values of the request (the user id, for example) to the corresponding services. These services will then create the proper domain model objects and use the proper repositories, data mappers, etc, in order to save/fetch to/from database.
As for the second question (if I understood it correctly): The repositories are to be seen as collections of entities - which are domain model components. As such, elements (e.g. entity instances) can be fetched, stored, altered, or removed to/from them. So, by definition, the entities must be defined/used separately from the repositories. In regard of Laravel, the same should apply: The "models" should be defined/used separately from the repositories.
A "general" MVC implementation (for more clarity):
Controller:
<?php
namespace MyApp\UI\Web\Controller\Users;
use MyApp\Domain\Service\Users;
use Psr\Http\Message\ServerRequestInterface;
/**
* Add a user.
*/
class AddUser {
/**
* User service.
*
* #var Users
*/
private $userService;
/**
*
* #param Users $userService User service.
*/
public function __construct(Users $userService) {
$this->userService = $userService;
}
/**
* Invoke.
*
* #param ServerRequestInterface $request Request.
* #return void
*/
public function __invoke(ServerRequestInterface $request) {
// Read request values.
$username = $request->getParsedBody()['username'];
// Call the corresponding service.
$this->userService->addUser($username);
}
}
Service:
<?php
namespace MyApp\Domain\Service;
use MyApp\Domain\Model\User\User;
use MyApp\Domain\Model\User\UserCollection;
use MyApp\Domain\Service\Exception\UserExists;
/**
* Service for handling the users.
*/
class Users {
/**
* User collection (a repository).
*
* #var UserCollection
*/
private $userCollection;
/**
*
* #param UserCollection $userCollection User collection.
*/
public function __construct(UserCollection $userCollection) {
$this->userCollection = $userCollection;
}
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findUserById(int $id) {
return $this->userCollection->findUserById($id);
}
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAllUsers() {
return $this->userCollection->findAllUsers();
}
/**
* Add a user.
*
* #param string $username Username.
* #return User User.
*/
public function addUser(string $username) {
$user = $this->createUser($username);
return $this->storeUser($user);
}
/**
* Create a user.
*
* #param string $username Username.
* #return User User.
*/
private function createUser(string $username) {
$user = new User();
$user->setUsername($username);
return $user;
}
/**
* Store a user.
*
* #param User $user User.
* #return User User.
*/
private function storeUser(User $user) {
if ($this->userCollection->userExists($user)) {
throw new UserExists('Username "' . $user->getUsername() . '" already used');
}
return $this->userCollection->storeUser($user);
}
}
Repository:
<?php
namespace MyApp\Domain\Infrastructure\Repository\User;
use MyApp\Domain\Model\User\User;
use MyApp\Domain\Infrastructure\Mapper\User\UserMapper;
use MyApp\Domain\Model\User\UserCollection as UserCollectionInterface;
/**
* User collection.
*/
class UserCollection implements UserCollectionInterface {
/**
* User mapper (a data mapper).
*
* #var UserMapper
*/
private $userMapper;
/**
*
* #param UserMapper $userMapper User mapper.
*/
public function __construct(UserMapper $userMapper) {
$this->userMapper = $userMapper;
}
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findUserById(int $id) {
return $this->userMapper->fetchUserById($id);
}
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAllUsers() {
return $this->userMapper->fetchAllUsers();
}
/**
* Store a user.
*
* #param User $user User.
* #return User User.
*/
public function storeUser(User $user) {
return $this->userMapper->saveUser($user);
}
/**
* Check if the given user exists.
*
* #param User $user User.
* #return bool True if user exists, false otherwise.
*/
public function userExists(User $user) {
return $this->userMapper->userExists($user);
}
}
Entity:
<?php
namespace MyApp\Domain\Model\User;
/**
* User.
*/
class User {
/**
* Id.
*
* #var int
*/
private $id;
/**
* Username.
*
* #var string
*/
private $username;
/**
* Get id.
*
* #return int
*/
public function getId() {
return $this->id;
}
/**
* Set id.
*
* #param int $id Id.
* #return $this
*/
public function setId(int $id) {
$this->id = $id;
return $this;
}
/**
* Get username.
*
* #return string
*/
public function getUsername() {
return $this->username;
}
/**
* Set username.
*
* #param string $username Username.
* #return $this
*/
public function setUsername(string $username) {
$this->username = $username;
return $this;
}
}
Data mapper:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\User;
use PDO;
use MyApp\Domain\Model\User\User;
use MyApp\Domain\Infrastructure\Mapper\User\UserMapper;
/**
* PDO user mapper.
*/
class PdoUserMapper implements UserMapper {
/**
* Database connection.
*
* #var PDO
*/
private $connection;
/**
*
* #param PDO $connection Database connection.
*/
public function __construct(PDO $connection) {
$this->connection = $connection;
}
/**
* Fetch a user by id.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id) {
$sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';
$statement = $this->connection->prepare($sql);
$statement->execute([
'id' => $id,
]);
$data = $statement->fetch(PDO::FETCH_ASSOC);
return ($data === false) ? null : $this->convertDataToUser($data);
}
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers() {
$sql = 'SELECT * FROM users';
$statement = $this->connection->prepare($sql);
$statement->execute();
$data = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertDataToUserList($data);
}
/**
* Check if a user exists.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user) {
$sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$data = $statement->fetch(PDO::FETCH_ASSOC);
return ($data['cnt'] > 0) ? true : false;
}
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user) {
return $this->insertUser($user);
}
/**
* Insert a user.
*
* #param User $user User.
* #return User User.
*/
private function insertUser(User $user) {
$sql = 'INSERT INTO users (username) VALUES (:username)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$user->setId($this->connection->lastInsertId());
return $user;
}
/**
* Update a user.
*
* #param User $user User.
* #return User User.
*/
private function updateUser(User $user) {
$sql = 'UPDATE users SET username = :username WHERE id = :id';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
':id' => $user->getId(),
]);
return $user;
}
/**
* Convert the given data to a user.
*
* #param array $data Data.
* #return User User.
*/
private function convertDataToUser(array $data) {
$user = new User();
$user
->setId($data['id'])
->setUsername($data['username'])
;
return $user;
}
/**
* Convert the given data to a list of users.
*
* #param array $data Data.
* #return User[] User list.
*/
private function convertDataToUserList(array $data) {
$userList = [];
foreach ($data as $item) {
$userList[] = $this->convertDataToUser($item);
}
return $userList;
}
}
View:
<?php
namespace MyApp\UI\Web\View\Users;
use MyApp\UI\Web\View\View;
use MyApp\Domain\Service\Users;
use MyLib\Template\TemplateInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ResponseFactoryInterface;
/**
* Add a user.
*/
class AddUser extends View {
/**
* User service.
*
* #var Users
*/
private $userService;
/**
*
* #param ResponseFactoryInterface $responseFactory Response factory.
* #param TemplateInterface $template Template.
* #param Users $userService User service.
*/
public function __construct(ResponseFactoryInterface $responseFactory, TemplateInterface $template, Users $userService) {
parent::__construct($responseFactory, $template);
$this->userService = $userService;
}
/**
* Display a form for adding a user.
*
* #return ResponseInterface Response.
*/
public function index() {
$body = $this->template->render('#Template/Users/add-user.html.twig', [
'activeMainMenuItem' => 'addUser',
'action' => '',
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($body);
return $response;
}
/**
* Add a user.
*
* #return ResponseInterface Response.
*/
public function addUser() {
$body = $this->template->render('#Template/Users/add-user.html.twig', [
'activeMainMenuItem' => 'addUser',
'message' => 'User successfully added.',
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($body);
return $response;
}
}
Resources:
How should a model be structured in MVC?
Keynote: Architecture the Lost Years
GeeCON 2014: Sandro Mancuso - Crafted Design
This is an opiniated answer but here's my take. What I suggest is to not add a repository layer for the sake of having a repository in Laravel. whatever methods you need, add them to the model classes, When they are bloated/expect it to be bloated then only think about repositories (Most probably you would need a service class or some other abstraction here).
Since all these eloquent model classes can be resolved from container its easy to use them. it's accessible anywhere and even in the controller like you have mentioned can be injected which provides a great level of ease.
And repositories help to change for example the underlying database, But eloquent provides us with that flexibility already. And when you plan to change your database, I don't think its going to be a simple change so why wrap the logic up in another layer of abstraction (unneccessarily).
At least from my experience the repository pattern doesn't suite well with Active Record Pattern. Which Laravel follows. Where repository suites very well for data mapper pattern (for example Symfony uses it). Thats why in laravel documentation you don't see them embracing the repository pattern. Rather in symfony documentation you can see it.
So I suggest to embrace the framework than to fight it
I have a laravel API project. I want to be able to send a login request and get back a token depending on some custom logic. I am not using a database so i cant use the default auth.
I have set up a provider called AuthCustomProvider.
namespace App\Providers;
use Auth;
use App\Authentication\UserProvider;
use Illuminate\Support\ServiceProvider;
class AuthCustomProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
Auth::provider('custom_auth', function($app, array $config) {
return new UserProvider();
});
}
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
//
}
}
I have then added this to the config/app.php file in the providers array:
'providers' => [
App\Providers\AuthCustomProvider::class,
Then i added my custom providers driver to the config/auth.php file in the providers array:
'providers' => [
'users' => [
'driver' => 'custom_auth',
],
],
As im not using a database, I took out the model property
Lastly I created a folder called App/Authentication which i put my UserProvider.php file in which is this:
<?php
namespace App\Authentication;
use Illuminate\Contracts\Auth\UserProvider as IlluminateUserProvider;
class UserProvider implements IlluminateUserProvider
{
/**
* #param mixed $identifier
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
// Get and return a user by their unique identifier
}
/**
* #param mixed $identifier
* #param string $token
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
{
// Get and return a user by their unique identifier and "remember me" token
}
/**
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $token
* #return void
*/
public function updateRememberToken(Authenticatable $user, $token)
{
// Save the given "remember me" token for the given user
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// Get and return a user by looking up the given credentials
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
// Check that given credentials belong to the given user
}
}
So lastly i make a function on the login controller. This is what the api call goes to:
public function Login(Request $request)
{
$user = Consultant::lookup('UserId', 1);
//Returns collection of user details (user id, username etc)
//Logic will go here in the future
$logThemIn = true;
if ($logThemIn)
{
auth()->login($user);
//return oauth2 token
}
}
So this is where im at now, if i run this, im getting the error:
'Declaration of App\Authentication\UserProvider::updateRememberToken(App\Authentication\Authenticatable $user, $token) must be compatible with Illuminate\Contracts\Auth\UserProvider::updateRememberToken(Illuminate\Contracts\Auth\Authenticatable $user, $token)'
Im new to laravel and there isnt alot of tutorials for what im trying to do that i can find. Any help is greatly appriciated
Change your UserProvider to this which uses Illuminate\Contracts\Auth\Authenticatable instead of App\Authentication\Authenticatable, php will load a class from the current namespace if one isn't specified.
<?php
namespace App\Authentication;
use Illuminate\Contracts\Auth\UserProvider as IlluminateUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
class UserProvider implements IlluminateUserProvider
{
/**
* #param mixed $identifier
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveById($identifier)
{
// Get and return a user by their unique identifier
}
/**
* #param mixed $identifier
* #param string $token
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByToken($identifier, $token)
{
// Get and return a user by their unique identifier and "remember me" token
}
/**
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $token
* #return void
*/
public function updateRememberToken(Authenticatable $user, $token)
{
// Save the given "remember me" token for the given user
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// Get and return a user by looking up the given credentials
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
// Check that given credentials belong to the given user
}
}
You forgot to import Authenticatable. Just add:
use Illuminate\Contracts\Auth\Authenticatable;
I've created successfully a BE user in an own extension for TYPO3 CMS 8.7.0.
Repository Injection:
/**
* beUserRepository
*
* #var \TYPO3\CMS\Extbase\Domain\Repository\BackendUserRepository
* #inject
*/
protected $beUserRepository = null;
Part of the ActionController:
$beuser = new \TYPO3\CMS\Extbase\Domain\Model\BackendUser();
$beuser->setUserName($username);
$beuser->setEmail($email);
$beuser->setRealName($realname);
$this->beUserRepository->add($beuser);
This works fine but I can't add a password like for FE users with setPassword(). Is there any way to get there or is it restricted for security reasons to set/change a BE user password?
Create your own BackendUser model in your extension
<?php
namespace YourVendor\YourExtKey\Domain\Model;
class BackendUser extends \TYPO3\CMS\Extbase\Domain\Model\BackendUser
{
/**
* #var string
*/
protected $password = '';
/**
* Returns the password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Sets the password
*
* #param string $password
* #return void
*/
public function setPassword($password)
{
$this->password = (string)$password;
}
}
Create your own Repository
<?php
namespace YourVendor\YourExtKey\Domain\Repository;
class BackendUserRepository extends \TYPO3\CMS\Extbase\Domain\Repository\BackendUserRepository
{
}
Then map your new domain model to be_users table:
plugin.tx_yourExtKey {
persistence {
classes {
YourVendor\YourExtKey\Domain\Model\BackendUser {
mapping {
tableName = be_users
}
}
}
}
}
Update your controller to use your new Repository
/**
* beUserRepository
*
* #var \YourVendor\YourExtKey\Domain\Repository\BackendUserRepository
* #inject
*/
protected $beUserRepository;
Back in your action
$beUser = new \YourVendor\YourExtKey\Domain\Model\BackendUser();
$saltFactory = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance('', 'BE');
$beUser->setPassword($saltFactory->getHashedPassword($newPassword));
TYPO3\CMS\Extbase\Domain\Model\BackendUser does not have a password property, so you can not set a password without extending the model. The easiest way would be if you create an own BackendUser model in your extension that extends the TYPO3\CMS\Extbase\Domain\Model\BackendUser and configure the mapping in TS. It just needs to have the $password property with getters/setters and a repository.
This is routes file and $users shows the values but login fails
Route::post('login', function () {
$user = array(
'username' => Input::get('username'),
'password' => Input::get('password')
);
if(Auth::attempt($user))
{
return Redirect::to('profile')
->with('flash_notice', 'You are successfully logged in.');
}
else
{
// authentication failure! lets go back to the login page
return Redirect::route('login')
->with('flash_error', 'Your username/password combination was incorrect.')
->withInput();
}
});
Modal:
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
When using the authentication mechanism provided by Laravel you should pass the following check list:
The database column where you store the password should be a string with a length of 60 characters.
The password should be stored encrypted, no plain. Since we are talking about BCrypt it has to be a value similar to: $2a$10$KssILxWNR6k62B7yiX0GAe2Q7wwHlrzhF3LqtVvpyvHZf0MwvNfVu.
You have to configure the security mechanism by editing the file located app/config/auth.php.
After that, you will eventually have your problem solved.