Drupal 8 custom registration form - php

I try to build a custom registration form which should be displayed in a custom block and I don't want to insert the normal registration form and alter it via a hook or use an extension like form_block, because I want to learn the ways how drupal 8 works with forms.
My block looks like this:
<?php
namespace Drupal\ajax_registration_form\Plugin\Block;
use Drupal\ajax_registration_form\Form\AjaxRegistrationForm;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'AjaxRegistrationBlock' block.
*
* #Block(
* id = "ajax_registration_block",
* admin_label = #Translation("Ajax registration block"),
* )
*/
class AjaxRegistrationBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
$content = \Drupal::formBuilder()->getForm(AjaxRegistrationForm::class);
return $content;
}
}
My custom registration form looks like this:
<?php
namespace Drupal\ajax_registration_form\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\RegisterForm;
class AjaxRegistrationForm extends RegisterForm {
/**
* {#inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
return parent::form($form, $form_state);
}
}
I just tried to extend the normal RegisterForm and in the first step I just wanted to return the parent form to see if it works. But it doesn't...
Error message:
Fatal error: Call to a member function getEntityTypeId() on null in /Users/*******/Sites/priv/radweiser/web/core/lib/Drupal/Core/Entity/EntityForm.php on line 77
I think it's the missing user entity in the form, but I don't know how I can "put" this entity in my form.

I found the solution in the code of the formblock module.
I altered my block to something like this:
<?php
namespace Drupal\ajax_registration_form\Plugin\Block;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Block\Annotation\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'AjaxRegistrationBlock' block.
*
* #Block(
* id = "ajax_registration_block",
* admin_label = #Translation("Ajax registration block"),
* )
*/
class AjaxRegistrationBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The entity manager
*
* #var \Drupal\Core\Entity\EntityManagerInterface.
*/
protected $entityManager;
/**
* The entity form builder
*
* #var \Drupal\Core\Entity\EntityManagerInterface.
*/
protected $entityFormBuilder;
/**
* Constructs a new UserRegisterBlock plugin
*
* #param array $configuration
* A configuration array containing information about the plugin instance.
* #param string $plugin_id
* The plugin_id for the plugin instance.
* #param mixed $plugin_definition
* The plugin implementation definition.
* #param \Drupal\Core\Entity\EntityManagerInterface $entityManager
* The entity manager.
* #param \Drupal\Core\Entity\EntityFormBuilderInterface $entityFormBuilder
* The entity form builder.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entityManager, EntityFormBuilderInterface $entityFormBuilder) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entityManager;
$this->entityFormBuilder = $entityFormBuilder;
}
/**
* #inheritdoc
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('entity.form_builder')
);
}
/**
* Implements \Drupal\block\BlockBase::build().
*/
public function build() {
$build = array();
$account = $this->entityManager->getStorage('user') ->create(array());
$build['form'] = $this->entityFormBuilder->getForm($account, 'register');
$build['form']['account']['mail']['#description'] = t('');
kint($build['form']['account']);
return $build;
}
/**
*Implements \Drupal\block\BlockBase::blockAccess().
*
* #param \Drupal\Core\Session\AccountInterface $account
*
* #return bool|\Drupal\Core\Access\AccessResult
*/
public function blockAccess(AccountInterface $account) {
return ($account->isAnonymous()) && (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY);
}
}
Now I can alter the form and implement ajax logic using the Form Api (alter the mail input description as example)

I have placed this block in front page content but it is not visible
<span data-big-pipe-placeholder-id="callback=Drupal%5Cblock%5CBlockViewBuilder%3A%3AlazyBuilder&args%5B0%5D=userregistrationform&args%5B1%5D=full&args%5B2%5D&token=-MyZMBSv_tseO1_TExVECdGUQnyGrlvE9eST64je7Ho"></span>
it shows like this
FYI I have changed permissions to isAnonymous()

Related

Laravel model pass to view

