Cakephp 3 PHPUnit ingration test fails after the test before - cakephp

I have the UsersFixture with three records.
The test methods first() and second(), which both are before guest_can_login(), pr's show "Joe", "Joe", as expected. But with test method third(), which comes after guest_can_login(), I get notice error: trying to get property of non-object.
So, for a reason, something in the guest_can_login() breaks the rest of the test methods. I have tried by making a duplicate of guest_can_login() as well.
I think it is strange, as tearDown should "reset" everything after each test. I'm out of ideas. And after reading the Cakephp Testing docs, I haven't been able to solve it.
Any suggestions to help me solve this is much appreciated.
Code below (gist if you prefer: https://gist.github.com/chris-andre/2eb3ad053073caf4f1c81722428a900b):
public $fixtures = [
'app.users',
'app.tenants',
'app.roles',
'app.roles_users',
];
public $Users;
public function setUp()
{
parent::setUp();
$config = TableRegistry::getTableLocator()->exists('Users') ? [] : ['className' => UsersTable::class];
$this->Users = TableRegistry::getTableLocator()->get('Users', $config);
}
/**
* tearDown method
*
* #return void
*/
public function tearDown()
{
unset($this->Users);
TableRegistry::clear();
parent::tearDown();
}
/** #test */
public function guest_can_register()
{
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->configRequest([
'headers' => [
'host' => 'timbas.test'
]
]);
$data = [
'email' => 'chris#andre.com',
'first_name' => 'Christian',
'last_name' => 'Andreassen',
'password' => '123456',
'tenant' => ['name' => 'Test Company AS', 'domain' => 'testcomp', 'active' => true],
'active' => true
];
$this->post('/register', $data);
$this->assertResponseSuccess();
$this->assertRedirect(['controller' => 'Users', 'action' => 'login', '_host' => 'testcomp.timbas.test']);
$user = $this->Users->find()
->contain(['Tenants', 'Roles'])
->where(['Users.email' => 'chris#andre.com'])
->first();
}
/** #test */
public function first()
{
$users = $this->Users->find()->first();
pr($users->first_name);
}
/** #test */
public function second()
{
$users = $this->Users->find()->first();
pr($users->first_name);
}
/** #test */
public function guest_can_login()
{
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->configRequest([
'headers' => [
'host' => 'testcomp.timbas.test'
]
]);
$data = [
'email' => 'chris#andre.com',
'first_name' => 'Christian',
'last_name' => 'Andreassen',
'password' => '123456',
'tenant' => ['name' => 'Test Company AS', 'domain' => 'testcomp', 'active' => true],
'active' => true,
'roles' => ['_ids' => [ADMINISTRATOR_ROLE_ID]]
];
$user = $this->Users->newEntity($data, [
'associated' => ['Tenants', 'Roles']
]);
$this->Users->save($user);
$getNewUser = $this->Users->find()
->contain(['Roles'])
->where(['Users.email' => 'chris#andre.com'])
->first()
->toArray();
// pr($getNewUser->id);
$this->post('/users/login', [
'email' => 'chris#andre.com',
'password' => '123456'
]);
$this->assertSession($getNewUser, 'Auth.User');
}
/** #test */
public function third()
{
$users = $this->Users->find()->first();
pr($users->first_name);
}
EDIT 2018-08-06:
Users::register() is a global context, I cannot be accessed from url with subdomain. E.g. tenant1.domain.com/register will throw a badRequest, while domain.com/register is a valid url. On registration success, user is forwarded to login from right url. Login-url = Tenants.domain + domain + suffix, e.g. tenant1.domain.com. When user is on the tenant scope (url with subdomain), the Tenants.id where Tenants.domain = tenant1, will be added to the where clause in all queries for the models having the behavior attached.
Now, what happens in third() is that the tenant_id from the newly created Tenant in guest_can_login() is added to the query, which means the test "is still on the tenant scope" when third() is run. That is the problem.
The other problem is that setUp() is called on all test methods but third(). testDown() is called on every test methods.
App\Middleware\TenantMiddleware.php:
use InstanceConfigTrait;
/**
* Default config.
* Options:
* - globalScope: tells the middleware what controller and action tenant scope is not being used
* Example
* 'globalScope' => [
* 'Pages' => ['*'], // All actions in PagesController is global
* 'Users' => ['register'] // Register action in UsersController is global
* ]
* #var array
*/
protected $_defaultConfig = [
'globalScope' => [
'Users' => ['register'],
'Landing' => ['*'],
'Pages' => ['*']
],
];
public function __construct($config = [])
{
if (!isset($config['primaryDomain'])) {
$config['primaryDomain'] = Configure::read('Site.domain');
}
$this->setConfig($config);
}
/**
* Invoke method.
*
* #param \Cake\Http\ServerRequest $request The request.
* #param \Psr\Http\Message\ResponseInterface $response The response.
* #param callable $next Callback to invoke the next middleware.
* #return \Psr\Http\Message\ResponseInterface A response
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
// Get subdomains
$subdomains = $request->subdomains();
// If subdomains not empty, the first is always the tenants domain
$subdomain = !empty($subdomains) ? $subdomains[0] : '';
Tenant::setDomain($subdomain);
// Get params of current request
$params = $request->getAttribute('params');
$controller = $params['controller'];
$action = $params['action'];
// Set tenantScope as default
Tenant::setScope('tenant');
$globalScope = $this->getConfig('globalScope');
// If Controller and action is a global scope
if (array_key_exists($controller, $globalScope)) {
if (in_array($action, $globalScope[$controller]) || in_array('*', $globalScope[$controller])) {
Tenant::setScope('global');
}
}
if (
(Tenant::getScope() === 'tenant' && Tenant::tenant() === null)
|| (Tenant::getDomain() === '' && Tenant::getScope() === 'tenant')
|| (Tenant::getDomain() !== '' && Tenant::getScope() === 'global')
) {
throw new NotFoundException('The page you are looking for does not exists.');
}
$primaryDomain = $this->getConfig('primaryDomain');
if (array_key_exists($controller, $globalScope)) {
if (in_array($action, $globalScope[$controller]) && Tenant::getScope() === 'global') {
}
}
return $next($request, $response);
}
App\Model\Behavior\TenantScopeBehavior.php:
protected $_table;
/**
* Default configuration.
*
* #var array
*/
protected $_defaultConfig = [];
public function __construct(Table $table, array $config = [])
{
parent::__construct($table, $config);
}
public function beforeFind(Event $event, Query $query, ArrayObject $options)
{
$model = $this->_table->getAlias();
$foreig_key = 'tenant_id';
if (!isset($options['skipTenantCheck']) || $options['skipTenantCheck'] !== true) {
if (Tenant::getScope() === 'tenant') {
if ($model === 'Tenants') {
$query->where(['Tenants.id' => Tenant::tenant()->id]);
} else {
$query->where([$model . '.' . $foreig_key => Tenant::tenant()->id]);
}
}
}
return $query;
}
public function beforeSave(Event $event, Entity $entity, $options)
{
if (Tenant::getScope() === 'tenant') {
if ($entity->isNew()) {
$entity->tenant_id = Tenant::tenant()->id;
} else {
// Check if current tenant is owner
if ($this->_table->getAlias() === 'Tenants') {
if ($entity->id != Tenant::tenant()->id) {
throw new BadRequestException();
}
} else {
if ($entity->tenant_id != Tenant::tenant()->id) {
throw new BadRequestException();
}
}
}
}
return true;
}
public function beforeDelete(Event $event, Entity $entity, $options)
{
if (Tenant::getScope() === 'tenant') {
if ($entity->tenant_id != Tenant::tenant()->id) { //current tenant is NOT owner
throw new BadRequestException();
}
}
return true;
}
App\Tenant\Tenant.php:
/**
* $_domain will be empty or comtain the domain (subdomain from url)
* #var string can be empty
*/
protected static $_domain;
/**
* $_scope shall be 'global' or 'tenant'.
* #var string
*/
protected static $_scope;
/**
* #var null|object \App\Model\Entity\Tenant
*/
protected static $_tenant;
/**
* Gets domain from $_domain and returns the string
* #return string
*/
public static function getDomain()
{
return self::$_domain;
}
/**
* Set the tenant scope domain. Will be set in the TenantMiddleware, and shall not be set anywhere else
* #param string $domain
* #return string empty or with domain
*/
public static function setDomain($domain)
{
self::$_domain = $domain;
}
/**
* Tenant method
* Return the object \App\Model\Table\Tenants ro null
* #return type
*/
public static function tenant()
{
$tenant = static::_getTenant();
return $tenant;
}
protected static function _getTenant()
{
if (self::$_tenant === null) {
$cachedTenants = Cache::read('tenants');
if($cachedTenants !== false) {
// do something
}
$tenantsTable = TableRegistry::get('Tenants');
$tenant = $tenantsTable->find('all', ['skipTenantCheck' => true])
->where(['Tenants.domain' => self::getDomain()])
->where(['Tenants.active' => true])
->first();
self::$_tenant = $tenant;
}
return self::$_tenant;
}
public static function getScope()
{
return self::$_scope;
}
/**
* Description
* #param type $scope
* #return type
*/
public static function setScope($scope)
{
self::$_scope = $scope;
}

Related

Laravel - Show data from a specific user

I'm trying to create a listing that will only show lyrics from the logged user, but no succeed. Need help please.
Sorry for the image link, but my reputation is still low to add images.
As you can see I have two lyrics menus, one is already accessible only for admin users and the other to show the lyrics list for authenticated user.
How can I do that?
LyricController
{
public function __construct(){
$this->middleware('auth');
$this->middleware('can:admin-content');
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$lyrics = Lyric::orderBy('lyric', 'ASC')->paginate('10');
return view('admin.lyrics.index', [
'lyrics' => $lyrics,
]);
}
/**
* Show the form for creating a new resource
*
* #return \Illuminate\Http\Response
*/
public function create()
{
// load the create form (app/views/lyrics/create.blade.php)
return view('admin.lyrics.create');
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$data = $request->only([
'title',
// 'artist',
'info',
'video_url',
'lyric'
]);
$data['slug'] = Str::slug($data['title'], '-');
$validator = Validator::make($data, [
'title' => ['required', 'string', 'max:100'],
'slug' => ['required', 'string', 'max:100', 'unique:lyrics'],
// 'artist' => ['required', 'string', 'max:200'],
'info' => ['string', 'max:100'],
'video_url' => ['required', 'string', 'max:100', 'unique:lyrics'],
'lyric' => ['required', 'string'],
]);
if ($validator->fails()) {
return redirect()->route('lyrics.create')
->withErrors($validator)
->withInput();
}
// $artist = new Singer;
// $artist->artist = $data['artist'];
// $artist->save();
$lyric = new Lyric;
$lyric->title = trim($data['title']);
$lyric->slug = $data['slug'];
$lyric->info = $data['info'];
$lyric->video_url = $data['video_url'];
$lyric->lyric = $data['lyric'];
$lyric->save();
Session::flash('message', 'Música adicionada com sucesso!');
return redirect()->route('lyrics.index');
}
/**
* Display the specified resource
*
*
.
*
* #param \App\Lyric $lyric
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$lyric = Lyric::find($id);
return view('admin.lyrics.show', [
'lyric' => $lyric
]);
}
/**
* Show the form for editing the specified resource.
*
* #param \App\Lyric $lyric
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
$lyric = Lyric::find($id);
if ($lyric) {
return view('admin.lyrics.edit', [
'lyric' => $lyric
]);
}
return redirect()->route('lyrics.index');
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Lyric $lyric
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$lyric = Lyric::find($id);
if ($lyric) {
$data = $request->only([
'title',
// 'artist',
'info',
'video_url',
'lyric'
]);
if ($lyric['title'] !== $data['title']) {
$data['slug'] = Str::slug($data['title'], '-');
$validator = Validator::make($data, [
'title' => ['required', 'string', 'max:100'],
'info' => ['string', 'max:100'],
'video_url' => ['required', 'string', 'max:100', 'url'],
'slug' => ['required', 'string', 'max:100', 'unique:lyrics'],
'lyric' => ['string'],
]);
} else {
$validator = Validator::make($data, [
'title' => ['required', 'string', 'max:100'],
'info' => ['string', 'max:100'],
'video_url' => ['required', 'string', 'max:100', 'url'],
'lyric' => ['string'],
]);
}
if ($validator->fails()) {
return redirect()->route('lyrics.edit', [
'lyric' => $id
])
->withErrors($validator)
->withInput();
}
$lyric->title = trim($data['title']);
$lyric->info = $data['info'];
$lyric->video_url = $data['video_url'];
$lyric->lyric = $data['lyric'];
if (!empty($data['slug'])) {
$lyric->slug = $data['slug'];
}
$lyric->save();
}
Session::flash('message', 'Música alterada com sucesso!');
return redirect()->route('lyrics.index');
}
/**
* Remove the specified resource from storage.
*
* #param \App\Lyric $lyric
* #return \Illuminate\Http\Response
*/
public function destroy($id)
{
$lyric = Lyric::find($id);
$lyric->delete();
Session::flash('message', 'Música excluída com sucesso!');
return redirect()->route('lyrics.index');
}
Edit: I have just added foreign key to the Users Migration as suggested
Route: web.php
Route::get('/', 'Site\HomeController#index')->name('home');
Auth::routes();
Route::prefix('painel')->group(function(){
Route::get('/', 'Admin\HomeController#index')->name('admin');
/* Login Routing */
Route::get('/login', 'Admin\Auth\LoginController#index')->name('login');
Route::post('/login', 'Admin\Auth\LoginController#authenticate');
/* Logout Route */
Route::post('/logout', 'Admin\Auth\LoginController#logout')->name('logout');
Route::get('/logout', 'Admin\Auth\LoginController#logout')->name('logout-get');
/* Register Routing */
Route::get('/register', 'Admin\Auth\RegisterController#index')->name('register');
Route::post('/register', 'Admin\Auth\RegisterController#register');
/* Users Routing */
Route::resource('/users', 'Admin\UserController');
/* Profile Routing */
Route::get('/profile', 'Admin\ProfileController#index')->name('profile');
Route::put('/profilesave', 'Admin\ProfileController#save')->name('profile.save');
/* Lyrics Routing */
Route::resource('/lyrics', 'Admin\LyricController');
});
User.php (model):
public function lyrics()
{
return $this->hasMany(Lyric::class)->withTimestamps();
}
Lyric.php (model):
protected $guarded = ['id', 'singer_id', 'created_at', 'updated_at'];
public function singer()
{
return $this->belongsTo(Singer::class)->withTimestamps();
}
public function user()
{
return $this->belongsTo(User::class);
}
It would be really helpful if you could add more details in you question, but let me suggest a scenario to give you an idea of how it might be done:
Assuming you have Passport or JWT for user authentication, and that your user is logged in through api, and that you have 'Lyrics' model with user_id foreign key, you can do the following :
$logged_user = auth()->guard('api')->user();
if(isset($logged_user)){
$lyrics = Lyrics::where('user_id',$logged_user->id)->get();
//do whatever you need with lyrics
}
else{
// user not authenticated
return false;
}
More on authentication from Laravel documentation here
To show data from a logged in User. You need to establish an eloquent relationship between the User Model and The lyrics model.
Then you could just do something like $user -> lyrics()

Cannot see validation errors when form is invalid (submitted as json from react)

How do I see validation errors when posting json data and self submitting to a symfony form? isValid() is false but I can't access error messages to return. But the Symfony Profiler DOES show the error messages in the Ajax request history. E.g. when duplicate username the profiler shows:
Validator calls in ValidationListener.php
data.username There is already an account with this username
Forms "registration_form" "App\Form\RegistrationFormType"
There is already an account with this username
Caused by: Symfony\Component\Validator\ConstraintViolation
When all the fields are valid the new User is created in the database successfully as expected.
Here is my controller:
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\LoginFormAuthenticator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
class RegistrationController extends AbstractController
{
/**
* #Route("/api/register", name="app_register")
*/
public function register(
Request $request,
UserPasswordEncoderInterface $passwordEncoder,
GuardAuthenticatorHandler $guardHandler,
LoginFormAuthenticator $authenticator
): Response {
if ($request->isMethod('POST')) {
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$data = json_decode($request->getContent(), true);
$form->submit($data);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
// login the newly registered user
$login = $guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$authenticator,
'main' // firewall name in security.yaml
);
if ($login !== null) {
return $login;
}
return $this->json([
'username' => $user->getUsername(),
'roles' => $user->getRoles(),
]);
} else {
$formErrors = $form->getErrors(true); // returns {}
return $this->json($formErrors, Response::HTTP_BAD_REQUEST);
}
}
}
}
Here is my RegistrationFormType:
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class, [
'label' => 'First Name',
'required' => false
])
->add('lastName', TextType::class, [
'label' => 'Last Name',
'required' => false
])
->add('username')
->add('emailAddress', EmailType::class, [
'label' => 'Email Address'
])
->add('plainPassword', PasswordType::class, [
'mapped' => false
])
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'You must comply.',
]),
],
])
->add('Register', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'csrf_protection' => false
]);
}
public function getName()
{
return 'registration_form';
}
}
Here is my entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"username"}, message="There is already an account with this username")
* #UniqueEntity(fields={"emailAddress"}, message="There is already an account with this email address")
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $username;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $emailAddress;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $firstName;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $lastName;
public function getId(): ?int
{
return $this->id;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getEmailAddress(): ?string
{
return $this->emailAddress;
}
public function setEmailAddress(string $emailAddress): self
{
$this->emailAddress = $emailAddress;
return $this;
}
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(?string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(?string $lastName): self
{
$this->lastName = $lastName;
return $this;
}
}
Got it working.
$formErrors = [];
foreach ($form->all() as $childForm) {
if ($childErrors = $childForm->getErrors()) {
foreach ($childErrors as $error) {
$formErrors[$error->getOrigin()->getName()] = $error->getMessage();
}
}
}
return $this->json(
['errors' => $formErrors],
Response::HTTP_BAD_REQUEST
);
Returns:
{
"errors": {
"username": "There is already an account with this username"
}
}
Basically you get form errors at the top level like if there are extra fields etc but you get the form field errors from the actual child elements. You are only returning the top level form errors.
I have a helper function I use to return all errors from a form if I am returning a JsonResponse.
The following is my extended abstract controller. All my controllers extend this and then I can keep a few helper methods here.
I use the createNamedForm() method and keep the name blank as it's easier when sending from Ajax as I don't need to nest the data in the array with the form name as the key.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as SymfonyAbstractController;
use Symfony\Component\Form\FormInterface;
class AbstractController extends SymfonyAbstractController
{
protected function createNamedForm(string $name, string $type, $data = null, array $options = []): FormInterface
{
return $this->container->get('form.factory')->createNamed($name, $type, $data, $options);
}
protected function getFormErrors($form)
{
$errors = [];
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}
foreach ($form->all() as $childForm) {
if ($childForm instanceof FormInterface) {
if ($childErrors = $this->getFormErrors($childForm)) {
$errors[$childForm->getName()] = $childErrors;
}
}
}
return $errors;
}
}
I would create the form in the following way:
$form = $this->createNamedForm('', RegistrationFormType::class);
and return my form errors as follows:
return $this->json($this->getFormErrors($form), 400)
If you want to use the normal createForm() function then you will need to send the data in the following format:
['registration_form' => ['firstName' => 'Tom', 'lastName' => 'Thumb']]
This works in both Symfony4 and Symfony5