Im using this library: https://www.laravelplay.com/packages/ycs77::laravel-wizard
I did all steps and have the same result like in the example.
Im trying to get data from database to each step.
Model (App/steps/intro/DropboxStep.php):
<?php
namespace App\Steps\Intro;
use Illuminate\Http\Request;
use Ycs77\LaravelWizard\Step;
use DB;
class DropboxStep extends Step
{
/**
* The step slug.
*
* #var string
*/
protected $slug = 'dropbox';
/**
* The step show label text.
*
* #var string
*/
protected $label = 'Dropbox';
/**
* The step form view path.
*
* #var string
*/
protected $view = 'steps.intro.dropbox';
/**
* Set the step model instance or the relationships instance.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation|null
*/
public function model(Request $request)
{
//
}
/**
* Save this step form data.
*
* #param \Illuminate\Http\Request $request
* #param array|null $data
* #param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation|null $model
* #return void
*/
public function saveData(Request $request, $data = null, $model = null)
{
//
}
/**
* Validation rules.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function rules(Request $request)
{
return [];
}
public function getOptions()
{
$stepa2 = DB::table('tutorials')->where('id', '2')->first();
return [
'stepa2' => $stepa2,
'Lucas',
];
}
}
View:
<div class="form-group">
{{ $stepa2 }}
</div>
Result:
Undefined variable: stepa2
Tried also through controller (IntroWizardController.php)
This is default controller:
<?php
namespace App\Http\Controllers;
use App\Steps\Intro\DropboxStep;
use App\Steps\Intro\H2NStep;
use App\Steps\Intro\PT4Step;
use Ycs77\LaravelWizard\Wizardable;
use DB;
class IntroWizardController extends Controller
{
use Wizardable;
/**
* The wizard name.
*
* #var string
*/
protected $wizardName = 'intro';
/**
* The wizard title.
*
* #var string
*/
protected $wizardTitle = 'Intro';
/**
* The wizard options.
*
* #var array
*/
protected $wizardOptions = [];
/**
* The wizard steps instance.
*
* #var array
*/
protected $steps = [
DropboxStep::class,
H2NStep::class,
PT4Step::class,
];
}
I added:
public function getOptions()
{
$stepa2 = DB::table('tutorials')->where('id', '2')->first();
return [
'stepa2' => $stepa2,
'Lucas',
];
}
Tried also in controller return to view, but then I get result from database in blank page, not with other parts.
Is it possible to pass database query to view with this library?
Thanks
EDIT:
With this routes:
Route::get('wizard/intro/dropbox', 'IntroWizardController#step1a', 'wizard.intro.dropbox');
Wizard::routes('wizard/intro', 'IntroWizardController', 'wizard.intro');
I get my result from database, but like I said before in white blank page:
But I want to get in this view like others (without query):
To pass any data from controller to blade view simply use these 2 options
option 1:
public function someFunction()
{
$model = DB::table('model_table')->where('id', '2')->first();
return view('blade_view_name', compact('model'));
}
option 2:
public function someFunction()
{
$model = DB::table('model_table')->where('id', '2')->first();
return view('blade_view_name')->with('model', $model);
}
if you have more or want more of variables you can chain the with() method like this:
return view('blade_view_name')
->with('model', $model)
->with('variable', 'Some other variable');

File not found exception on Symfony upload