Can not config Header security middleware CakePHP

I have a site built with CakePHP, default it doesn't allow subdomain embed on iframe.
I configured Frame Option on nginx/conf.d and now my homepage can be embedded in an iframe of a subdomain.
However, another post cannot be embedded on a subdomain's iframe. (exp: http://example.net/postabc will not display). I tried to change options on Header security middleware(please read below).
Is there anywhere I need to change the configuration to so all my posts can be displayed?
<?php
namespace Cake\Http\Middleware;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class SecurityHeadersMiddleware
{
protected $headers = [];
public function noSniff()
{
$this->headers['x-content-type-options'] = 'nosniff';
return $this;
}
public function noOpen()
{
$this->headers['x-download-options'] = 'noopen';
return $this;
}
public function setReferrerPolicy($policy = 'same-origin')
{
$available = [
'no-referrer', 'no-referrer-when-downgrade', 'origin',
'origin-when-cross-origin',
'same-origin', 'strict-origin', 'strict-origin-when-cross-origin',
'unsafe-url'
];
$this->checkValues($policy, $available);
$this->headers['referrer-policy'] = $policy;
return $this;
}
public function setXFrameOptions($option = 'allow-from', $url = 'http://subdomain.example.net')
{
$this->checkValues($option, ['deny', 'sameorigin', 'allow-from']);
if ($option === 'allow-from') {
if (empty($url)) {
throw new InvalidArgumentException('The 2nd arg $url can not be empty when `allow-from` is used');
}
$option .= ' ' . $url;
}
$this->headers['x-frame-options'] = $option;
return $this;
}
public function setXssProtection($mode = 'block')
{
$mode = (string)$mode;
if ($mode === 'block') {
$mode = '1; mode=block';
}
$this->checkValues($mode, ['1', '0', '1; mode=block']);
$this->headers['x-xss-protection'] = $mode;
return $this;
}
public function setCrossDomainPolicy($policy = 'all')
{
$this->checkValues($policy, ['all', 'none', 'master-only', 'by-content-type', 'by-ftp-filename']);
$this->headers['x-permitted-cross-domain-policies'] = $policy;
return $this;
}
protected function checkValues($value, array $allowed)
{
if (!in_array($value, $allowed)) {
throw new InvalidArgumentException(sprintf(
'Invalid arg `%s`, use one of these: %s',
$value,
implode(', ', $allowed)
));
}
}
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
$response = $next($request, $response);
foreach ($this->headers as $header => $value) {
$response = $response->withHeader($header, $value);
}
return $response;
}
}