I'm using Symfony 3.4 to work on a simple REST API microservice. There are not much resources to be found when working with HTTP APIs and file uploads. I'm following some of the instructions from the documentation but I found a wall.
What I want to do is to store the relative path to an uploaded file on an entity field, but it seems like the validation expects the field to be a full path.
Here's some of my code:
<?php
// BusinessClient.php
namespace DemoBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use ApiBundle\Entity\BaseEntity;
use ApiBundle\Entity\Client;
use JMS\Serializer\Annotation as Serializer;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints;
/**
* Class BusinessClient
* #package DemoBundle\Entity
* #ORM\Entity(repositoryClass="DemoBundle\Repository\ClientRepository")
* #ORM\Table(name="business_client")
* #Serializer\ExclusionPolicy("all")
* #Serializer\AccessorOrder("alphabetical")
*/
class BusinessClient extends BaseEntity
{
/**
* #Constraints\NotBlank()
* #ORM\ManyToOne(targetEntity="ApiBundle\Entity\Client", fetch="EAGER")
* #ORM\JoinColumn(name="client_id", referencedColumnName="oauth2_client_id", nullable=false)
*/
public $client;
/**
* #Constraints\NotBlank()
* #ORM\Column(type="string", length=255, nullable=false)
* #Serializer\Expose
*/
protected $name;
/**
* #Constraints\Image(minWidth=100, minHeight=100)
* #ORM\Column(type="text", nullable=true)
*/
protected $logo;
/**
* One Business may have many brands
* #ORM\OneToMany(targetEntity="DemoBundle\Entity\Brand", mappedBy="business")
* #Serializer\Expose
*/
protected $brands;
/**
* BusinessClient constructor.
*/
public function __construct()
{
$this->brands = new ArrayCollection();
}
/**
* Set the links property for the resource response
*
* #Serializer\VirtualProperty(name="_links")
* #Serializer\SerializedName("_links")
*/
public function getLinks()
{
return [
"self" => "/clients/{$this->getId()}",
"brands" => "/clients/{$this->getId()}/brands"
];
}
/**
* Get the name of the business client
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the name of the business client
*
* #param string $name
*/
public function setName($name): void
{
$this->name = $name;
}
/**
* Get the logo
*
* #Serializer\Expose
* #Serializer\VirtualProperty(name="logo")
* #Serializer\SerializedName("logo")
*/
public function getLogo()
{
return $this->logo;
}
/**
* Set the logo field
*
* #param string|File|UploadedFile $logo
*/
public function setLogo($logo): void
{
$this->logo = $logo;
}
/**
* Get the client property
*
* #return Client
*/
public function getClient()
{
return $this->client;
}
/**
* Set the client property
*
* #param Client $client
*/
public function setClient($client): void
{
$this->client = $client;
}
}
Uploader Service:
<?php
namespace DemoBundle\Services;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Class FileUploader
* #package DemoBundle\Services
*/
class FileUploader
{
/** #var string $uploadDir The directory where the files will be uploaded */
private $uploadDir;
/**
* FileUploader constructor.
* #param $uploadDir
*/
public function __construct($uploadDir)
{
$this->uploadDir = $uploadDir;
}
/**
* Upload a file to the specified upload dir
* #param UploadedFile $file File to be uploaded
* #return string The unique filename generated
*/
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move($this->getTargetDirectory(), $fileName);
return $fileName;
}
/**
* Get the base dir for the upload files
* #return string
*/
public function getTargetDirectory()
{
return $this->uploadDir;
}
}
I've registered the service:
services:
# ...
public: false
DemoBundle\Services\FileUploader:
arguments:
$uploadDir: '%logo_upload_dir%'
And last the controller:
<?php
namespace DemoBundle\Controller;
use ApiBundle\Exception\HttpException;
use DemoBundle\Entity\BusinessClient;
use DemoBundle\Services\FileUploader;
use FOS\RestBundle\Controller\Annotations as REST;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Swagger\Annotations as SWG;
use Symfony\Component\Validator\Constraints\ImageValidator;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Class BusinessClientController
* #package DemoBundle\Controller
*/
class BusinessClientController extends BaseController
{
/**
* Create a new business entity and persist it in database
*
* #REST\Post("/clients")
* #SWG\Tag(name="business_clients")
* #SWG\Response(
* response="201",
* description="Create a business client and return it's data"
* )
* #param Request $request
* #param FileUploader $uploader
* #return Response
* #throws HttpException
*/
public function createAction(Request $request, FileUploader $uploader, LoggerInterface $logger)
{
$entityManager = $this->getDoctrine()->getManager();
$oauthClient = $this->getOauthClient();
$data = $request->request->all();
$client = new BusinessClient();
$client->setName($data["name"]);
$client->setClient($oauthClient);
$file = $request->files->get('logo');
if (!is_null($file)) {
$fileName = $uploader->upload($file);
$client->setLogo($fileName);
}
$errors = $this->validate($client);
if (count($errors) > 0) {
$err = [];
/** #var ConstraintViolationInterface $error */
foreach ($errors as $error) {
$err[] = [
"message" => $error->getMessage(),
"value" => $error->getInvalidValue(),
"params" => $error->getParameters()
];
}
throw HttpException::badRequest($err);
}
$entityManager->persist($client);
$entityManager->flush();
$r = new Response();
$r->setContent($this->serialize($client));
$r->setStatusCode(201);
$r->headers->set('Content-type', 'application/json');
return $r;
}
/**
* Get data for a single business client
*
* #REST\Get("/clients/{id}", requirements={"id" = "\d+"})
* #param $id
* #return Response
* #SWG\Tag(name="business_clients")
* #SWG\Response(
* response="200",
* description="Get data for a single business client"
* )
*/
public function getClientAction($id)
{
$object = $this->getDoctrine()->getRepository(BusinessClient::class)
->find($id);
$j = new Response($this->serialize($object));
return $j;
}
}
When I try to set the logo as a file basename string the request will fail. with error that the file (basename) is not found. This makes sense in a way.
If otherwise I try to set not a string but a File with valid path to the newly uploaded file the request will succeed, but the field in the table will be replaced with a full system path. The same happens when I put a valid system path instead of a file.
<?php
// Controller
.....
// This works
if (!is_null($file)) {
$fileName = $uploader->upload($file);
$client->setLogo($this->getParameter("logo_upload_dir")."/$fileName");
}
Parameter for the upload dir:
parameters:
logo_upload_dir: '%kernel.project_dir%/web/uploads/logos'
I'm not using any forms as this is a third party API and I'm mainly using the request objects directly to handle the data. Most of the documentations used Forms to handle this. Also all my responses are in JSON.
I'd appreciate any help on this. Otherwise I'll have to store the full path and that in not a good idea and very impractical.
Thanks in advance.
Here is a thought on this: Instead of validating the property which your plan to be a relative path to an image, validate the method. Something like this maybe:
class BusinessClient extends BaseEntity
{
public static $basePath;
// ....
/**
* Get the logo
*
* #Constraints\Image(minWidth=100, minHeight=100)
*/
public function getAbsolutePathLogo()
{
return self::$basePath . '/' . $this->logo;
}
So, remove the validation from your logo member, add a new method (I named it getAbsolutePathLogo buy you can choose anything) and set up validation on top of it.
This way, your logo will be persisted as relative path and validation should work. However, the challenge now is to determine the right moment to set that static $basePath. In reality, this one does not even need to be a class static member, but could be something global:
return MyGlobalPath::IMAGE_PATH . '/' . $this->logo;
Does this make sense?
Hope it helps a bit...

CodeIgniter Models vs Symfony/Doctrine Models

Background:
I have build my web application using CodeIgniter because it was the only framework I could grasp easily enough to get going quickly. Now seeing the unbelievably advanced functionality of symfony and the PSR standards I am hyped to get into it all.
Dialemma
I am not sure how to approach the model layer with symfony/doctrine. As I understand it: doctrine generates an entity class for a database table like so...
This class contains a bunch of setter/getter functions.
My mental block at the moment is that I don't understand how I am supposed to add to functionality to my model layer.
To understand where I am coming from take a look at a typical CodeIgniter Model that I am currently working with. This one handles discount coupons.
<?php
/**
* This class handles all coupon codes
*/
class Coupon_Model extends CI_Model
{
/**
* gets a specific coupon
* #param string $coupon_code
* #return obj
*/
public function getCoupon($coupon_code)
{
$this->db->where('coupon_code', $coupon_code);
$query = $this->db->get('coupons');
return $query->row();
}
/**
* gets all coupons associated with a course
* #param int $course_id
* #return array
*/
public function getCourseCoupons($course_id)
{
$this->db->where('course_id', $course_id);
$query = $this->db->get('coupons');
return $query->result();
}
/**
* generates a string of 10 random alphanumeric numbers
* #return string
*/
public function generateCouponCode()
{
return strtoupper(substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
}
/**
* creates a new active coupon
* #param array $data
* #param string $coupon_code
* #return bool
*/
public function createCoupon($data, $coupon_code = null)
{
if ($coupon_code !== '') {
$data['coupon_code'] = $coupon_code;
} else {
$data['coupon_code'] = $this->generateCouponCode();
}
return $this->db->insert('coupons', $data);
}
/**
* checks if a coupon is valid
* #param string $coupon_code
* #param int $course_id
* #return bool
*/
public function checkCoupon($coupon_code, $course_id = null)
{
$this->db->where('coupon_code', $coupon_code);
$query = $this->db->get('coupons');
$coupon = $query->row();
// if coupon code exists
if ($coupon === null) {
return false;
}
// if coupon is for the right course
if ($coupon->course_id !== $course_id && $course_id !== null) {
return false;
}
// if coupon code has not expired
if ($coupon->expiry_date <= $this->Time_Model->getCarbonNow()->timestamp) {
return false;
}
return true;
}
/**
* deletes a coupon record
* #param int coupon_id
* #return bool
*/
public function deleteCoupon($coupon_id)
{
$this->db->where('coupon_id', $coupon_id);
return $this->db->delete('coupons');
}
/**
* applys the coupon discount
* #param int $price
* #param float $discount (percentage)
*/
public function applyDiscount($price, $discount)
{
$price = $price - (($discount / 100) * $price);
return $price;
}
}
As you can see it is pretty straight forward, if I wanted to add functionality I would literally just create a new function.
To use this model I would simply load it on the Controller like this:
$this->model->load('coupons/Coupon_Model');
$this->Coupon_Model->getCoupon($coupon_code);
Simple, done and dusted... unfortunately I am not sure how to implement this sort of functionality with symfony/doctrine.
Will I need to create a new class separate from the entity and add extra functionality to this class? Or should I add more functions to the entity class?
Take for example my simple function which generates the coupon code:
/**
* generates a string of 10 random alphanumeric numbers
* #return string
*/
public function generateCouponCode()
{
return strtoupper(substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
}
Where would be the best place to put this function? Under AppBundle/models/coupons?
I have clearly picked up bad habits from CodeIgniter and have a feeling that I am approaching this the wrong way.
Symfony + Doctrine ORM comes with a lot of the default needs for the replacement of CodeIgniter models by using the EntityManager within your Controller(s).
For example
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController extends Controller
{
/**
* #Route("/{id}/show", name="app_show", defaults={"id" = 1})
*/
public function showAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
if (!$coupon = $em->find('AppBundle:Coupon', $id)) {
throw new NotFoundException('Unknown Coupon Specified');
}
//see below to see how this was implemented
$similarCoupons = $em->getRepository('AppBundle:Coupon')
->filterCourse($coupon->course);
return $this->render('AppBundle:template.twig', [
'coupon' => $coupon,
'similarCoupons' => $similarCoupons
]);
}
/**
* #Route("/new", name="app_new")
*/
public function newAction(Request $request)
{
//use Symfony Form Component instead
$em = $this->getDoctrine()->getManager();
$coupon = new \AppBundle\Entity\Coupon;
//calls __construct to call generateCouponCode
$coupon->setName($request->get('name'));
$em->persist($coupon);
$em->flush();
return $this->redirectToRoute('app_show', ['id' => $coupon->getId()]);
}
//...
}
You want to specify the functionality you want each entity to have when working with it from within the Entity class.
That it becomes available without needing to revisit the repository, since an Entity should never be aware of the EntityManager.
In effect, each Entity can be considered their own models.
For example $coupon->generateCouponCode(); or $this->generateCouponCode() from within the entity.
Otherwise you would use a Repository of your Doctrine Database Entity(ies) to add more complex functionality.
// /src/AppBundle/Entity/Coupon.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repository="CouponRepository")
*/
class Coupon
{
/**
* #var integer
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=50)
*/
private $name;
/**
* #var string
* #ORM\Column(name="coupon_code", type="string", length=10)
*/
private $couponCode;
/**
* #var Course
* #ORM\ManyToOne(targetEntity="Course", inversedBy="coupons")
* #ORM\JoinColumn(name="course", referencedColumnName="id")
*/
private $course;
//...
public function __construct()
{
//optionally create code when persisting a new database entry by using LifeCycleCallbacks or a Listener instead of this line.
$this->couponCode = $this->generateCouponCode();
}
//...
/**
* generates a string of 10 random alphanumeric numbers
* #return string
*/
public function generateCouponCode()
{
return strtoupper(substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10));
}
}
Then your custom queries would go into your Repository.
// /src/AppBundle/Entity/CouponRepository.php
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class CouponRepository extends EntityRepository
{
/**
* filters a collection of Coupons that matches the supplied Course
* #param Course $course
* #return array|Coupons[]
*/
public function filterCourse(Course $course)
{
$qb = $this->createQueryBuilder('c');
$expr = $qb->expr();
$qb->where($expr->eq('c.course', ':course'))
->setParameter('course', $course);
return $qb->getQuery()->getResult();
}
}
Additionally you can filter collections of an association (Foreign Key) reference within your entity.
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
//...
class Course
{
//...
/**
* #var ArrayCollection|Coupon[]
* #ORM\OneToMany(targetEntity="Coupon", mappedBy="course")
*/
private $coupons;
public function __construct()
{
$this->coupons = new ArrayCollection;
}
/**
* #return ArrayCollection|Coupon[]
*/
public function getCoupons()
{
return $this->coupons;
}
/**
* #param string $name
* #return \Doctrine\Common\Collections\Collection|Coupon[]
*/
public function getCouponsByName($name)
{
$criteria = Criteria::create();
$expr = $criteria::expr();
return $this->coupons->matching($criteria->where($expr->eq('name', $name)));
}
}

Laravel 4.2: Troubleshooting "Target not instantiable" error

I've been stuck on this "Target not instantiable" error for the last 2-3 days and I have no idea why. Someone in the IRC #laravel chat room, yesterday, suggested using var_dump(App::make('Project\Frontend\Repo\Lead\LeadInterface')) when in Artisan's tinker interface which I've done and got this response:
class Project\Frontend\Repo\Lead\EloquentLead#798 (1) {
protected $lead =>
class Lead#809 (21) {
// bunch of info about the model etc relating to the interface and it's repo class
}
}
One of the members in #laravel pointed out that this meant the interface was instantiated which is great but then I realised this isn't the interface that I'm having a problem with.
So below is how I have things setup. The interface that's apparently not instantiated is ValidableInterface (last portion of code) and running var_dump(App::make('Project\Backend\Service\Validation\ValidableInterface'))
returns the dreaded "Target not instantiable" error.
EDIT: new LeadFormLaravelValidator( $app['validator'] ) extends AbstractLaravelValidator which implements ValidableInterface.
Am I missing some glaring problem with my code?
My service provider
<?php namespace Project\Frontend\Service\Form;
use Illuminate\Support\ServiceProvider;
use Project\Frontend\Service\Form\Lead\LeadForm;
use Project\Frontend\Service\Form\Lead\LeadFormLaravelValidator;
class FormServiceProvider extends ServiceProvider {
/**
* Register the binding
*
* #return void
*/
public function register()
{
$app = $this->app;
$app->bind('Project\Frontend\Service\Form\Lead\LeadForm', function($app)
{
return new LeadForm(
new LeadFormLaravelValidator( $app['validator'] ),
$app->make('Project\Frontend\Repo\Lead\LeadInterface')
);
});
}
}
My form class
<?php namespace Project\Frontend\Service\Form\Lead;
use Project\Backend\Service\Validation\ValidableInterface;
use Project\Frontend\Repo\Lead\LeadInterface;
class LeadForm {
/**
* Form Data
*
* #var array
*/
protected $data;
/**
* Validator
*
* #var \Project\Backend\Service\Validation\ValidableInterface
*/
protected $validator;
/**
* Lead repository
*
* #var \Project\Frontend\Repo\Lead\LeadInterface
*/
protected $lead;
public function __construct(ValidableInterface $validator, LeadInterface $lead)
{
$this->validator = $validator;
$this->lead = $lead;
}
My validation rules
<?php namespace Project\Frontend\Service\Form\Lead;
use Project\Backend\Service\Validation\AbstractLaravelValidator;
class LeadFormLaravelValidator extends AbstractLaravelValidator {
/**
* Validation rules
*
* #var Array
*/
protected $rules = array(
'name' => 'required|regex:/^[a-zA-Z-\s]+$/',
'email' => 'email',
'cell' => 'required|numeric|digits_between:10,11',
);
/**
* Validation messages
*
* #var Array
*/
protected $messages = array(
'regex' => 'The :attribute may only contain letters, dashes and spaces.',
'digits_between' => 'The :attribute must be 10 numbers long.',
);
}
My abstract validator
<?php namespace Project\Backend\Service\Validation;
use Illuminate\Validation\Factory;
abstract class AbstractLaravelValidator implements ValidableInterface {
/**
* Validator
*
* #var \Illuminate\Validation\Factory
*/
protected $validator;
/**
* Validation data key => value array
*
* #var Array
*/
protected $data = array();
/**
* Validation errors
*
* #var Array
*/
protected $errors = array();
/**
* Validation rules
*
* #var Array
*/
protected $rules = array();
/**
* Custom validation messages
*
* #var Array
*/
protected $messages = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
/**
* Set data to validate
*
* #return \Project\Backend\Service\Validation\AbstractLaravelValidator
*/
public function with(array $data)
{
$this->data = $data;
return $this;
}
/**
* Validation passes or fails
*
* #return Boolean
*/
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules, $this->messages);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
/**
* Return errors, if any
*
* #return array
*/
public function errors()
{
return $this->errors;
}
}
My validator interface
<?php namespace Project\Backend\Service\Validation;
interface ValidableInterface {
/**
* Add data to validation against
*
* #param array
* #return \Project\Backend\Service\Validation\ValidableInterface $this
*/
public function with(array $input);
/**
* Test if validation passes
*
* #return boolean
*/
public function passes();
/**
* Retrieve validation errors
*
* #return array
*/
public function errors();
}
I believe the problem is $app->make('Project\Frontend\Repo\Lead\LeadInterface'). Laravel has no way of knowing what class to instantiate here. You have to tell Laravel by doing:
$app->bind('Project\Frontend\Repo\Lead\LeadInterface', 'Your\Implementation\Of\LeadInterface');
Edit
It's weird that you get that exception since you manually instantiate LeadForm and inject the LeadFormLaravelValidator. However this should probably resolve the issue:
$app->bind('Project\Backend\Service\Validation\ValidableInterface',
'Project\Frontend\Service\Form\Lead\LeadFormLaravelValidator');