QueryException "Array To String Conversion" when seeding in laravel 5.4

when I try to seed database using php artisan db:seed
following exception is occured
Array to string conversion (SQL: insert into users (name, email, password, remember_token, verified, verification_token, admin, updated_at, created_at) values (Rosanna Nicolas, kuhn.wilhelm#example.net, $2y$10$bW.zAFI2rZaLSUKIsqoPLu24nH
otRIHRQkXYyKu8QwdcWRaOzblsC, l6ERPG47fC, 1, , 0, 2018-03-03 20:40:07, 2018-03-03 20:40:07))
this is my ModelFactory.php file
$factory->define(User::class, function (Faker\Generator $faker) {
static $password;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
'verified' => $verified = $faker->randomElement([User::VERIFIED_USER,User::UNVERIFIED_USER]),
'verification_token' => $verified == User::VERIFIED_USER ? null : User::generateVerificationCode(),
'admin' => $verified = $faker->randomElements([User::ADMIN_USER, User::REGULAR_USER]),
];});
this is my migration code
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->string('verified')->default(User::UNVERIFIED_USER);
$table->string('verification_token')->nullable();
$table->string('admin')->default(User::REGULAR_USER);
$table->timestamps();
});
}
this is my seeder class
public function run()
{
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
User::truncate();
Category::truncate();
Product::truncate();
Transaction::truncate();
DB::table('category_product') -> truncate();
$usersQuantity = 200;
$categoriesQuantity = 30;
$productsQuantity = 1000;
$transactionsQuantity = 1000;
factory(User::class, $usersQuantity)->create();
factory(Category::class, $categoriesQuantity)->create();
factory(Product::class, $productsQuantity)->create()->each(
function ($product) {
$categories = Category::all()->random(mt_rand(1, 5))->pluck('id');
$product->categories()->attach($categories);
});
factory(Transaction::class, $transactionsQuantity)->create();
}
and this is the model
class User extends Authenticatable{
use Notifiable;
const VERIFIED_USER = '1';
const UNVERIFIED_USER = '0';
const ADMIN_USER = '1';
const REGULAR_USER = '0';
protected $table = 'users';
protected $fillable = [
'name',
'email',
'password',
'verified',
'verification_token',
'admin',
];
protected $hidden = [
'password',
'remember_token',
'verification_token',
];
public function isVerified(){
return $this->verified == User::VERIFIED_USER;
}
public function isAdmin(){
return $this->admin == User::ADMIN_USER;
}
public static function generateVerificationCode(){
return str_random(40);
}
anyone can give the solution it will be grateful. !
Well for starters the randomElements function returns an array, not a string.

Symfony, how to render and save use form

Imagine I have an Article entity, and in this entity have a report attribute which is a json_array type.
Json_array's data like {"key1":"value1","ke2":"value2",...}.
Now I don't know how to use symfony form to render and save these json_array like other normal attribute(e.g.,title).
I searched many articles but I haven't find a clear way to realize it.
class Article
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var array
*
* #ORM\Column(name="report", type="json_array")
*/
private $report;
}
Here is my take on it. It sounds like you need a custom form type in a collection and a transformer. The idea is that you create a custom form type to house your key/value pairs, let's call it KeyValueType. Next you want to add this type to your ArticleType using a CollectionType as a wrapper. In order to turn your json to a useable data structure for the form and vice versa a transformer is used.
The KeyValueType class:
class KeyValueType extends AbstractType
{
public function buildForm($builder, $options)
{
$builder
->add('key')
->add('value');
}
}
The ArticleType class:
class ArticleType extends AbstractType
{
public function buildForm($builder, $options)
{
$builder
->add('report', CollectionType::class, [
'entry_type' => KeyValueType::class,
'allow_add' => true,
'allow_delete' => true,
]);
$builder->get('report')
->addModelTransformer(new CallbackTransformer(
function ($reportJson) {
// $reportJson has the structure {"key1":"value1","ke2":"value2",...}
if ($reportJson == null) {
return null;
}
$data = [];
foreach (json_decode($reportJson, true) as $key => $value) {
$data[] = ['key' => $key, 'value' => $value];
}
return $data;
},
function ($reportArray) {
// $reportArray has the structure [ [ 'key' => 'key1', 'value' => 'value1'], [ 'key' => 'key2', 'value' => 'value2'] ]
if ($reportArray == null) {
return null;
}
$data = [];
foreach ($reportArray as $report) {
$data[$report['key']] = $report['value'];
}
return json_encode($data);
}
));
}
}

Resources