Wrong instance passed in Laravel DB post request

So I am working on a page in Laravel that generates invite codes upon email submission. I have run into this issue, every time when I enter my email into the form, it is supposed to generate an invite code an input it into my DB then redirect me. Instead I get this error code:
Argument 1 passed to myapp\Repositories\Invite\EloquentInviteRepository::__construct()
must be an instance of Illuminate\Database\Eloquent\Model, instance of
Illuminate\Foundation\Application given, called in /var/www/laravel/bootstrap/compiled.php
on line 4259 and defined
This is my EloquentInviteRepository.php file, apparently line 21 is the line in error:
<?php namespace myapp\Repositories\Invite;
use myapp\Repositories\Crudable;
use Illuminate\Support\MessageBag;
use myapp\Repositories\Repository;
use Illuminate\Database\Eloquent\Model;
use myapp\Repositories\AbstractRepository;
class EloquentInviteRepository extends AbstractRepository implements Repository, Crudable, InviteRepository {
/**
* #var Illuminate\Database\Eloquent\Model
*/
protected $model;
/**
* Construct
*
* #param Illuminate\Database\Eloquent\Model $user
*/
public function __construct(Model $model)
{
parent::__construct(new MessageBag);
$this->model = $model;
}
/**
* Find a valid invite by a code
*
* #param string $code
* #return Illuminate\Database\Eloquent\Model
*/
public function getValidInviteByCode($code)
{
return $this->model->where('code', '=', $code)
->where('claimed_at', '=', null)
->first();
}
/**
* Create
*
* #param array $data
* #return Illuminate\Database\Eloquent\Model
*/
public function create(array $data)
{
$data['code'] = bin2hex(openssl_random_pseudo_bytes(16));
return $this->model->create($data);
}
/**
* Update
*
* #param array $data
* #return Illuminate\Database\Eloquent\Model
*/
public function update(array $data){}
/**
* Delete
*
* #param int $id
* #return boolean
*/
public function delete($id){}
}
In case anyone was curious; the __construct() interface from Illuminate\Database\Eloquent\Model.php:
/**
* Create a new Eloquent model instance.
*
* #param array $attributes
* #return void
*/
public function __construct(array $attributes = array())
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
and Illuminate\Foundation\Application.php:
/**
* Create a new Illuminate application instance.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
public function __construct(Request $request = null)
{
$this->registerBaseBindings($request ?: $this->createNewRequest());
$this->registerBaseServiceProviders();
$this->registerBaseMiddlewares();
}
In case these help in debugging the issue!
As per request I have included my controller element used during the post function, this is the part that seems to activate the repository and prompt the error:
<?php
use myapp\Repositories\Invite\InviteRepository;
class InviteController extends BaseController {
/**
* InviteRepository
*
* #var myapp\Repositories\Invite\InviteRepository
*/
protected $repository;
/**
* Create a new instance of the InviteController
*
* #param myapp\Repositories\Invite\InviteRepository
*/
public function __construct(InviteRepository $repository)
{
$this->repository = $repository;
}
/**
* Create a new invite
*
* #return Response
*/
public function store()
{
$invite = $this->repository->create(Input::all());
}
}
RepositoryServiceProvider.php
<?php namespace myapp\Repositories;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider {
/**
* Register
*/
public function register()
{
$this->registerInviteRepository();
}
/**
* Register the Invite Repository
*
* #return void
*/
public function registerInviteRepository()
{
$this->app->bind('myapp\Repositories\Invite\InviteRepository', function($app)
{
return new EloquentInviteRepository( new Invite );
});
}
}
Any idea's as to what I am missing?
Thanks for the help guys,
You've been a great resource so far!
In the file RepositoryServiceProvider.php, replace this
$this->app->bind('myapp\Repositories\Invite\InviteRepository', function($app)
{
return new EloquentInviteRepository( new Invite );
});
With this:
$this->app->bind('myapp\Repositories\Invite\InviteRepository',
'myapp\Repositories\Invite\EloquentInviteRepository');

Categories